Skip to content

Repair persistent sessions and integrate page control fixes - #601

Merged
NewWorldVenture merged 32 commits into
mainfrom
codex/audit-source-reconciliation-20260927
Sep 27, 2026
Merged

NewWorldVenture merged 32 commits into
mainfrom
codex/audit-source-reconciliation-20260927

Conversation

@NewWorldVenture

@NewWorldVenture NewWorldVenture commented Sep 27, 2026 •

Copy link
Copy Markdown
Owner

Stale SDK session events or reads can arrive after logout, an account switch or token rotation. This change checks their token against current cookies before replacing the active identity, preventing an old result from restoring the wrong session. Reconciliation stays outside the SDK callback lock; legitimate rotation and temporary read failures preserve the established login.

This also integrates the remaining reviewed page/control repairs from #598 at4e2abf1 with main e96e745, which already contains #585 and #603. Initially open dialogs now install focus, keyboard and scroll behavior when their hydrated portal appears. Endpoint plan overrides retain the authenticated principal and report lookup outages as503. Responsive, translation, administrator ticket and export repairs are retained, along with all current catalogue keys. Shared navigation matches current main exactly; no SQL changes are included.

The durable meal test acknowledges the real save notification through its visible Dismiss button before proceeding, retaining database checks and its120-second limit. Seven existing controlled-browser fixtures now pair their synthetic cookie token with their SDK session; all assertions are retained, and a mismatched token still fails the real session guard. Source scans work on Windows and Linux.

Final52d validation passes: 20,856 unit tests across1,638 files in each of three hosted time zones; production build247; full generated types; lint; Database and Mobile jobs. Hosted run36339544177 finishes with a clean1,786/1,786 end-to-end browser matrix in18.6 minutes, with no failed or flaky cases. The separate canonical local run also passes20,856 UTC units/build/types/lint with all5,221 raw files/modes unchanged. The session repair retains six original failing controls and17 final browser passes. Earlier failures remain recorded in the audit rather than being erased by this clean run.

The five audit documents preserve all earlier failures, permanent IDs and Claude's attributed findings. RegisterB includes14,214 items after reconciling one previously unlisted existing test. Production readiness remains NO. Framework upgrade #602, newer #598 follow-ups and the reproduced Inbox selection/draft defects remain separate; no complete provider, physical-device or private-media workflow acceptance is claimed.

claude and others added 27 commits September 27, 2026 10:58
All 398 page.tsx routes are listed in thirteen lane files under
docs/audit/pages/, each lane mapped to the A-unit that owns the same
paths (docs/audit/COORDINATION.md), so Claude sessions and Codex can
audit in parallel without editing the same file. A lane is claimed
before it is worked; README.md holds the protocol and what PASS means.

