Matthew Meszaros
568bdb48ba
feat: never render an empty IMAP error detail and close the CRM rail when the viewport narrows past lg
2026-09-09 09:24:43 -07:00
SUMAN JANA
73d9c18bfe
fix(imap, unibox): show the server's text for a codeless IMAP error; contact rail starts closed
...
- An IMAP NO/BAD without a response code (Gmail's "NO System Error")
rendered as "Something went wrong: " in the mailbox's error list. Fall
back to the server's text when the code is empty.
- The unibox contact rail opened by itself on lg+ screens, putting the
contact form in front of every thread the reader opened. It now starts
closed at every width and opens from the header toggle.
2026-09-09 15:47:50 +00:00
Matthew Meszaros
035865e5c5
Merge remote-tracking branch 'origin/main' into feat/issue-393-html-css-email
2026-09-09 08:30:52 -07:00
Matthew Meszaros
f5eeac7b2a
feat: address the CodeRabbit review by carrying every stretch of stylesheet the CSS parser cannot read through as a verbatim item, since the sheet is rewritten from parsed items the moment any rule inlines and an unrelated match was deleting the rest, keeping a link's destination out of the content score now that the text renderer emits it so a CTA pointing at a free-trial page stops costing eight points, promising inlining in the editor only for a stylesheet that is actually eligible for it, skipping the client notes entirely for a plain-text campaign that ships no HTML part, switching a step into HTML mode when a template replaces its body with document markup rather than waiting for the next visual edit to gut it, accepting a pasted background shorthand only when it is a single colour so Word's "yellow none repeat scroll" stops becoming an invalid longhand, listing in SCHEMA_TAGS only the tags the mounted schema actually keeps so the warning fires for h1, font, center, thead and caption instead of staying silent while they are dropped, and correcting the guide's byte-for-byte claim and its unconditional plain-text claim
2026-09-09 08:30:49 -07:00
Matthew Meszaros
538dc29575
feat: stop the CSS inliner deleting the half of a selector list it cannot take over, so a rule like ".btn, .other:hover" keeps its declaration for the hover half after the first inlines, leave a rule whose selector list cannot be split on commas exactly as the author wrote it because a comma inside :is() or an attribute value belongs to the selector and rejoining the pieces writes a different one, keep a rule that matches nothing today rather than dropping it, and declare the paste-adoption ref above the editor that closes over it as the other paste-time refs already are
2026-09-09 08:20:23 -07:00
Matthew Meszaros
c91363ca5e
feat: seed the mailhtml fuzz target with the two inputs that found real bugs, an unparsable at-rule and a body whose case fold changes its length, so the seeds guard both fixes on every go test rather than only when the fuzzer is run
2026-09-09 08:04:32 -07:00
Matthew Meszaros
4b6de5e807
feat: stop the send path corrupting a body whose case fold changes its length, found by fuzzing internal/pkg/mailhtml, since lastRealTag lowercased a copy and indexed the original so a Turkish capital I or a Kelvin sign shifted every offset and put the signature and opt-out footer at the wrong byte or past the end of the body, and stop InlineCSS deleting a stylesheet it could not parse by leaving the block byte for byte unless a rule actually moved onto an element, with a final guard returning the original whenever the pass would emit nothing from something
2026-09-09 07:25:28 -07:00
Matthew Meszaros
a5993a1ed0
feat: put the standalone-statement note where the docs said it was, shellcheck the join-script checker as well since its own disable directives are load-bearing, and stop claiming the EnvironmentFile assertion covers two render variants when only the mount list varies with the environment
2026-09-09 07:04:58 -07:00
Matthew Meszaros
9203695a41
feat: harden the join-script guard against being fooled rather than against being reformatted, matching the ensure_blob_root call on its first field after a looser regex proved satisfiable by the name appearing inside a warn string, capturing the function body without a pipeline so a renamed function reports that instead of the assertion it happened to fail, and rendering both unit variants so the no-blob one is covered too
2026-09-09 06:59:14 -07:00
Matthew Meszaros
eb4e1a8bb8
feat: make the join-script checker survive its own mutation tests, matching the ensure_blob_root call on the command field so a commented-out call no longer passes, tolerating a space before the parentheses in a function definition, and capturing the function body before asserting so a renamed function reports that rather than the assertion it happened to fail
2026-09-09 06:47:56 -07:00
Matthew Meszaros
6947aca9b3
feat: make the join-script check assert on what the script renders via a new --print-unit mode, after the previous version compared a heredoc copied into the checker and stayed green when the systemd command-substitution bug was put back, and make docker_mounts pure so the unit can be rendered without creating directories
2026-09-09 06:37:56 -07:00
Matthew Meszaros
2c1b5d8204
Merge remote-tracking branch 'origin/main' into feat/issue-393-html-css-email
2026-09-09 06:35:44 -07:00
Matthew Meszaros
426ea45013
feat: full HTML and CSS support for campaign bodies and mailbox signatures, adding a send-time CSS inliner and a structure-aware plain-text renderer in internal/pkg/mailhtml, persisting HTML mode on a step through the previously unused sequences.body_code so a designed email is no longer gutted by the editor schema on reopen, widening the TipTap schema to hold tables, div containers, colours, fonts, sizes and alignment with toolbar controls for each, folding alignment into a single style producer because TipTap merges two style attributes by splitting on the first colon and truncated background-image url(https://...) to url("https"), replacing the regex plain-text stripper that put a whole stylesheet into the text/plain part and cost a content score eight points for a .free-trial-banner class, locating </body> past Outlook conditional comments so the signature and opt-out footer stop landing inside one, forcing the signature source view for markup a contentEditable cannot host safely, and reporting per-client compatibility findings from the preview endpoint
2026-09-09 06:34:47 -07:00
Matthew Meszaros
29bf9b4320
feat: cover the join script with make join-check, wired into make lint, since nothing tested the highest-consequence non-Go file in the repo and three defects reached the branch through it; and fix the four the review found in the last round, matching the fs provider alias, creating blob parents under a 0022 umask rather than the 0700 one write_config leaves set, warning instead of silently mounting a blob root the node cannot write, and validating the path right after enrolment rather than halfway through the install
2026-09-09 06:28:30 -07:00
Matthew Meszaros
03255754ff
feat: read the blob settings from the env in memory so the dry run reports what a real join would do instead of a previous join's file, create the blob root 0755 rather than inheriting the 0700 umask a co-located backend cannot read, refuse a relative BLOB_FS_ROOT that docker cannot mount, and stop discarding the errors that made a failed mount look like a successful join
2026-09-09 06:12:56 -07:00
Matthew Meszaros
75a068cd07
feat: always create and mount BLOB_FS_ROOT for a node because its storage layer exits when the directory is not writable and an unmounted path is root-owned by docker, and say plainly in the script and the docs that filesystem blobs cannot serve a fleet at all since the worker has to read the body the backend wrote
2026-09-09 06:06:04 -07:00
Matthew Meszaros
4fc6d1db77
feat: put every docker mount on one line in the generated unit, because command substitution strips the trailing newline and my previous multi-line blob mount collapsed the continuation so the node restart-looped on any filesystem-blob install, and mount BLOB_FS_ROOT only when it already exists rather than creating and recursively chowning a directory a co-located bare-metal backend owns
2026-09-09 05:59:44 -07:00
Matthew Meszaros
0a08983158
feat: keep the node's write path reachable and the image reference root-owned, by granting traverse on the state directory without making it writable, re-owning image-ref on every join so a machine joined by the earlier chown-everything script converges, mounting BLOB_FS_ROOT separately when blobs live under the state directory the mount no longer covers, and renaming the reserve dialog's remaining convert-to-dedicated button
2026-09-09 05:53:28 -07:00
Matthew Meszaros
92470e1528
feat: drop the racy liveness re-read from the placement live test, which re-queried whichever worker placement chose and so raced with the email package's live tests creating and deleting their own worker in a parallel package; that placement only ever picks a live worker is already covered by the unit tests, which need no database
2026-09-09 05:44:25 -07:00
Matthew Meszaros
65fe82c472
feat: stop the node container being able to choose the image root runs, by giving it a dedicated uid-1000 subdirectory to write its update target into instead of making the whole state directory writable, validating the tag it asks for before the updater acts on it, and no longer recursively chowning a directory a bare-metal install also keeps its blobs in
2026-09-09 05:41:16 -07:00
Matthew Meszaros
bfdbd77b04
feat: fix the eight defects the second review pass found, including three where the previous fix did not land: the bind-mounted state dir was root-owned so the node running as uid 1000 still could not write its update target, elevating the reserved-worker eviction did nothing because the rotation loop bailed on target-equals-current before the urgency was consulted, and the warmup-pool assertion was vacuous which hid that warmupPoolFor checked the subscription repo before the billing provider and answered free on a self-host install
2026-09-09 05:32:30 -07:00
Matthew Meszaros
8b83062eff
feat: fix ten defects the review found, the worst being that every seeder still wrote the worker columns migration 000141 moved to fleet_nodes so make dev could not seed, that the generated systemd unit used a command substitution systemd never expands so a joined machine restart-looped while the script reported success, that the state directory was not bind-mounted so the node wrote its update target inside the container where the host timer never saw it, and that placement stopped assigning warmup pool membership so paid mailboxes silently warmed in the free pool
2026-09-09 05:21:17 -07:00
Matthew Meszaros
704fecdcc0
feat: settle on WARMBLY_NODE_REGION as the one region variable for every role, because the join script wrote it while the worker still read WORKER_REGION, so a joined worker reported no region and the placement geography term stayed dead for exactly the machines that had been given one
2026-09-09 05:04:55 -07:00
Matthew Meszaros
de60bc3784
feat: let an isolated-egress reservation move a mailbox without clearing the score gain, because the reserved worker scores lower than the incumbent by construction (the incumbent holds the stickiness bonus) so the rotation loop refused the move on every tick and the organization never converged onto the worker it pays for
2026-09-09 05:02:53 -07:00
Matthew Meszaros
88992c396c
feat: hand the node its env base64 encoded at join instead of as a raw JSON string, because the POSIX-sh join script has no JSON parser and its sed extraction appended a stray ","heartbeat_seconds line to node.env, and refuse a heartbeat that re-registers an existing node id under the other role, which would have left a worker's mailboxes assigned to a machine that had stopped doing worker work
2026-09-09 05:01:13 -07:00
Matthew Meszaros
1a7fd85f4b
feat: drop the throwaway migration harness that a blanket git add swept into the placement commit, since it only existed to apply migrations against a scratch database while verifying the branch
2026-09-09 04:56:07 -07:00
Matthew Meszaros
58b7d81d33
Merge remote-tracking branch 'origin/main' into refactor/automatic-worker-mailbox-placement
2026-09-09 04:54:04 -07:00
Matthew Meszaros
435dbb522f
feat: replace the worker tier/type/risk-pool/egress categories with a scored placement model and make the fleet pull-based, so a machine joins with one command, workers and consumers share one node registry with usage and liveness, nodes self-update to the version the control plane resolves, and the Hetzner provisioning, worker profiles and SSH orchestrator are removed
2026-09-09 04:54:01 -07:00
Matthew Meszaros
db2a9ba439
feat: stamp the rotating campaign send's real sending mailbox onto tasks.email_account_id before dispatch so budgets, round-robin position, deliverability rates and the contact feed name the mailbox that actually sent, and stop the contact email list and campaign-state steps reporting automated prefetches as recipient opens (issue #392 )
2026-09-09 03:55:28 -07:00
Matthew Meszaros
e783c3e3a0
feat: merge main into the email editor branch
2026-09-08 21:27:28 -07:00
Matthew Meszaros
7ad49c649a
Merge branch 'main' into fix/thread-on-in-reply-to
2026-09-08 21:22:31 -07:00
Matthew Meszaros
1e1bb3622e
feat: address the CodeRabbit review on the email editor PR by generating email image object keys from a uuid and the sniffed extension instead of the uploader's filename so a name carrying '..' cannot produce a key the public route refuses to serve and a private filename never travels inside a recipient's URL, deleting the stored object before its row and refusing the request when storage will not take it so a failed delete leaves nothing loadable that the quota has stopped counting, accepting only https image addresses, resynchronising the HTML source view when a template replaces the body underneath it, and rendering the campaign preview through the inbox's sandboxed frame so markup written in the source view can neither run nor restyle the dashboard for a teammate
2026-09-08 21:17:36 -07:00
Matthew Meszaros
4f40ab7ec8
feat: merge main into the in-reply-to threading branch
2026-09-08 21:06:29 -07:00
Matthew Meszaros
95a4986d9e
feat: merge main into the verification reopen branch
2026-09-08 21:05:37 -07:00
Matthew Meszaros
e47937432f
feat: address the CodeRabbit review on the verification reopen PR by restricting the connection-triggered recheck to built-in verdicts so a verdict a paid verifier already produced is not re-spent when another connection is made, refusing to restore a connection the member disconnected while the credit round trip was in flight, and registering each live fixture's cleanup as it is written so a failed assertion cannot strand an integration row that then breaks the shared org fixture's teardown
2026-09-08 21:05:37 -07:00
Matthew Meszaros
9ce1e060dd
feat: fix the campaign step body editor for issue #380 by registering TipTap's UndoRedo and HardBreak so Ctrl+Z and Shift+Enter work at all, normalising pasted HTML from Gmail/Outlook/Word so their blank-line scaffolding stops rendering a second gap on top of our paragraph margins, adding a workspace email image library (migration 000139, public email-images/ objects sharing the attachment storage quota, keyset-paginated /email-images endpoints) with toolbar upload, URL and library insertion, drag-drop, clipboard paste and a size/alignment/alt bubble, and adding an HTML source view that names the tags the schema would drop before switching back
2026-09-08 21:00:09 -07:00
Matthew Meszaros
0e7525866a
feat: merge main into the select-all branch and rebuild the contact selection on top of its new keyset pagination, dropping this branch's own cursor repair now that Search carries the sort key and boundary value in a SortCursor, re-extracting the shared WHERE builder from that version, and pointing SearchIDs at the contactSorts spec and the conditional campaign-count lateral so a resolved selection orders and joins exactly the way the list does
2026-09-08 20:26:34 -07:00
Matthew Meszaros
5a1a726fdc
Merge branch 'main' into fix/reopen-verification-on-connect
2026-09-08 20:10:31 -07:00
Matthew Meszaros
29b479321a
feat: merge main and renumber the campaign entry-delay migration to 000138, since main released 000137_contacts_org_created_index while this branch was waiting to merge
2026-09-08 09:59:43 -07:00
Matthew Meszaros
c8042e2942
Merge remote-tracking branch 'origin/main' into feat/campaign-entry-delay
2026-09-08 09:58:38 -07:00
joao-crm
83ba4b38b4
fix: re-check the addresses the built-in probe left unknown as soon as a workspace connects a paid verifier, and withdraw the connection's degraded state once that verifier answers again, since a verdict reached before the connection existed otherwise waited out the 30-day shelf life while the credits sat unused and the card kept reporting whatever the provider said on the very first pass
2026-09-08 15:48:12 +00:00
Matthew Meszaros
a62652de3a
feat: merge main and renumber the contacts index migration to 000137, since main released its own 000136 for lead sync segments while this branch was in review and two branches that each take the next number are green alone but leave golang-migrate refusing to build its source driver once both land
2026-09-08 08:34:52 -07:00
Matthew Meszaros
37ed967919
Merge remote-tracking branch 'origin/main' into fix/issue-382-user-experience
2026-09-08 08:32:31 -07:00
Matthew Meszaros
073bb3c6bc
feat: merge main and renumber the campaign entry-delay migration to 000137, since main released 000136_lead_sync_segments while this branch was open and a duplicate version makes golang-migrate refuse to build its source driver, which is what failed the Migrations job and the check-migrations prerequisite inside make lint on Go CI
2026-09-08 07:33:21 -07:00
Matthew Meszaros
4e52e9476b
Merge remote-tracking branch 'origin/main' into feat/campaign-entry-delay
2026-09-08 07:31:31 -07:00
Matthew Meszaros
f8c0899be4
feat: address the CodeRabbit review by promoting a rounded wait into the next unit so a contact drawer never reads 60 minutes or 24 hours, describing the entry delay's anchor as the lead's own enrolment moment with the campaign's creation date only as the legacy fallback in both the campaigns and segments guides, replacing the launch dialog's 'sending begins immediately' line when a delay is set, following an outside value change with the picker's preset or custom mode, and chaining the trigger card's saves so two commits inside one round trip cannot land out of order
2026-09-08 07:30:24 -07:00
Matthew Meszaros
0f83329936
feat: answer the CodeRabbit re-review by guaranteeing the promise the failed-pin warning makes: an import-level note now goes to the front of the error list and is never dropped by the per-row cap, so a file full of bad addresses cannot bury or evict the one note explaining why the rows that did import are not in the segment they were imported into, with a single note however many segments were targeted and a live test that forces the write to fail behind a capped error list
2026-09-08 06:54:36 -07:00
Matthew Meszaros
78dd032a06
feat: address the CodeRabbit review on the contacts pagination PR by refusing a cursor boundary the SQL cast would choke on so a hand-made token is a 400 instead of a database error surfacing as a 500, writing timestamp boundaries with an explicit to_char pattern the Go validator mirrors so a token never depends on the server DateStyle, basing the full-screen error guard on rows loaded rather than rows left after the client-side subscription filter, keeping the failure visible when that filter hides every loaded row, and pointing Try again at the request that actually failed by reading isFetchNextPageError instead of guessing from hasNextPage
2026-09-08 06:53:57 -07:00
Matthew Meszaros
687aa6ec5e
feat: page through the contact list under a text query, the min and max campaign filters and a segment scope in the pagination regression test, since each appends bound parameters either side of the cursor's or compiles a SQL fragment of its own and a shifted placeholder would otherwise only show up in production
2026-09-08 06:00:16 -07:00
Matthew Meszaros
f46728733e
feat: address the CodeRabbit review on the select-all PR by tying the header checkbox to the rows on screen and making it put unticked rows back before it clears a select-all, bounding the exclusion list before it sizes a map, skipping half-filled custom-field pills when a filter is saved as a segment so the segment matches the search, and naming the fifty-thousand selection cap on the campaigns and segments guides
2026-09-08 05:39:16 -07:00