Repository navigation
Discover and launch relevant household outcomes - #441
Merged
NewWorldVenture merged 0 commit intoSep 10, 2026
Merged
NewWorldVenture merged 0 commit into
NewWorldVenture merged 0 commit into
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
NewWorldVenture
marked this pull request as ready for review
September 9, 2026 21:54
NewWorldVenture
force-pushed
the
claude/roadmap-implementation-ld8bon
branch
3 times, most recently
from
September 10, 2026 16:31
11aa925 to
677e44c
Compare
NewWorldVenture
merged commit Sep 10, 2026
6101554
into
claude/roadmap-implementation-ld8bon
7 checks passed
NewWorldVenture
force-pushed
the
claude/roadmap-implementation-ld8bon
branch
from
September 10, 2026 20:53
56d8dd0 to
404d5d5
Compare
NewWorldVenture
pushed a commit
that referenced
this pull request
Sep 27, 2026
…refused Page audit B11 signed in a child through /kid-login and pressed every control and submitted every form that session can reach (268 routes, 1,050 clicks, 106 submissions, 10 direct writes with its own token). P-24: a refused save read "Minified React error #441" (licences) or fell to "This page hit a snag" (media library, social connect). Production keeps only a thrown action's digest, so useActionError now reads the refusal from it (refusalForThrown) and never shows React's redaction text; five action files and SocialAccessError carry the refusal digest. P-25: relationship and weekly briefing answer a provider failure with 503, like their sibling AI routes, not 500. Eighteen more routes are recorded as open. P-26: the watchlist and family poll decided insert/update from votes that arrive by realtime, so a second tap inserted again and got a 409. The watchlist upserts on (title, member); a single-choice poll clears by poll and member and accepts its own duplicate; both re-read after each write. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TRY21ZKsFrfB3qtoP972A4
This was referenced Sep 27, 2026
NewWorldVenture
pushed a commit
that referenced
this pull request
Oct 1, 2026
…nk paste (A11Y-001) Two of the five defects #778 reproduced, fixed in the composer only (components/modules/paperwork-module.tsx): - A refused paste showed a thrown action's message as-is, which a production build replaces with React's redacted "Minified React error #441 ..." text. The catch now reads it with refusalForThrown and shows the actionRefusal.* message, as useActionError already does. A real thrown refusal (a temporary local check constraint) now reads "Some of what was entered can't be accepted. Check the form and try again." - A paste of only spaces passed native `required`, the action trimmed it to nothing and answered ok without saving, and the composer closed with nothing said. The form now clears a blank paste and reports it the way the browser reports an empty one (valueMissing, focus on the field), so no action request is sent. The action and its error calls are unchanged. Both regressions in tests/e2e/form-error-semantics.spec.ts drop their test.fail() marker and assert the fixed behaviour. The other three (shared Button focus, shared toast obstruction, composer focus) stay marked and unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TRY21ZKsFrfB3qtoP972A4
NewWorldVenture
added a commit
that referenced
this pull request
Oct 4, 2026
…; contact focus and paperwork composer refusal, blank paste, focus and draft fixed (#778) * E2E: what the contact and paperwork forms expose when a submit fails (A11Y-001) Test and evidence only (JIMMY-BLK-FORM-A11Y-20261001); no source change. Real /dashboard/contacts and /dashboard/paperwork pages, a signed-in Plus parent of a disposable local household, synthetic records, 390 and 1280 px. Controls that pass on main: the contact form's validation alert is said once and stays in Chromium's accessibility tree inside the aria-modal dialog, focus stays on the field, a malformed email is the browser's own constraint validation, a database refusal is one alert in the reader's words with the dialog and values kept, and a save announces, closes, returns focus to the opener and reads back. An empty paperwork paste is the browser's required-field refusal; a real paste is saved. Reproduced defects, marked test.fail() so they run every time and flip when fixed: - contacts: a refused save drops focus to <body>, outside the dialog (the shared Button is natively disabled while loading); - contacts, 1280x800: the stacked error toasts cover the dialog's own submit button (the toast-obstruction class recorded 2026-09-27); - paperwork: a refused paste shows React's redacted #441 text, not the not-saved message useActionError already gives; - paperwork: focus drops to <body> after a refused and after a saved paste; - paperwork: a whitespace-only paste closes as done with nothing saved. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TRY21ZKsFrfB3qtoP972A4 * E2E: schedule the social post a week ahead, not on a fixed date social-publish-consumers.spec.ts:269 filled the schedule field with '2026-10-01T12:00'. From 12:00 UTC on 2026-10-01 that is a past time, which the studio rightly refuses, so the test fails on every branch, main included (01b2c38 passed at 10:35 UTC; the same commit fails after 12:00, 1 failed / 11 passed locally, pinned Chromium 149). The time is now computed in the page as a week ahead, in the browser's own local time, which is what a datetime-local field takes. No assertion changed. Pinned Chromium, retries 0: 36/36 (x3). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TRY21ZKsFrfB3qtoP972A4 (cherry picked from commit 046bc33) * Paperwork composer: say a refusal in the reader's words; refuse a blank paste (A11Y-001) Two of the five defects #778 reproduced, fixed in the composer only (components/modules/paperwork-module.tsx): - A refused paste showed a thrown action's message as-is, which a production build replaces with React's redacted "Minified React error #441 ..." text. The catch now reads it with refusalForThrown and shows the actionRefusal.* message, as useActionError already does. A real thrown refusal (a temporary local check constraint) now reads "Some of what was entered can't be accepted. Check the form and try again." - A paste of only spaces passed native `required`, the action trimmed it to nothing and answered ok without saving, and the composer closed with nothing said. The form now clears a blank paste and reports it the way the browser reports an empty one (valueMissing, focus on the field), so no action request is sent. The action and its error calls are unchanged. Both regressions in tests/e2e/form-error-semantics.spec.ts drop their test.fail() marker and assert the fixed behaviour. The other three (shared Button focus, shared toast obstruction, composer focus) stay marked and unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TRY21ZKsFrfB3qtoP972A4 * Paperwork composer: keep keyboard focus through a refused or saved paste (A11Y-001) Fix #4 from #778, in components/modules/paperwork-module.tsx only. - The pending submit was natively `disabled`, so the focus that pressed it fell to <body> while the action ran and stayed there after a refusal. It is now aria-disabled while pending (styled with aria-disabled:), and a ref refuses a second submit while one is in flight. - A save closed the composer around the focused submit, dropping focus to <body>. Focus now moves to the "Add paperwork" toggle that opened it. The #4 case in tests/e2e/form-error-semantics.spec.ts drops test.fail() and asserts focus on the submit after a refusal and on the toggle after a save. #1 (shared Button) and #2 (shared toast) stay marked. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TRY21ZKsFrfB3qtoP972A4 * Paperwork composer: only hand focus back while the saving composer holds it Review 5941818072 on #778 (P2): fix #4 focused the "Add paperwork" toggle on every successful save. A save answered after the reader had closed the composer and moved to another control pulled focus back to the toggle, interrupting them. The composer now reports whether it still owns the focus when its save lands: it is still mounted, and focus is inside it or has fallen to <body> with its submit. Only then does the module focus the toggle; an ordinary save still lands on the toggle as before. New regression (390 and 1280 px): hold the save, close the composer with its toggle, focus the Archived filter, release the save; focus stays on the filter. It fails on 361a971 at both widths and passes now. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TRY21ZKsFrfB3qtoP972A4 * Paperwork composer: keep a refused paste; a late save closes only its own composer Two remaining composer defects on #778, in components/modules/paperwork- module.tsx only. - A paste the server refused was wiped. The composer submitted through <form action>, and React resets a form's fields when its action settles, refused or not. It now submits from onSubmit (FormData plus the same transition), so nothing resets the fields; on success the composer closes as before. Reproduced without a stub: a paste over the 1 MB server-action body limit is refused by the framework, and the 1.2 MB letter and its sender were both cleared. - A save from a composer that had been closed (and reopened) closed the new one. A save now closes only the composer that sent it. Two regressions at 390 and 1280 px: an oversize paste keeps its text and sender with the not-saved alert; a held save released after close and reopen leaves the reopened composer and its draft. Both fail on 65b6cf2 and pass now. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TRY21ZKsFrfB3qtoP972A4 * E2E: say why the Plus-plan setup failed; retry it once only when nothing answered CI run 36942913853 on 915eb15 passed with one flaky case: the beforeEach upsert that gives the disposable household a Plus plan returned an error once (00:03:35Z, 390 px contacts) and passed on retry. The step threw away the response, so the log cannot say what it hit. No other spec writes subscriptions or reloads the schema, and the database side is an ordinary update (provision already made the row; no subscriptions trigger refuses a service-role write). The step now reads the row back and reports the HTTP status and error code (never the message or details). It retries once, and only when nothing answered (status 0) or the gateway did (5xx): the upsert is idempotent, and postgrest-js retries a GET on those but never a POST. A database refusal is thrown at once. Local controls, reverted before commit: a dropped first request retries and passes ([0]); a missing household is thrown once as (409 23503); a wrong plan read back is thrown once. Spec 19/19, then 38/38 at --repeat-each=2, 2 workers. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TRY21ZKsFrfB3qtoP972A4 * Contact form: a refused save keeps keyboard focus on its submit (A11Y-001 #1) FLEET-NEXT-BLACKSTONE-20261002-1340 on #778. In components/modules/ contacts-module.tsx only; the shared Button is unchanged. The contact dialog's submit used the shared Button's `loading`, which sets `disabled`. Chromium moves focus off a focused control that becomes disabled, so pressing Enter on "Add Contact" left focus on <body>, outside the dialog, while the write ran and after it was refused. The submit is now aria-disabled while saving, with its own spinner and the same disabled look, and a ref refuses a second submit while one is in flight (a repeated Enter or click sends nothing). Tests (tests/e2e/form-error-semantics.spec.ts): - #1 drops test.fail(): with the write held, the pressed submit keeps the focus, and still has it after the refusal. It fails on 7c2bb3e at both widths (focus "inactive") and passes now. - New control: a second Enter and a DOM click during a held save send one write; the contact is added once and focus returns to the opener. - The paperwork refusal case now first asserts its refused request was sent, so a failure says which side failed (one unreproduced miss locally; 20/20 on repeat). Full spec 21/21 x3 (20 ordinary passes; #2, the shared toast, still an expected failure) and the changed cases 30/30 at --repeat-each=5. tsc, eslint and git diff --check clean; Vitest 24,137 passed + 2 expected failures, after rewording one comment that the aria-modal ratchet reads. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TRY21ZKsFrfB3qtoP972A4 * Paperwork spec: start each case once the page's own server actions settle CI on bd4b711 (run 37025780260) passed with one flaky case, "paperwork: keyboard focus is not dropped ... after a refused or a saved paste" at 1280 px: the composer was still open 5 s after a real save. Cause, from request logs on a reproduction (4 workers, 4-6 of 40 failed): the save request was not sent until another server action on the page had answered. That one is the dashboard sidebar's mount read, loadSidebarPrefs, and on a loaded runner it took 2-4 s. Next runs one server action at a time, so the save waited behind it and then ran past the assertion's 5 s. The composer itself behaved correctly: its in-flight guard had cleared and the save, once sent, closed it. Spec only; no product code and no timeout changes: - openComposer tracks in-flight server actions from before sign-in. Once the composer has opened (so the page has hydrated and run its mount effects), it waits for them to settle. - The refusal stub and the action counter now match only the composer's save, by its `_1_text` form field. They used to match every action posted from the page, including the sidebar read. Same reproduction: 40/40. A/B at 3 workers, focus and plain-save cases x15: 1 of 60 failed before, 0 of 60 after. Full spec 21/21 twice. tsc, eslint and git diff --check clean. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TRY21ZKsFrfB3qtoP972A4 * Mobile audit: accept node-forge GHSA-86w9-cpqp-85rv by id until it is fixed The mobile job's `npm audit --omit=dev --audit-level=high` has been red on main and every PR since a high advisory was published for node-forge (<=1.4.0, RSA PKCS#1 v1.5 signature check). No patched release exists, and the package arrives only through expo -> @expo/cli and @expo/code-signing-certificates. npm cannot accept a single advisory, so the gate either blocked everything or would have had to be dropped. - scripts/audit-npm-advisories.mjs runs the same `npm audit` with --json. It fails on every advisory at or above the level except one listed by GHSA id for its own package. Packages that are high only through a dependency are not counted twice. It fails closed when npm returns an error or no report. It prints each accepted advisory on every run, and flags an entry npm no longer reports so it gets removed. - mobile/npm-audit-allowlist.json holds the one entry, with its package, date and reason. - The mobile CI step calls the script with the same flags. The web audit (`moderate`) is unchanged. A new advisory, including a second one on node-forge, still fails. Checked against mobile/'s real lockfile: passes with the allowlist, and fails on GHSA-86w9-cpqp-85rv with an empty one. New unit tests cover the filter; the MAIN-F-C10 pin now matches the new command and checks the allowlist's shape. The 52 test files that read ci.yml, scripts/ or mobile/ pass (591 tests). tsc, eslint and git diff --check are clean. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TRY21ZKsFrfB3qtoP972A4 (cherry picked from commit 4442ebc) * Contacts spec: the held write's wait ends and names why it was not sent CI on d671e2d passed with one flaky case, "contacts: a second Enter while a save is in flight sends one write" at 390 px. It hit the 30 s test timeout on `await seen`, which waits for the held contact write with no limit of its own. Cause, reproduced locally (2-3 of 40 at 4 workers) and then shown on purpose with a temporary local-only build hook (not committed): under load the shared session boundary briefly reported the session "unavailable". The page was replaced with its "Your session is temporarily unavailable" notice and everything under it reset. The open dialog and its typed name were gone before Enter, so no write was ever sent. When the session returned to ready, the dialog did not come back. This is the session cache's behaviour (lib/auth/cache-session.ts and lib/offline/cache-scope.tsx), not the contact form's. This PR does not change it. Spec only: both contact cases that hold a write now wait for it at most 10 s. If it never comes, they fail saying so, and say whether the session boundary replaced the page. A control that skips the Enter fails at 10 s with that message. Full spec 21/21 twice; both contact cases 40/40 at 4 workers. tsc, eslint and git diff --check are clean. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TRY21ZKsFrfB3qtoP972A4 * Toasts sit bottom-right and at most 14rem wide on desktop (A11Y-001 #2) Bottom-centre at every width, a 384px toast covered a centred dialog's own footer the moment a refusal was shown: at 1024 and 1280 px a click at the contact dialog's "Add Contact" landed on the toast's Dismiss. From lg the stack now sits bottom-right (lg:inset-x-auto lg:right-4 lg:items-end lg:px-0) and each toast is at most 14rem (lg:max-w-56); below lg nothing changes. A centred 512px dialog leaves 256px either side at 1024 px, so the narrower width is what clears it; longer messages wrap. While a toast shows it covers Quick capture and the AI button in that corner instead. Placement only: #788's localization lines in toast.tsx are untouched, and so are message timing, format and handlers. Spec: the expected-failure case becomes ordinary cases at 1024x768, 1280x800 and 1440x900 (no dialog control under a toast; the submit is the hit target; toasts at most 224px, right-aligned), a long German notice, the quick-capture Undo toast (Undo and Dismiss are its own hit targets; it holds while hovered or focused past its 7s lifetime; Undo by keyboard), and 390 px keeping its mobile place. Before the fix the desktop cases fail at those assertions; after it they pass. Full form spec 26/26 twice; the toast cases 30/30 at --repeat-each=5 with 4 workers; the six other specs that load the real toast 192/192; the 64 unit test files that read toast.tsx (1329 tests); tsc and eslint clean. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TRY21ZKsFrfB3qtoP972A4 * Characterize the toast stack over the corner buttons (A11Y-001 #2) The spec now records, rather than describes, the trade-off disclosed with the bottom-right placement: at 1024x768, 1280x800, 1440x900 and 390x844, with one short notice, one long one (German, errors.thatChangeWasNotSaved) and three at once on /dashboard/contacts with no dialog open, it reads every control a toast's box intersects, what a pointer at the centre of Quick capture and of the AI orb lands on, and that both are still reached (focus, Tab, Shift+Tab) and opened (Enter) from the keyboard while the notices show. Today, from lg: one notice covers Quick capture (a pointer there hits the toast), a long one or three cover the AI orb too. At 390 the full-width stack ends level with Quick capture's foot, so a tap there lands on the notice's own Dismiss; a long one or three reach the orb and the language bar. The keyboard path holds at every size. The pointer rows are an explicit table (CORNER) that a placement fix has to change. Also recorded, not the toast's: at 1024 the assistant page the orb opens leaves its composer 0px wide (components/assistant/workspace.tsx, lg:grid-cols-[320px_1fr_330px] in a 688px main). The new cases block the service worker: 3 of the first ~60 local runs saved a write page.route should have refused. Spec only; no product change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TRY21ZKsFrfB3qtoP972A4 * Lift the toast stack above the corner controls from lg (A11Y-001 #2) From lg the toast stack sat bottom-right at bottom 1.5rem, over Quick capture (lg:bottom-6) and, with a long notice or three, the AI orb (lg:bottom-24, h-14: its top 9.5rem up). A pointer at either button's centre hit the notice instead. The stack now sits at lg:bottom-40 (10rem), clear of both. Everything else is as it was: the mobile offset bottom-[calc(5rem+var(--safe-bottom))], the 14rem toast width, the timers, hover/focus pause, Dismiss and Undo. Cleared by the owner on #778 (comment 5969520828, on the measurements in 5969464576). Measured on local production builds, /dashboard/contacts, no dialog open (CORNER table in tests/e2e/form-error-semantics.spec.ts): - before, 1024x768 / 1280x800 / 1440x900: one short notice covers Quick capture (pointer hits the toast); a long German notice or three cover the AI orb too (pointer hits the toast at both). - after, same sizes and states: nothing covered; a pointer at each button's centre lands on that button. The stack container's bottom edge sits 160px above the viewport's foot (asserted, +-1px). - 390x844: unchanged, and still recorded as the open collision. Residual, now named in the comment: raised, the stack can sit over the right edge of a page's own content. On a fresh household at 1024x768 with three notices it covers the meals page's "Analyze week" button and part of the contacts empty-state line; with 14 seeded contacts it covers the right end of 1-4 contact rows (Email/Call), as bottom-6 already did. tests/mobile-toast-safe-area.test.ts keeps M-033 (safe-area-aware mobile offset that only changes at lg, no sm:bottom-6, no bare bottom-20) and now asserts lg:bottom-40 in place of lg:bottom-6. Validation (local, this build): form-error-semantics.spec.ts full run twice (37/38 then 38/38; the one miss was the unchanged 390x844 long row, 1 of 15 runs of it), the toast and corner cases --repeat-each=3 --workers=4 (54/54), weekly-meal-planner + quick-capture-task (83/83), 64 unit files reading toast.tsx (1330 tests), tsc, eslint. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TRY21ZKsFrfB3qtoP972A4 * Corner cases: compare who takes the tap, not which part of the notice CI on b5bf755 failed one case, twice (1970 passed): 390x844, long. The pointer at the AI orb's centre landed on the German notice's body where the table expected its Ausblenden button. Locally the same row missed once in 15 runs. Which part of a notice sits under a point moves with where its text wraps. The 390 rows are mobile geometry that lg:bottom-40 does not touch. The claim the pointer columns make is who takes the tap: the notice, or the corner button itself. Any hit inside the stack now compares as "toast", whether on its body or on one of its own buttons. A hit outside the stack still has to match by name, so the desktop rows, which expect Quick capture and the AI orb themselves under the pointer, are as strict as before. The 390 rows are kept as written, with a note. Spec only. Local, b5bf755 build: the corner describe at --repeat-each=3 --workers=4, 36/36; tsc and eslint clean. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TRY21ZKsFrfB3qtoP972A4 * Corner cases: the blocked invariant made explicit, with a moved-clear negative control On b5bf755, CI failed the 390x844 long-German corner row on both attempts: expected ai 'toast: Ausblenden', got 'toast'. The covered set and the Quick capture result were unchanged. The assertion relied on which descendant of the notice sat under the orb's centre. Locally the orb's centre (y 672) is 1px inside the top of the notice's 16px Ausblenden button (y 671-687). Any shift of 1px or more moves the point onto the notice's body. One local mechanism was measured: each notice fades in from 8px below (animate-fade-in, 0.4s), and in 2 of 12 local samples the report was read mid-slide. A wrap difference in CI would do the same. Spec only, this file only; no CSS, placement, timer or timeout change, and the 390 collision stays open. - tapped() and matchesCorner() now sit beside the CORNER table. A hit anywhere inside the toast stack is "toast"; otherwise it is the name of the button hit. A row matches when the covered set is identical and the same thing takes each tap. - The corner cases now assert the invariant per button. For a blocked row, the point is inside the stack (the notice takes the tap). For a clear row (every desktop row), the corner button is under its own centre, by exact name. The covered set is compared exactly. The keyboard path and the Dismiss/Undo assertions are unchanged. - cornerReport waits for the notices' finite animations to finish before reading, so boxes are read at rest. - New at 390x844, one per state: a moved-clear negative control. It raises the same notices and confirms the blocked row matches. It then lifts the stack 8px above the orb (inline style on the stack element in this page only) and holds the notices again. It asserts that neither button is covered, that neither tap is 'toast', that each button is under its own centre, and that matchesCorner(..., blocked) is false. Reproduced first, on the b5bf755 build, by holding the notice's fade-in at 150ms of 400ms before the report: - b5bf755's spec: 3/3 failed with CI's exact diff ('toast' for 'toast: Ausblenden'). - 0f6f495's spec: 3/3 passed. - This spec, with the animation set to 150ms and left running: 3/3 passed. - The negative control with the move removed: 3/3 failed, at not.toContain(names.capture). Validation, same build, unchanged source: - corner describe (12 + 3 new) at --repeat-each=5 --workers=4: 75/75. - form-error-semantics.spec.ts full, twice at --workers=2: 41/41, 41/41. - tsc --noEmit, eslint and git diff --check: clean. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TRY21ZKsFrfB3qtoP972A4 * Below lg, the toast stack stops left of the corner buttons (A11Y-001 #2) At 390x844 the full-width mobile stack sat level with Quick capture (bottom 5rem) and, when taller, the AI orb (bottom 9rem). Both are fixed in a 3.5rem column at right 1rem. A short notice took the tap meant for Quick capture; a long German notice or three took the orb's as well. The stack now keeps 5rem clear on the right below lg: px-4 becomes pl-4 pr-[calc(5rem+var(--safe-right))]. That is the column plus a 0.5rem gap, safe-area-aware like the buttons. lg:px-0 drops the inset from lg, so lg:bottom-40 and the desktop placement are unchanged. So are the bottom offset above the tab bar, the toast width cap, localization, roles/live regions, timers, hover/focus pause, Dismiss/Undo and the keyboard path. Requested on #778 (5970654661). Options measured at 390x844 with a runtime style override on the stack (measurement only; real notices; dialog open and closed): - full width (as was): Quick capture covered and intercepted (short); plus the orb and the language bar (long, stacked); dialog Phone / Alternate phone. - right inset 5rem (chosen): both corner buttons clear in every state. Covered set is a subset of before in every state: the language bar (long, stacked) and dialog Phone / Alternate phone, as before. - lifted above the orb (bottom 13rem): corner clear, but newly covers the empty-state "Add First Contact" and up to 8 relationship chips in the open dialog. - top, below the header: corner clear, but covers search, "Add Contact", the filter and, with the dialog open, its Close button and Full name. Disclosed trade-off at 360x780: the narrower toast (264px) wraps the long German notice to more lines, which newly reaches "Ersten Kontakt hinzufügen" and, in the open dialog, the Freund / Sonstiges chips. 3 stacked notices covered those at full width already. At 768x1024 nothing changes, since the 384px cap is narrower than the room left. Tests: - 390 CORNER rows now read clear. BEFORE keeps the replaced placements' rows for every size (from 736e1ff). - A negative control per size and state puts the replaced placement back on the stack, as an inline style in that page only. It must read as blocked: Quick capture intercepted, the BEFORE row matching. It must also no longer match the clear row. - The 390 placement test expects the right edge at 390 - 80. - The safe-area unit test asserts pl-4, pr-[calc(5rem+var(--safe-right))], no px-4 and lg:px-0 on the stack. Baseline-red, on the 03eecde build (old classes), with these tests: - The corner describe and 390 placement case: 7 failed, 18 passed. The failures were the 390 corner rows x3 (covered set), the 390 placement (right edge) and the 390 controls x3 (clear row precondition). - The new unit case failed against the old toast.tsx. Fix-green, after the rebuild: 25/25, and the unit test 3/3. Validation (local production build, local Supabase, --retries=0): - form-error-semantics.spec.ts full, twice at --workers=2: 50/50, 50/50. - Corner describe (12 cases + 12 controls), --repeat-each=5 --workers=4: 124/125, then 125/125. The miss was 1440x900 long's keyboard path: Enter on the orb didn't reach /dashboard/assistant within the 30s test timeout under 4 workers. That is desktop and unaffected by this change. - weekly-meal-planner + quick-capture-task: 83/83. - 64 unit files that read toast.tsx: 1,331 passed. - tsc --noEmit, eslint and git diff --check: clean. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TRY21ZKsFrfB3qtoP972A4 * Corner cases at 360x780: the residual recorded as it is (A11Y-001 #2) Spec only; no source, CSS or timer change. On #778 (5971395586) the owner asked for the 360x780 regression disclosed with f3988cc to be measured and kept open, not closed. The corner describe below lg now also runs at 360x780: short, long (de) and stacked, each with its replaced-placement negative control. As measured on f3988cc: - Both corner buttons are clear in every state at 360. - The 264px-wide stack lets the long German notice wrap to 7 lines (166px; 5 lines, 126px, at full width). It reaches the empty page's "Ersten Kontakt hinzufügen" as well as the language bar. - Three notices reach "Add First Contact", at full width too. The rows are labelled RESIDUAL, not accepted. BEFORE gains the full-width 360 rows, which the controls reproduce in the page: - short: Quick capture intercepted; - long and stacked: both corner buttons intercepted. Validation (local production build of f3988cc, --retries=0): - 360x780 cases: 6/6. - Corner describe (15 cases + 15 controls) at --repeat-each=3 --workers=4: 93/93. - form-error-semantics.spec.ts full: 56/56. - tsc, eslint and git diff --check: clean. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TRY21ZKsFrfB3qtoP972A4 * Below lg, the toast stack's right inset is 4.75rem, not 5rem (A11Y-001 #2) At 360x780 the 5rem inset made the stack 264px wide. The longest German notice (errors.thatChangeWasNotSaved) then took a seventh line, which reached further: with the contact dialog open, it took the tap meant for the Sonstiges chip and covered Freund. With the dialog closed, it took the centre of the empty page's "Ersten Kontakt hinzufügen". At 4.75rem the stack is 268px wide and the notice keeps to six lines. Measured first with runtime overrides on #778 (5971751746); applied on the owner's go-ahead (5971765202). - components/ui/toast.tsx: pr-[calc(5rem+var(--safe-right))] becomes pr-[calc(4.75rem+var(--safe-right))], a 0.25rem gap to the corner column. The comment says why, and what remains. Unchanged: lg:bottom-40 and everything from lg, the bottom offset, roles and live regions, timers, hover/focus pause, Dismiss/Undo. - tests/mobile-toast-safe-area.test.ts asserts the 4.75rem inset. - tests/e2e/form-error-semantics.spec.ts: - The 390 placement edge is 390 - 76. - New dialog-open cases at 360 and 390, short/long/stacked. They record the dialog controls a notice's box covers and those whose centre it takes (DIALOG_OPEN). Freund and Sonstiges must stay clear of the long notice, and each notice's Dismiss takes its own tap and focus. - UNDER_NOTICE measures "Ersten Kontakt hinzufügen" / "Add First Contact" at 360: where a centre tap lands, and the largest overlap in px (+-2). - RESIDUAL, not closed: - long notice: 229x15 of the 249x44 button, centre clear; - three notices: all of the 184x44 button, centre taken; - with the dialog open: Telefon under the long notice; Friend, Other and Phone under three; Phone / Alternate phone at 390; - the language bar, as before. Baseline-red, on the d3b3a51 build (5rem), with these tests: - 360 long, dialog open: covered also had Freund and Sonstiges, and intercepted also had Sonstiges. - 360 long, closed: the centre of "Ersten Kontakt hinzufügen" landed on the notice ('toast'). - 360 stacked: failed only on the overlap placeholder (184 measured). Those values were then recorded from the fixed build, 184x44, the same as before. - The unit case failed against the 5rem toast.tsx. Fix-green, on the 4.75rem build: - 360/390 corner, dialog-open and placement cases: all pass. - Quick capture and Ask AI take their own taps in every state. - The replaced-placement negative controls still read as blocked. Validation (local production build, local Supabase, --retries=0): - form-error-semantics.spec.ts full, twice at --workers=2: 62/62, 62/62. - Corner describe (15 cases + 15 controls + 6 dialog-open + the 390 placement) at --repeat-each=5 --workers=4: 185/185. No flaky. - weekly-meal-planner + quick-capture-task: 83/83. - 64 unit files that read toast.tsx: 1,331 passed. - tsc --noEmit, eslint and git diff --check: clean. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TRY21ZKsFrfB3qtoP972A4 * Assistant: the welcome composer either side of lg, recorded (new spec) A new spec only; no product change. The owner asked on #778 (5972242509) whether the 0px-wide assistant composer seen at 1024px is a test artifact or reproduces on the page as rendered. It reproduces with the standard local fixtures: a fresh household, a real sign-in, a production build. It is the same whether the page is reached by direct load, the sidebar link or the corner orb; with the service worker allowed or blocked; and with or without a notice showing. The page opens on its welcome surface (hero) until a conversation with one of the reader's own messages is open. That is the state on every arrival, saved conversations or not, and after "New chat". From lg, components/assistant/workspace.tsx lays the workspace out as lg:grid-cols-[320px_1fr_330px] with gap-6. That sits in a main of W - 280 (sidebar) - 56 (padding). The fixed tracks and gaps take 698px, so the centre column, where the hero sits, measures W - 1034, and 0 below that. The hero's chrome takes about 146px of it. Measured on the page: - centre column: 0 / 66 / 146 / 246 / 406 px at 1024 / 1100 / 1180 / 1280 / 1440; - the "Ask Bubaly" text box: 0 / 0 / 0 / 100 / 260 px wide; - 768-1023: a single pane, text box 526px. At 1024-1180 the box can't be seen or pointed at. It still takes focus and text, so a keyboard user types blind. The 44px send button overflows the column and still takes its own tap. The spec records: - per width, whether the text box can be seen, pointed at and typed in. At 1024, 1100 and 1180 this is marked test.fail() as a reproduced defect; it fails at the box's 0px width, as checked with the marker removed; - a passing record of what the reader gets instead at those widths, so the fix flips both; - a control at 1024: with a seeded conversation opened, the docked composer in the 320px left column can be seen, clicked and typed in. Any request to a host other than localhost is aborted and fails the test. The page makes none on load, so no AI provider is reached. No AI request is sent: Send is never clicked. Validation (local production build of 93c306f, local Supabase, --retries=0): - the spec at --repeat-each=3 --workers=3: 36/36, 9 of them the expected failures; - tsc, eslint and git diff --check: clean. Proposed fix, not made here (components/assistant/workspace.tsx is the assistant module's, read-only for this branch): - line 127: lg:grid-cols-[300px_minmax(0,1fr)] 2xl:grid-cols-[320px_1fr_330px]; - line 130: on the context aside, lg:col-span-2 2xl:col-span-1, so it sits under the two columns until 1536px. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TRY21ZKsFrfB3qtoP972A4 --------- Co-authored-by: Claude <noreply@anthropic.com>
This branch was successfully deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Families could launch outcomes from the dedicated Outcomes page, but Home and module pages did not reveal the relevant workflows. This closes the remaining W5 discovery gap from
docs/MARKET_DOMINATION_AUDIT.mdand the original #421 strategy coordination request.Home and Command Center now show outcome cards using their actual, already-loaded household counts. Related-outcome links on eight module routes derive from the existing outcome plans and open the selected workflow in the real launcher. Failed or incomplete counts show a refresh path; a capped roster never becomes a misleading birthday count. Day boundaries use the family's timezone.
Settings includes an explicit personal outcome navigation preset. The service reads the caller's current saved layout, preserves other pins (including paid destinations), child choices and unrelated notification preferences, and uses the existing updated-at trigger for a conditional save. A failed or interrupted save preserves or restores the prior layout. Shared sidebar files and global navigation defaults are unchanged; no migration is included.
Validation:
git diff --checkand the declared i18n gate pass. All new copy is translated in seven catalogues; existing translations are unchanged.household-trail, twoi18n-server-boundarycases,supabase-query-audit,untrusted-fence-ratchet).Target:
claude/roadmap-implementation-ld8bon. This is a reviewable W5 delivery; it does not claim that the remaining strategy, schema-owned metric snapshots, or connected-calendar onboarding are complete.