finalaudit.md carries a generated roll-up (per-lane counts and every
page's status). scripts/page-audit-register.mjs regenerates it from
the lane files, so a conflict there is resolved by re-running, never
by merging. tests/every-page-is-in-the-page-register.test.ts fails if
a page is missing, listed twice, carries an unknown status, or the
roll-up is stale.

scripts/page-audit-crawl.mjs drives Chromium over every route and
records status, redirects, console/page errors, failed same-origin
requests, raw catalogue keys, the error boundary and overflow at
390 px. scripts/page-audit-session.mjs signs a disposable account in
through the real login form against a LOCAL Supabase only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01776xJyhVe8xJQvTGeNfT9T
C1-S9-94 — /dashboard/calendar threw "cannot add postgres_changes
callbacks ... after subscribe()" and dropped a section to its error
boundary. The browser client is a singleton and realtime-js channel()
returns the EXISTING channel for a topic already open, so two readers
of one table shared a channel: the second's .on() came after the
first's .subscribe(), and whichever unmounted first removed it from
under the other. lib/realtime/own-channel.ts gives each
postgres_changes subscription its own topic (the server filters on the
.on() config, not the name); all ten sites use it. Presence keeps its
shared topic, since presence meets on it. A guard reproduces
realtime-js's dedupe in a fake and is red on the shared name.

C1-S9-95 — every blog post's share links hydrated with a different
href than the server sent: window.location.origin in the browser, a
hard-coded host on the server. They now use canonicalUrl on both
sides, which is also the address a share should carry.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01776xJyhVe8xJQvTGeNfT9T
C1-S9-96 — /dashboard/vacations/new opens "New trip" on arrival and
failed hydration. Modal gated its portal on `typeof document`, which
is false on the client's first render (the hydration render), so the
server sent nothing and hydration produced a portal; React threw the
tree away and rebuilt it client-side. useHydrated reads the server
snapshot while hydrating, so the portal waits one commit. The command
bar takes the same gate. A guard fails on any portal gated on a
typeof check, and is red on the old Modal.

C1-S9-97 — every missing record under /dashboard rendered
AppNotFound's defaults, which were English literals in its parameter
list, where no catalogue scanner looks; the wallet passed a literal
label too. Four keys in the seven base catalogues; rendered in German
by a test that is red on the old component.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01776xJyhVe8xJQvTGeNfT9T
use-realtime-query and app-context now import lib/realtime/own-channel, and
the fixtures that mount them in a page list every module they load. The
module-graph guard (tests/medications-fixture-module-graph.test.ts) named
all eight; each lists the file now. The ten fixture specs that load the hook
pass locally (218 tests).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01776xJyhVe8xJQvTGeNfT9T
Found by the page audit crawl, signed in as a super administrator whose
family is on the free plan:

- /dashboard/autopilot said "100% probability the day runs smoothly",
  "0" risk alerts and "All clear" after POST /api/autopilot/scan answered
  403. A refused scan left no open suggestions, and the forecast read that
  absence as good news. The forecast, the risk count and the all-clear now
  wait for a scan that ran. A failed one shows "—" and says so, with a retry.
- /dashboard/briefing said "Failed to generate briefing" in English to a
  refusal the route had worded in the reader's language. The route's reason
  (wait, plan, try again) now reaches the screen, and the session's own two
  fallbacks are translated at render.

The 403 itself: requireFeature lets a super administrator onto every
feature page to preview it (F16 records the same bypass for the family
write path), and none of the endpoints behind those pages did. So
refuseUnlessEntitled and the on-demand scan now let a super administrator
through too, asked only after the family has been refused. The nightly
cron still runs each family on its real plan.

Guards (red on the old code, each checked): the route gate and the scan
route pass a super admin below the tier and answer 503 when the account
cannot be read; the Autopilot screen shows no forecast after a 403, a 500 or
before the first scan finishes, and does after a clean one; a refused
briefing keeps the route's reason.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01776xJyhVe8xJQvTGeNfT9T
Two conflicts, both additive: messages-module keeps ownChannel (C1-S9-94)
and main's family-media imports; hydration-ledger's fixture lists both
own-channel.ts and main's lib/ui/a11y.ts.

main's new tests/a-dialog-does-not-steal-the-caret.test.ts calls Modal as a
function under mocked hooks, and Modal now also reads useSyncExternalStore
(useHydrated, C1-S9-96). The harness models that hook as a client render
after hydration does — it returns the client snapshot — and asserts exactly
what it did before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01776xJyhVe8xJQvTGeNfT9T
…n its sentence

The page audit crawl, signed in at 390px, found 17 routes that scrolled
sideways, from 8px (/dashboard/sports) to 647px (/admin/support-tickets).
A DOM bisection per route found four causes:

- A responsive grid with no mobile column count. Its single column is an
  implicit `auto` track, never narrower than its content's min-content, so a
  table or a filter row widened the page. Fixed with grid-cols-1
  (minmax(0,1fr)), and minmax(0,1fr) in the lg templates: six admin pages,
  social settings, marketplace community, outcomes.
- A row of controls that could not wrap. PageHeader wrapped its own slot but
  not the button row 31 modules pass into it (photos, recipes). It wraps that
  row now. Behavior, guardian and the kitchen display wrap their own.
- A column tile laid out as a row. `.stat-card` is a flex row, and sports and
  school stack an icon over three lines of text inside one. Those tiles are
  columns now, and their English labels come from 13 new keys.
- Text that could not wrap. The feedback admin's GitHub notice is now one
  wrapping sentence rather than loose flex items. An inline link's truncate
  did nothing, so the link is a block.

0px on all 17 afterwards, with the same probe.

Found alongside: all seven catalogues defined three locator keys twice (from
a Sep 26 commit). JSON.parse keeps the last copy, so the geofence failure had
lost "The alert setting has not changed". The later copies are removed, and
tests/no-catalogue-defines-a-key-twice.test.ts scans lines, not the parsed
object (red on main's catalogues).

tests/a-page-fits-a-phone.test.ts pins each cause, and ratchets the 330
remaining responsive-but-implicit grids so the count can only fall.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01776xJyhVe8xJQvTGeNfT9T
…anguage

The i18n gate covers app/(marketing). Thirteen public and sign-in pages sit
outside it, and still carried English:

- a browser-tab title on /login, /signup, /kid-login, /auth/recovery,
  /auth/complete, /auth/step-up, /join, /offline, /gift/[token],
  /pay/[handle], /reviews, /reviews/new and /s/[slug]
- the join link's "This invite link is missing its token."
- the review page's "Review us on {platform}" buttons and its star labels

The titles come from generateMetadata and catalogue keys. Each review button
is one whole sentence per platform, because "on the App Store" and
"on Google" take different words in German or French. The star labels live
in the scope the page ships to the browser. That adds 21 keys to the seven
base catalogues.

Left as they are: three strings the server writes into rows or an email with
no reader locale (the gift notification, the welcome email subject, the
imported-event note). All three are I18N-001, which belongs to the owner.

tests/auth-callback-completion-page.test.ts now awaits generateMetadata and
asserts the title as well as noindex and no-referrer.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01776xJyhVe8xJQvTGeNfT9T
…larms

scripts/page-audit-static.mjs follows each page's imports to the app/ and
components/ files that render on it, and counts their hardcoded copy with the
repo's own scanner. It is the machine half of the README's "read the page"
step. Shared chrome and the UI kit are reported once, not on all 354 pages.

The crawler now:
- counts the signed-in app's own not-found copy (appNotFound.title) as a
  not-found, as well as the site's 404
- treats a 429 from the two tracking beacons as environmental: they
  rate-limit by IP by design, and a crawl is hundreds of views from one
  address
- ignores catalogue-key spellings inside code, pre, kbd and samp

The last one was /admin/social/providers. It lists TikTok's required OAuth
scopes, one of which is `video.publish`, the same spelling as a catalogue
key. The scopes are identifiers, so the page now marks them up as <code>.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01776xJyhVe8xJQvTGeNfT9T
Rows:
- FIXED, each naming its finding and commit: calendar, autopilot, briefing,
  behavior, vacations/new, blog/[slug], the 17 routes that overflowed at
  390px, and the 13 public and sign-in pages whose titles and copy were
  English. Where a fixed page still carries hardcoded copy, its row names
  the count under C1-S9-101.
- IN PROGRESS, with crawl and static evidence: the rest of the PUBLIC and
  AUTH lanes, which Claude-1 holds. The read is still pending.

Lanes Claude-1 does not hold were claimed for this edit and released FREE in
the same commit.

finalaudit.md:
- C1-S9-93 to C1-S9-100: the register, and the seven fixes the crawl led to.
- C1-S9-101 (OPEN, HIGH): 2,765 hardcoded English strings across 596 files
  in the signed-in modules, counted per lane.
- C1-S9-102 (OPEN, LOW): notFound() under the app loading boundary answers
  200.

PRODUCTION READY stays NO.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01776xJyhVe8xJQvTGeNfT9T
…nguage

Every blog post's save button had four English labels, and the subscribe
form's failure message was English. components/blog sat outside every gated
i18n surface. It is gated now.

Five keys in seven catalogues. The save button's keys use a blogHeartButton
prefix, added to the marketing client scope.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01776xJyhVe8xJQvTGeNfT9T
occasionLabel() returned English, and both the public gift page and the
parent's gift screen (occasion picker included) printed it. The public page
also fell back to "a child" and "a family" when a name was unknown. It is now
occasionLabelKey(), and each caller words the key with its own t. Eight keys
in seven catalogues.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01776xJyhVe8xJQvTGeNfT9T
…flags English prose

The i18n scanner reads source by pattern, and misses copy in a ternary or a
setter argument (C1-S9-103). With --locale de-DE the crawler sets the locale
cookie and reports every visible text node, aria-label, placeholder or title
that has two or more English function words. Code and aria-hidden text are
skipped.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01776xJyhVe8xJQvTGeNfT9T
… that were not true

C1-S9-105. Controls that looked like controls and had no handler:
- Invite Admin (/admin/admins) and New Ticket (/admin/support-tickets)
  open forms that call their existing, previously unused actions.
- Export downloads a CSV from a new super-admin route. It reads every
  ticket, paged. A failed read is a 502, the export is audit-logged, and a
  formula-leading cell is written as text.
- The ticket menu's "Assign agent" had no action at all. It is now
  "Assign to me", backed by a new assignTicketToMeAction.
- "Ask AI" on sports and school opens that page's AI insight.
- Each row's "more options" on both tables is now a confirmed delete, with a
  read-back.
- The six sports tabs changed only their highlight. Each now shows its own
  sections, and the two with nothing behind them are gone.
- School's tab labels, weekday names (via Intl) and due-in text (via
  Intl.RelativeTimeFormat) follow the reader's language.

tests/a-button-does-something.test.ts scans every page and component for a
button with no action, and carries its own self-test.

C1-S9-106. The /admin/admins Settings tab showed four switches that did
nothing. Two of them said "Require 2FA for all admins" and "Admin session
timeout" were ON, and neither is enforced anywhere. They are now read-only
statements of what the console enforces today. Whether to require two-step
sign-in and an idle timeout for admins is recorded for the owner.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01776xJyhVe8xJQvTGeNfT9T
main now carries session_01KP9rt5's page audit (#591): a per-page "Every page"
table in finalaudit.md with lanes A–F, fixes for the same defects this branch
found, and its own approaches to two of them.

Conflicts, resolved:
- Grids on six admin pages and outcomes: this side's grid-cols-1 plus
  minmax(0,1fr), a superset of main's minmax, and what
  tests/a-page-fits-a-phone.test.ts pins.
- Public page titles: this side's generateMetadata. The pageTitle.* values
  drop " · Bubaly", because main's root template now adds the brand.
- /gift/[token]: main's dead-link card, with this side's translated title and
  fallbacks.
- AppNotFound: main's comment and its notFound.goToDashboard key. This
  side's parallel appNotFound.goToDashboard is removed, and its test reads the
  adopted wording.
- Seven catalogues: merged key by key three ways, written in main's key
  order.

Composed rather than chosen:
- Autopilot. main's preview skips the automatic scan. An unscanned preview now
  says "no scan has run yet" and offers Re-scan, rather than sitting on
  "Scanning your family…". Re-scan works for a super admin through
  C1-S9-98's route bypass.

C1-S9-107, one register:
- Claude-1's docs/audit/pages/ lane files and roll-up are removed.
- scripts/page-audit-register.mjs lists routes and checks main's table:
  every page.tsx has exactly one row. The test says so.
- Lanes A–F are all claimed elsewhere (A by session_01TRY21 in #594). This
  branch's fixes on their pages are in a hand-off table above the register,
  not edited into their rows.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01776xJyhVe8xJQvTGeNfT9T
main's port of C1-S9-94 (854d8dd) added the same import this branch had,
and the merge kept both. tsc: TS2300 duplicate identifier.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01776xJyhVe8xJQvTGeNfT9T
… titles off a phone

Found by rendering every page in German (page-audit-crawl --locale de-DE). The
i18n gate had called these surfaces clean, because the copy sat where it does
not look:
- The closing call to action's defaults, in a parameter list, closed
  /, /faq, /privacy, /terms, /cookies and /acceptable-use on English.
- /ai's call-to-action subtitle was an English attribute.
- Two /privacy paragraphs were literals inside an array of keys.
- /reviews/new fell back to English default copy.

All are catalogue keys now: eight keys in seven catalogues.

"Datenschutzerklärung" and "Nutzungsbedingungen" are single words wider than
a 390 px phone. The legal title now hyphenates by <html lang>, with a break
fallback, so all four legal pages measure 0 px in German.

Left OPEN as content: the FAQ answers come from marketing_aeo_questions,
which 0279 translates only in part.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01776xJyhVe8xJQvTGeNfT9T
…ices" says why it can't open

/dashboard/billing showed English in every locale:
- the plan-choice button ("Current plan", "Choose Family+", "Switch billing
  to annual")
- the status badge, and the plan names, descriptions and feature lists
- the tabs and stat labels
- the fifteen spending categories

All of it now comes from catalogue keys, in labelKey/nameKey fields.
Categories stay stored as the English word, because it is data, and are shown
through categoryLabel().

openPortal() read the route's JSON, found no url, and did nothing, so every
refusal the route words (not a parent, no billing account, unavailable) was
dropped. It now toasts that reason.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01776xJyhVe8xJQvTGeNfT9T
…mplate creates

/dashboard/chores showed English in every locale:
- the status badges, tab labels and approval toast
- every "N pts"
- the ten chore templates

A template is worse than a label: picking one wrote its English title and
description into the family's new chore. Templates now hold keys, and each is
worded when shown and when picked, so the chore is created in the picker's
language.

33 keys in seven catalogues. Scanner findings for chores-module drop from 36
to 3 (type-alias fragments).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01776xJyhVe8xJQvTGeNfT9T
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01776xJyhVe8xJQvTGeNfT9T
@vercel

vercel Bot commented Sep 27, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated
bubaly Ignored Ignored Sep 27, 2026 6:08pm UTC

Request Review

@NewWorldVenture NewWorldVenture changed the title Integrate page fixes with private AI logs and consistent account authorization Repair persistent sessions and integrate page control fixes Sep 27, 2026
@NewWorldVenture
NewWorldVenture marked this pull request as ready for review September 27, 2026 18:31
@NewWorldVenture
NewWorldVenture merged commit 82da8da into main Sep 27, 2026
7 checks passed
NewWorldVenture pushed a commit that referenced this pull request Sep 27, 2026
19 files conflicted, almost all where both sides had translated the same
thing:
- Auth and public page titles: main's C1-S9-100 translated them too.
  Main's version is kept, and the duplicate imports both sides added are
  dropped.
- /reviews/new star labels and the chores approval toast: same text, and
  main's keys are kept.
- The auth-callback test keeps main's note, with the port's mock that
  passes parameters through.
- Catalogues: the union. Six keys that both sides added twice are kept
  once, with main's wording where the two differed.
- i18n ratchet: both histories kept. The merged tree measures 2,208,
  banked as the ceiling.

tsc clean. Lint: 0 errors, 15 warnings. Full suite in UTC: 21,381 tests in
1,698 files, with the 12 duplicate-key failures now fixed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DXw2nu25BjyRfA6Fg3YiMS
NewWorldVenture pushed a commit that referenced this pull request Sep 27, 2026
…ct-45d8k6

Main integrated this branch's PR585/598 work. Its side is kept wherever
both touched the same thing:
- route-feature-gate: main's isSuperAdmin(db), with 503 when the read
  fails; route-plan-gate.test.ts is main's copy, which pins that 503.
- a-client-write-reads-what-it-changed, a-seeded-settings-row…: main's
  versions of the same translated assertions.
- Catalogues: main's order and values (incl. its appNotFound wording) plus
  this branch's 540 keys main lacks; nothing of main's removed.
- finalaudit.md: C1-S9-115…126 kept after main's entries.
- Ratchet: main's 2650 lowered to 2349 by C1-S9-114…126.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01776xJyhVe8xJQvTGeNfT9T
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants