Commit Graph
1973 Commits
Author SHA1 Message Date
Matthew Meszaros 1cb2b65cd7 Merge remote-tracking branch 'origin/main' into feat/link-click-attribution 2026-09-03 01:42:44 -07:00
Matthew Meszaros e8393eaeb8 feat: identify links by ticket in the click burst check so two tickets sharing a destination still count as a scanner walking the email, re-read a deferred human click's classification with retries and skip its effects entirely when the read keeps failing instead of assuming it was a person, and document the burst window plus one second wait 2026-09-03 01:36:37 -07:00
Matthew Meszaros 8096ca047c feat: hold a human click's side effects (evidence, instant actions, webhook, live event) until the click burst window has passed and re-read its classification first, so the first click of a security scanner's burst can no longer fire a clicked automation or webhook before the second click reveals the burst; the stamp, log row and dedupe mark are still written immediately and walked back as before 2026-09-03 01:29:54 -07:00
Matthew Meszaros d3a9afd775 Merge pull request #299 from warmbly/fix/profile-avatar-persistence
Fix profile and workspace avatar changes not persisting after refresh
2026-09-03 01:27:13 -07:00
Matthew Meszaros b785d3b86b feat: address review on per-link click attribution: read bare href values and ignore data-href when tracking anchors, compare the destination host instead of substring-matching the tracking domain, tag bare URLs in plain-text bodies when UTM tagging is on, count UTM limits in characters, expose the UTM overrides in the AI campaign tool, dedupe clicks by ticket so two links sharing a destination are two clicks, count machine_clicks only for steps with machine and no human clicks, never withdraw a click stamp that predates per-link logging, keep the coarse timeline click unless a logged click stands for it, resolve the link once per event, give auto-clicks their own tooltip and make the wizard's discard guard notice toggled settings 2026-09-03 01:20:48 -07:00
Matthew Meszaros 117affbb09 Merge remote-tracking branch 'origin/main' into fix/profile-avatar-persistence 2026-09-03 01:20:48 -07:00
Matthew Meszaros 3601147c7c feat: address review on the avatar persistence fix: avatar object keys carry a random nonce next to the millisecond epoch so two uploads in the same millisecond cannot share an immutably cached URL, the org avatar hooks capture the target workspace id when the mutation starts and patch the org pointer, list and current-org caches only for that id so a mid-flight workspace switch cannot stamp the avatar onto the wrong org, and the API docs describe the stored file deletion on remove as best effort 2026-09-03 01:15:10 -07:00
Matthew Meszaros 6f2918748f Merge pull request #297 from warmbly/fix/forms-dockerfile-pnpm-build
fix: keep host node_modules out of the Docker build context so the forms image builds (#292)
2026-09-03 01:08:01 -07:00
Matthew Meszaros f579497c81 feat: make profile and workspace avatar changes survive a refresh: user avatar upload and remove go through the user service so the cached /auth/me copy is dropped instead of serving the old avatar for 30 minutes, the org list mapper carries avatar_url into the persisted workspace pointer it previously discarded, the avatar mutation hooks patch the cached user, org pointer and org queries with the server's answer before invalidating, all four handlers delete the replaced or removed object under our own avatars/ prefix with millisecond keys so quick replacements cannot collide on an immutable URL, the org avatar removal is audited, the uploader copy stops promising WebP and GIF, and the API docs note that remove deletes the stored file (fixes #295) 2026-09-03 01:06:36 -07:00
Matthew Meszaros 99be92f159 feat: attribute every campaign click to the exact link with a per-link click log (email_link_clicks) behind the contact activity timeline, campaign live feed, recent activity and the email_clicked webhook, add per-campaign automatic UTM tagging (utm_tracking with source, medium and campaign overrides, utm_content from the link text) applied at send time to the stored ticket destination, and classify opens and clicks as machine when they arrive within ten seconds of dispatch or when several links of one email are followed within five seconds, so scanner clicks are logged but never count as engagement, fire automations or send webhooks 2026-09-03 01:05:34 -07:00
Matthew Meszaros 16eed14501 feat: keep every frontend node_modules and build output out of the root Docker build context so a host forms/node_modules from a native make forms or make dev no longer overwrites the forms image's own pnpm install and aborts the build with ERR_PNPM_ABORTED_REMOVE_MODULES_DIR_NO_TTY, set CI=true in the forms, web and admin app build stages so pnpm reinstalls instead of prompting in a build with no TTY, and document the symptom on the troubleshooting page (#292) 2026-09-03 01:04:27 -07:00
Matthew Meszaros c8df7fb7cb Merge pull request #290 from warmbly/fix/ios-safari-index-scroll-crash
fix: iOS Safari crash on the index page, and phones now show the desktop visuals
v0.3.0
2026-09-02 09:15:55 -07:00
Matthew Meszaros 8da0235155 feat: let the footer's bottom row wrap on phones instead of forcing GitHub, Discord, RSS and Open source onto one line that pushed the page wider than the screen and made iOS Safari scale the whole layout, and clip horizontal overflow at the html and body level so no single overflowing element can widen the page again 2026-09-02 09:13:28 -07:00
Matthew Meszaros 15d9f22659 feat: make phones show the same product visuals as desktop: every feature window is the 1280px browser with chrome and app shell at every width, scaled and cropped from the same anchor at the same zoom, with the frame keeping a desktop frame's proportions below lg instead of growing to each mock's content height; the app shell drops its chrome prop and becomes a container query root, and the mocks' responsive classes become @sm/@md/@lg/@xl container variants with viewport units pinned to the 1280x900 app viewport, so a mock renders its desktop state inside the window regardless of the visitor's screen instead of collapsing to phone layouts inside a desktop screenshot 2026-09-02 09:08:56 -07:00
Matthew Meszaros 3ecb25786a feat: register the app shell's visibility observer under reduced motion too so an off-screen shell still pauses its animations while only the counter ticker stays off, and read the ?noshell, ?noanim and ?nogl phone diagnostics as query parameter names through URLSearchParams instead of matching the raw query string 2026-09-02 09:00:29 -07:00
Matthew Meszaros b675b82657 feat: remove the remaining iOS Safari GPU hogs from the index page without changing its look: the app shell's three SkyChrome cloud blurs are baked into one SVG background (public/backdrops/shell-ambient.svg, same shapes, blur radii and opacities) so fifteen shells stop running forty-five live 60-70px blur layers, .round-clip clips with clip-path instead of the translateZ + mask-image trick that forced a 3D layer and a mask buffer on all fourteen frames, a shell that scrolls off screen pauses its pings and pulses, and ?noshell, ?noanim and ?nogl query switches let a phone isolate whichever subsystem is left 2026-09-02 08:54:16 -07:00
Matthew Meszaros f3a4d09f0b feat: release the grain frame's shader mount through dispose(), the method ShaderMount actually exposes, so the WebGL context and canvas are really freed when a frame scrolls out of range instead of throwing on a missing disconnect() and leaking every context 2026-09-02 08:46:01 -07:00
Matthew Meszaros e6d615beb2 feat: stop iOS Safari crashing while scrolling the index page by keeping its GPU footprint bounded: every grain frame's WebGL canvas is now released when it scrolls 300px past the viewport and recreated on approach so the page holds two or three GL contexts instead of nine, phones render the grain at a 1280x720 pixel budget instead of the full 3x device ratio, feature sections a viewport or more away drop their app screens from rendering with content-visibility (display none where unsupported) and refit them on the way back so at most a few of the fourteen scaled windows are composited at once, and backdrop filters inside the scaled app shells are turned off on touch devices where the blur is invisible at that size, with the desktop look unchanged 2026-09-02 08:40:22 -07:00
Matthew Meszaros 23d7bfa166 Merge pull request #289 from warmbly/feature/marketing-hero-visual-backdrop
feat: index page visuals 1/1 with the dashboard, hero pricing line, Discord links (#268)
2026-09-02 06:15:05 -07:00
Matthew Meszaros 454c1391c6 feat: pause the feature carousel's reduced-motion timer while the frame is hovered or off screen and resume it with the remaining hold time, so a slide never advances right after release the way the paused fill animation never does 2026-09-02 06:11:45 -07:00
Matthew Meszaros 37d48baf0b feat: address the CodeRabbit review on the index visuals: the app shells and feature screens are aria-hidden pictures whose buttons, inputs and links leave the tab order and the shell's fake Search control is a span, the CRM board counts and sums open deals per stage and hides the open row at zero like the app's pg_crm summary so the Won column no longer reads 42,000 open, the dashboard stat strip totals agree with the at-risk, failing and paused rows beneath it, the story carousel advances on a timer under reduced motion where no fill animation ends, the forms sparkline gradients are keyed by row index instead of name length so two 17-character names no longer share an id, the unibox reply controls reveal on hover at md and up, the hosted form mock uses paragraphs instead of h1/h2 so it stays out of the page outline, and four stylelint findings in global.css are cleared 2026-09-02 06:04:11 -07:00
Matthew Meszaros 1d626496af feat: rebuild the index page product visuals 1/1 with the dashboard for issue #268: every feature section renders the real app shell (AppShellMock with the live sidebar counts) inside a browser window hung from a corner, a shared demo cursor engine (lib/cursor.ts + DemoCursor) walks element anchors instead of guessed coordinates, the campaign builder opens its step editor as the 320px spring side sheet and portals the ⌘J caret assistant over the whole window, analytics, integrations, developers and CRM use a new wide FeatureSection mode that fits the full 1280px window by width so centred dialogs read as centred, forms, form builder and hosted form mocks replace the AI assistant mock, the hero gets a grain backdrop (@paper-design/shaders) and a three-point free to self-host / 10 mailboxes / no card line instead of the long pricing paragraph, the How we differ section is removed, and a Discord glyph linking to dc.warmbly.com is added to the header, mobile menu and footer 2026-09-02 05:53:04 -07:00
Matthew Meszaros f200c8cf0a Merge pull request #287 from warmbly/feature/public-forms
feat: forms v2 with themes, layouts, pages, personalized links, funnel analytics, campaign integration and a custom forms domain
2026-09-01 09:55:24 -07:00
Matthew Meszaros 53b932292b fix: address review findings on forms v2: the list aggregates query passed a Go int into a text concatenation so pgx could not encode it and starts, identified and the trend sparkline were always empty behind a silent degrade; the forms tables sat above campaigns in the org transfer order despite carrying a campaign_id foreign key, which would fail a full import; an imported forms domain inherited the destination workspace's stale verified flag; a form whose creator was offboarded stopped creating contacts because created_by is ON DELETE SET NULL, now falling back to the workspace owner; the forms mirror check never ran when only the web-side copy changed; and the bare metal guide asked for five DNS names while certbot requested a sixth 2026-09-01 09:49:06 -07:00
Matthew Meszaros 290f1f00aa Merge remote-tracking branch 'origin/main' into feature/public-forms
# Conflicts:
#	cmd/backend/main.go
#	docs/content/docs/guides/workspace-export-import.mdx
2026-09-01 09:29:53 -07:00
Matthew Meszaros 6fb5c3ca08 feat: connect forms to campaigns end to end: a toolbar button and {{ menu that insert a per-recipient {{form_link:id}} chip resolved at send time, a Forms panel on the campaign overview reporting links sent, opened, started, submitted and the resulting rate per form, GET /campaigns/:id/forms behind the campaign read gates, and campaign attribution carried through every form submission, funnel event and analytics breakdown 2026-09-01 09:27:33 -07:00
Matthew Meszaros 05d74dbe9f feat: forms v2, a full redesign of the hosted form and its builder: eight one-click themes, card/wide/split layouts with a brand cover panel, classic paged and Typeform-style focus modes split on a new page_break block, logo/cover/background uploads with size, fit and veil controls, an optional header bar that can span the page or sit with the form, a real HSV colour picker and font preview, per-contact personalized links that prefill and attribute without email verification, a render-token gate so the form JSON cannot be scraped without loading the page, funnel analytics with per-page drop-off and identified visitors, a leads-style forms list and responses table, and an organization custom forms domain verified by CNAME with hourly re-checks 2026-09-01 09:27:28 -07:00
Matthew Meszaros 84815381a9 fix: route the orphaned website tracking settings page, which the settings nav linked to but main.tsx never registered, and give every unmapped route a document title so forms, segments, categories, oauth apps and six settings pages stop rendering the literal Page not found in the browser tab 2026-09-01 09:26:40 -07:00
Matthew Meszaros 62e8f26d10 Merge pull request #284 from warmbly/fix/issue-277
feat: live segment-to-campaign linking, lead removal, and import targeting (issue #277)
2026-09-01 08:19:43 -07:00
Matthew Meszaros 271b4b1625 feat: coalesce org segment syncs instead of dropping them, so an import's pinned contacts reach their linked campaigns in the same request: SyncOrgLinkedCampaigns now records a follow-up when a request lands while a pass is running (ImportCommit writes segment membership after its chunked Add already started one, so that pass read the old membership and left the contacts to the 2-minute sweep), the per-org state moves from a sync.Map to a mutex-guarded map so running, queued and released are one atomic transition, and two hermetic tests cover the mid-pass coalesce and the idle re-run 2026-09-01 04:19:59 -07:00
Matthew Meszaros 4385f6ec02 feat: merge main and renumber the segment migrations after PR #283 took 000114 and 000115: campaign_segments becomes 000116 and campaign_lead_removals becomes 000117, since unibox_folders and mailbox_security_mode are already recorded in deployments' schema_migrations and ours are not, restoring a clean 000001-000117 sequence that check-migrations and golang-migrate both accept 2026-09-01 04:06:13 -07:00
Matthew Meszaros 128e314103 Merge remote-tracking branch 'origin/main' into fix/issue-277 2026-09-01 04:03:37 -07:00
Matthew Meszaros 33011468fb feat: rebuild the contacts bulk edit panel: a Will apply strip above the footer lists every queued change as a chip you can take back one at a time so a bulk write is never applied blind, the header names the segment or campaign the selection came from, add and remove pickers sit side by side under one heading behind small tinted glyphs instead of shouting green and red labels, the locally duplicated campaign picker is replaced by the shared CampaignMultiPicker so chips match the rest of the app, custom-field rows put key and value on one line with the type hint inline and mark a half-filled operation as skipped instead of sending it, and Escape now closes an open picker or the confirm before the drawer (with data-floating added to CategoryPicker) 2026-09-01 03:55:10 -07:00
Matthew Meszaros d6ddf1f170 feat: fix implicit-TLS SMTP on 465 and IMAP STARTTLS on 143 behind a stored per-mailbox security mode that accepts any port, stop worker ID churn orphaning mailbox assignments via flock-claimed persistent worker ids, give the unibox a standard mail-folder sidebar (inbox/sent/drafts/archive/spam/trash) backed by a provider-derived folder column, and expose the AI tool registry over REST for non-MCP function-calling agents (#283) 2026-09-01 03:53:19 -07:00
Matthew Meszaros c2b0f93583 feat: close the last silent-success window on the segment pin: the in-transaction segment_members insert now compares RowsAffected against the deduplicated segment list and rolls the create back with a 400 when a segment was deleted between the service's existence check and the write, plus a live repository test that a contact created with a segment is pinned as an include override while a sibling naming none stays out, and that a create naming a segment the organization does not have fails and leaves no contact behind 2026-09-01 03:15:08 -07:00
Matthew Meszaros 103145e970 feat: address the CodeRabbit review on the segment pin and campaign selection bar: the include override for a contact created inside a segment is now written inside the contact repository's own transaction (parsed and deduped alongside campaigns and categories, org-scoped, ON CONFLICT upsert) so a failed override write rolls the contact back instead of answering 200 with a membership that never happened, the service keeps only the pre-write existence check that turns an unknown segment into a 400, and the leads selection bar drops its bulk Delete inside a campaign so the destructive bulk action matches the row action and cannot delete workspace contacts from a Leads tab 2026-09-01 03:07:39 -07:00
Matthew Meszaros 55307b0ad9 feat: fix the segment dialog UI and make a contact created inside a segment join it (issue #285): the add-to-campaign picker now shows a humanised, colour-toned campaign status instead of the raw PAUSED_NO_ACCOUNTS enum, its footer wraps instead of clipping the hint mid-sentence and the Add leads button no longer breaks across two lines, campaign status labels move to a shared components/app/campaigns/status module, the Segments, Categories and Segment pages plus every unmapped settings and onboarding route get a document title so they stop reading Page not found, and POST /contacts takes a segments array that pins the new contacts in as include overrides (validated before the write, best-effort after it) which the New contact dialog sends when opened from a segment page 2026-09-01 02:57:30 -07:00
Matthew Meszaros 7b6830daa5 feat: say segments out loud in the API scope catalog: the READ_CONTACTS and WRITE_CONTACTS descriptions served to the API key dialog (and the docs permission table) now name segments, since segment CRUD, previews, member overrides and enrolment ride on the contact scopes and the picker never said so 2026-09-01 01:32:56 -07:00
Matthew Meszaros 5e87b1bbbf Merge remote-tracking branch 'origin/main' into feature/public-forms 2026-09-01 01:17:54 -07:00
Matthew Meszaros 60c9e316d9 feat: hosted lead-capture forms end to end: drag-and-drop builder with field settings, design panel, embed/share and submissions tabs in the dashboard, a public TanStack form app (forms/) served by the new standalone forms service (cmd/forms + internal/formserver) on FORMS_DOMAIN with per-form frame-ancestors CSP, honeypot/fill-time/Turnstile/per-IP submit protection and a same-origin JSON API proxying the backend internal API, form submissions creating contacts with categories and campaign enrollment plus realtime, audit, webhook and org-transfer coverage, migration 000114, seed forms, CI jobs, Dockerfile, systemd/nginx/compose manifests and docs 2026-09-01 01:17:51 -07:00
Matthew Meszaros 8db032a656 feat: address review on segment-campaign linking: a campaign_lead_removals table (migration 000115, orgtransfer-registered) records hand-removed leads so the automatic segment sync never re-adds them while a manual add or the one-shot enrol clears the record, PUT /campaigns/:id/segments rejects an omitted segment_ids so {} cannot detach everything, rows.Err checks on the four new link queries so a truncated read cannot commit as success, write-path enrolment syncs detached from the request goroutine with a per-org in-flight dedupe, per-campaign 30s deadlines in the sweep instead of one shared budget, and an error-with-retry state in the linked-segments dialog 2026-09-01 00:33:42 -07:00
Matthew Meszaros 66a105fb9f feat: make segments a live campaign audience and round out lead management (issue #277): a campaign_segments link table with GET/PUT /campaigns/:id/segments (max 20, replace semantics), immediate enrolment of linked-segment members as leads plus targeted re-syncs on contact writes, segment edits, pin-ins and imports and a 2-minute backend sweep for drift, waking active campaigns and restarting completed ones through the full launch checks; a Segments manager dialog and toolbar button on the campaign Leads tab; Remove from campaign as the row action and a selection-bar bulk action so leads can leave a campaign without deleting the contact; campaign and segment pickers in the CSV import wizard backed by a new segment_ids import option that pins imported rows as include overrides; a delete guard naming linked campaigns; orgtransfer registration, live repo tests and docs for all of it 2026-08-31 08:45:27 -07:00
Matthew Meszaros ddb0e349ad Merge pull request #281 from warmbly/fix/account-reverification-button-missing
Add the missing mailbox re-verification flow
2026-08-31 06:34:45 -07:00
Matthew Meszaros 773a3ecaf2 Merge remote-tracking branch 'origin/main' into fix/account-reverification-button-missing 2026-08-31 06:26:40 -07:00
Matthew Meszaros c2f5cc4e9e feat: address review on the reconnect flow: a reauth whose repeat consent omits the refresh token now refuses (typed error) when the stored one cannot be read instead of sealing an empty string over it, and credential errors resolve only after the reactivation Update succeeds so a failed reactivation keeps the banner and its reconnect button 2026-08-31 06:26:39 -07:00
Matthew Meszaros acec534b6b feat: give the mailbox drawer's ramp-hold, lifecycle and cold-ramp notice cards top padding (px-5 py-4 like every other Overview section) so they no longer touch the section divider above them 2026-08-31 06:16:15 -07:00
Matthew Meszaros 2d0db3ef63 Merge pull request #282 from warmbly/fix/issue-273
Fix missing line breaks in imported messages: read unibox bodies from the key the worker writes
2026-08-31 06:13:57 -07:00
Matthew Meszaros 70ca90a3e0 feat: fix the reconnect reactivation 500 found in live testing: reconnectAccount now loads the row via GetByID itself because the org-scoped Get selects no user_id and the owner-scoped Update was handed an empty uuid; UpdateSMTPIMAPCredentials switches to GetByID with an explicit tenancy check, and the test stub's Get now mimics the real partial row 2026-08-31 05:53:12 -07:00
Matthew Meszaros 6918ee06b0 Merge remote-tracking branch 'origin/main' into fix/issue-273 2026-08-31 05:49:38 -07:00
Matthew Meszaros 846d502284 Merge pull request #280 from warmbly/fix/cold-email-sending-defaults
feat: make the per-mailbox daily campaign cap configurable up to 5,000/day
2026-08-31 05:43:40 -07:00