While building and live-testing the account & privacy page (#503 PR 1), we found and diagnosed several pre-existing auth-flow and dev-tooling issues. None are caused by or fixed in that PR. Planned as two follow-up PRs off main: (a) auth-flow fixes, (b) dev tooling.
The state_mismatch mystery — resolved, no code bug
Sign-ins were intermittently dying on a state_mismatch error page. A controlled clean run (one backend, one incognito window, one attempt) completed with zero errors — the OAuth state machinery is correct. The failures were multi-attempt artifacts: clicking sign-in twice overwrites the single better-auth.state cookie, and replaying/refreshing an old callback tab consumes-then-misses the verification row. Fix: prevention, not state-machine surgery (see Cancel/reset below). We will not ship skipStateCookieCheck or storeStateStrategy: 'cookie' — those were diagnostic-only.
Device-flow UX bugs
- No way to cancel a sign-in in progress.
useDeviceAuth.reset() exists but isn't exposed through AppAuthSession, so the pane offers no Cancel while polling — and abandoned attempts are exactly what caused the state_mismatch mess. One-line enabler + a button.
- 4-minute device code is too tight (
backend/src/auth.ts expiresIn: '4m'). A Google 2FA detour (observed live: an org-policy rejection then a second account) easily outlives the code, and the user only finds out at Approve time. Fix: longer expiry and/or a countdown in the pane.
- The approval page shows raw JSON on failure —
{"error":"invalid_request","error_description":"Invalid user code"} instead of "This code expired — request a new one in Word."
Callback / error-page bugs
- No
errorCallbackURL on the approval-page sign-in, so OAuth failures land on Better Auth's default error page whose "Go Home" points at the backend root → 404 dead end.
requestDeviceCode / pollForToken parse the body before checking res.ok — an empty 502/proxy body throws "Unexpected end of JSON input" and masks the real failure ("backend not running").
Dev-experience gaps (found during PR 1 live testing)
- Backend errors are invisible in dev.
app.onError ships exceptions to PostHog only; nothing reaches the console. A dev-mode console.error would have surfaced PR 1's FIELD_NOT_ALLOWED bug in seconds instead of requiring a temporary patch.
- Deletion freshness window: delete-account's re-auth gate only triggers past Better Auth's default
freshAge (~1 day), so most real deletions never re-prompt. Decide whether deletion should always demand a fresh sign-in (tighten freshAge or gate deletion explicitly).
- (minor) Repeated editor loads in demo mode mint a new anonymous user each time — cosmetic in dev DBs, but worth a look.
Dev script issues (→ tooling PR)
- Frontend npm scripts still point at
manifest.xml, which moved to public/manifest.xml in fbcfae3 — sideload scripts are broken; workaround is npx office-addin-debugging start public/manifest.xml.
- The DB migration command isn't committed anywhere — should be a proper npm script (
npx tsx src/migrate.ts).
🤖 Generated with Claude Code
While building and live-testing the account & privacy page (#503 PR 1), we found and diagnosed several pre-existing auth-flow and dev-tooling issues. None are caused by or fixed in that PR. Planned as two follow-up PRs off
main: (a) auth-flow fixes, (b) dev tooling.The
state_mismatchmystery — resolved, no code bugSign-ins were intermittently dying on a
state_mismatcherror page. A controlled clean run (one backend, one incognito window, one attempt) completed with zero errors — the OAuth state machinery is correct. The failures were multi-attempt artifacts: clicking sign-in twice overwrites the singlebetter-auth.statecookie, and replaying/refreshing an old callback tab consumes-then-misses the verification row. Fix: prevention, not state-machine surgery (see Cancel/reset below). We will not shipskipStateCookieCheckorstoreStateStrategy: 'cookie'— those were diagnostic-only.Device-flow UX bugs
useDeviceAuth.reset()exists but isn't exposed throughAppAuthSession, so the pane offers no Cancel while polling — and abandoned attempts are exactly what caused the state_mismatch mess. One-line enabler + a button.backend/src/auth.tsexpiresIn: '4m'). A Google 2FA detour (observed live: an org-policy rejection then a second account) easily outlives the code, and the user only finds out at Approve time. Fix: longer expiry and/or a countdown in the pane.{"error":"invalid_request","error_description":"Invalid user code"}instead of "This code expired — request a new one in Word."Callback / error-page bugs
errorCallbackURLon the approval-page sign-in, so OAuth failures land on Better Auth's default error page whose "Go Home" points at the backend root → 404 dead end.requestDeviceCode/pollForTokenparse the body before checkingres.ok— an empty 502/proxy body throws "Unexpected end of JSON input" and masks the real failure ("backend not running").Dev-experience gaps (found during PR 1 live testing)
app.onErrorships exceptions to PostHog only; nothing reaches the console. A dev-modeconsole.errorwould have surfaced PR 1'sFIELD_NOT_ALLOWEDbug in seconds instead of requiring a temporary patch.freshAge(~1 day), so most real deletions never re-prompt. Decide whether deletion should always demand a fresh sign-in (tightenfreshAgeor gate deletion explicitly).Dev script issues (→ tooling PR)
manifest.xml, which moved topublic/manifest.xmlinfbcfae3— sideload scripts are broken; workaround isnpx office-addin-debugging start public/manifest.xml.npx tsx src/migrate.ts).🤖 Generated with Claude Code