Niggles: reply-push markdown + preview deep-link UX + auth-hint tests
Follow-ups from field-testing #2422 (reply push notifications) and #2429 (preview QR deep-link hints). Stacked on #2429.
Status summary#
Implementation complete; all local checks green (typecheck, lint, knip, format; os notifications tests, auth tests, mobile specs).
- Done: markdown-flattened push bodies, AppDrawerButton on signed-out screens, always-shown switch screen with Continue + commit message row, auth login_hint e2e + pure-helper unit tests.
- Remaining: CI + review on PR #2433.
Niggle 1 — push notification shows raw markdown#
**Berlin** renders literally in the push body. iOS notifications have no inline rich text at all — the bold first line is just how iOS styles the title; APNs bodies are always plain — so "respect bold/italics" is impossible. Therefore: strip.
- Add a small
markdownToPlainTexthelper in the notifications domain (checked: annotated-markdown has no text renderer; wrotemarkdown-plain-text.ts): bold/italic/strikethrough markers dropped, inline code/backticks unwrapped,[text](url)→text, heading#s and list markers dropped, code fences unwrapped, images → alt text, collapse blank runs - Apply in
pushBody(apps/os/src/domains/notifications/chat-reply-notify-implementation.ts) BEFORE truncation, so markers don't eat the length budget - Unit tests next to the producer tests (18-case table in markdown-plain-text.test.ts + Berlin-with-link producer case) (chat-reply-notify.test.ts): the Berlin case, links, fences, truncation-after-strip
Niggle 2 — preview deep-link UX#
- Hamburger always reachable. The drawer only exists inside a project; the signed-out sign-in screen has no path to Build info. Add a persistent menu affordance on the signed-out/sign-in screen — when signed out it can carry just Build info (and the current channel is visible there).
- Explore the existing drawer and reuse it (project-drawer.tsx generalized:
AppDrawerButtonrenders the same drawer with only Build info when no project; placed on the sign-in screen and the Projects header)
- Explore the existing drawer and reuse it (project-drawer.tsx generalized:
- Scanning a QR for the channel you're already on must still show the "Switch to" screen (reassurance, not a silent redirect). Today
[channel].tsxauto-forwards whencurrent === channel.- Always render the screen; when already on target, the primary button reads "Continue" and just forwards the hints to the sign-in screen (post-switch relaunch gets the same treatment; spec covers the flow via localStorage-seeded channel override)
- Show commit details:
buildInfostampsmessagealongsidecommit/branch— add the running bundle's commit message (and keepbranch @ sha) to the card
- Auth changes unprotected by tests (rewrite-proofing the login_hint feature):
-
apps/authe2e (oauth-code-exchange.e2e.test.tslane — plain fetch against a deployed/local auth): authorize request withlogin_hint→ assert the signed/loginredirect Location carries it (protects the@better-auth/oauth-providerpatch) - Extract the login page's hint/otp-guess derivation (
hintedEmail,otpGuessfrom search + config) into a pureapps/auth/src/utils/login-hint.tswith node unit tests (utils/login-hint.ts + 8 node tests; login.tsx now renders what it derives) - Note: full-UI coverage (Continue-as button → prefilled OTP) already exists in
specs/mobile/preview-deeplink-hints.spec.ts(preview e2e lane)
-
Assumptions#
- "Respect bold if we can" — we can't (APNs plain-text bodies), so stripping is the whole move; no config knob.
- Target-channel commit details (pre-switch) would need the EAS API from the device; out of scope — the card shows the running bundle's sha + message, which covers the reassurance case after switching.
- Hamburger on signed-in project screens already exists and stays as-is; this only adds reachability from the signed-out flow.
Implementation notes#
(log kept while implementing)
Late addition#
- Remove the floating build-timestamp overlay (bottom of every screen,
_layout.tsx) — redundant now that Build info is reachable from everywhere via the drawer;BUILD_TIMESTAMPexport dropped (build-info screen readsbuildInfo.builtAtdirectly)