Repository navigation
refactor: dedupe provider zone fallback, journey leg steps, and logout write wait - #4871
Conversation
#4827 pulled Microsoft's zone resolution into microsoft-timezone.ts but stopped at the provider boundary, leaving the same IANA-or-UTC fallback copied verbatim in the Google and Apple normalizers and a fourth near-copy (resolveIanaZone) inside microsoft-recurrence.ts. Move the predicate and the UTC fallback to provider-timezone.util.ts and have all four call sites use it. microsoft-timezone.ts now also exports requireIanaTimeZone for the write path, which must throw rather than anchor UNTIL to the wrong wall clock; it replaces resolveIanaZone's `includes("/")` guess with the Intl-backed check, so a bogus "Foo/Bar" raises instead of flowing into dayjs as an unusable zone. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HmwVQGs5UYvpcKv3TCDt5Y
The staging and prod journey specs ran the same account lifecycle against different deployments with the scaffolding copy-pasted: provider parsing, credential lookup, onboarding seeding, signup, login, logout, and delete-account. Last week's seven staging fixes (#4854-#4860) only ever landed on the staging copy, so the prod leg had drifted into three dead selectors: - `button "Settings"` matched the panel title, not the sidebar control, which is labelled "Open settings" (#4857). - The delete dialog is titled "Delete your Compass account?", so `/delete your account/i` never matched (#4860). - The confirmation input is compared with `===` against "Delete my Compass account", so typing it lower case left the submit button disabled and the leg could never finish. Move the shared steps into journey-leg.util.ts and have both legs call them, so the prod leg picks up the fixed selectors and the next one lands once. The prod leg also now opens its draft with the shared keyboard helper and disambiguates the grid card with `.last()`, matching #4854 and #4856. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HmwVQGs5UYvpcKv3TCDt5Y
…loop #4866 gave waitForPendingEventMutations a timeout by recomputing the remaining budget and allocating a fresh setTimeout on every pass, with an extra `remainingMs <= 0` guard to cover the pass where nothing was left. One timer for the whole budget does the same job: the loop re-reads the pending set (a settled batch can enqueue follow-up writes) and races it against that single timer, with the clear in a `finally` so no timer outlives the wait. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HmwVQGs5UYvpcKv3TCDt5Y
resolveSyntheticAccountFromEmail restated isSyntheticAccount's inbox match instead of calling it, so the two could disagree about what counts as a journey address. It now delegates, keeping the header check that is the only thing it adds. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HmwVQGs5UYvpcKv3TCDt5Y
|
Tech-debt sweep over merges from 2026-10-20 to 2026-10-27 (~50 commits). Debt items addressed, one commit each:
Item 2 is the one with teeth. The prod journey leg could not have completed: it looked for Scanned and found clean, having already had a dedupe pass in this window: the hidden-draft / pointer-gate / deck-layout cluster (#4835, #4833), the email dispatch send tail (#4827, #4850), the OAuth scopes-recovery scaffolding (#4866), and the Microsoft recurrence mapping (#4844). Two items noticed and deliberately left out of scope, both older than this window: Merged via the merge queue. On the verify caveat recorded in the PR body: local Generated by Claude Code |
|
Boot bundle: 528.8 KB gzip (+0.0 KB vs main) |
What and why
A debt pass over the last seven days of merges. Four items, one commit each,
no behavior change intended beyond the three dead journey selectors called
out below. Everything is on one branch because this session is scoped to
claude/kind-dirac-2qmerp; the commits are independent and revert cleanlyon their own.
1.
refactor(sync): share the provider iana zone fallback#4827 pulled Microsoft's zone resolution into
microsoft-timezone.tsbutstopped at the provider boundary. The same IANA-or-UTC fallback was still
copied verbatim into the Google and Apple normalizers, and
microsoft-recurrence.tskept a fourth near-copy (resolveIanaZone) thatguessed at IANA-ness with
zone.includes("/"). The predicate and thefallback now live in
provider-timezone.util.ts, and the write path gets anexplicit
requireIanaTimeZone(it must throw rather than anchorUNTILtothe wrong wall clock). The
includes("/")guess is gone, so a bogus"Foo/Bar"raises instead of flowing into dayjs as an unusable zone.2.
refactor(e2e): share journey leg steps so selector fixes reach both legsThe staging and prod journey specs drive the same account lifecycle against
different deployments, with the scaffolding copy-pasted. Last week's seven
staging fixes (#4854-#4860) only landed on the staging copy, so the prod leg
had three selectors that cannot match the app:
button "Settings"matched the panel title, not the sidebar control, whichis
aria-label="Open settings"(SidebarActions.tsx:72, fixed for stagingin fix(journey): open settings via sidebar control name #4857).
"Delete your Compass account?", so the prodleg's
/delete your account/inever matched (fix(journey): target delete-account dialog and confirmation phrase #4860).===againstDELETE_ACCOUNT_PHRASEinDeleteAccountConfirmationDialog.tsx, sothe prod leg's lower-case
"delete my compass account"left the submitbutton
disabledand the leg could never finish.The shared steps moved to
journey-leg.util.ts; both legs call them, so theprod leg picks up the fixed selectors and the next fix lands once. The prod
leg also now opens its draft with the shared keyboard helper and
disambiguates its grid card with
.last()(#4854, #4856), and itscancel-subscription confirm is scoped to that dialog instead of matching any
open
dialogwhile the settings panel is also one. Its checkout-sessionresponse listener now attaches before signup rather than after the trial
dialog appears, so the response cannot land before the listener and leave the
expect.pollwaiting out its full 120s.3.
simplify(web): hoist the logout pending-write timer out of its retry loop#4866 gave
waitForPendingEventMutationsa timeout by recomputing theremaining budget and allocating a fresh
setTimeouton every pass, plus aremainingMs <= 0guard for the pass where nothing was left. One timer forthe whole budget does the same job; the clear moved into a
finallyso notimer outlives the wait.
4.
refactor(backend): route the synthetic email check through one predicateresolveSyntheticAccountFromEmailrestatedisSyntheticAccount's inboxmatch instead of calling it, so the two could drift on what counts as a
journey address. It delegates now, keeping the request-header check that is
the only thing it adds.
Also noticed but left alone, as neither is from this window:
patchedDependenciesstill pins@tanstack/hotkeys@0.3.3while the resolvedtransitive version is
0.16.0, sobun installprintspatch no longer applieson every fresh worktree; and the onboardinglocalStorageseedingis open-coded as raw key strings in ~30 e2e specs.
Verify
Green:
test:sync:fast(899 pass),test:web(738 pass),test:a11y(67 passed),
type-check,lint,knip.Both failures are this sandbox rather than this change, and I would rather
show that than paste a PASS I did not get:
test:backend:fast— 13 failures, all pre-existing. They areGET /api/config500s andSSE Serverevent timeouts, i.e. exactly thesuites AGENTS.md lists as needing
compass.yaml; the config schema wantsMONGO_URI,SUPERTOKENS_URI,SUPERTOKENS_KEYandSYNC_INTERNAL_AUTH_TOKEN, which this sandbox has no secrets for. Checkedrather than assumed: with the one backend file in this PR reverted to
origin/main, the same suite fails the same 13 tests (619 pass / 13 faileither way), and none of the 13 goes anywhere near synthetic accounts.
test:e2e— 1 failure, in a file this diff cannot reach.e2e/billing/explore-before-trial.spec.ts:273spent its 15s waiting on thedraft title input and failed at 16.4s under full-suite load. Re-run on its
own it passes in 3.2s (
5 passed), so this is sandbox load: the only e2efiles here are the two journey specs and their new helper, and both legs
skip unless
*_JOURNEY_ENABLED=1.The journey legs cannot be executed from here (they need staging/prod and
real accounts), so their evidence is
type-checkovere2e/tsconfig.json,bun lint, and a Playwright run confirming both legs still collect and skipunder their own configs (
playwright.staging-journey.config.ts,playwright.prod-journey.config.ts). The three selector corrections wereeach read off the component that renders them, cited above.
Generated by Claude Code