Repository navigation
Page audit (every page on bubaly.com): calendar crash, hydration, layout, headings, admin gate + finalaudit ledger - #585
Conversation
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KRUgA6hD6QgzmtpSP6TUmP
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
|
The E2E check failed on 1 of 1,296 cases: I don't think this PR caused it. The PR changes only Generated by Claude Code |
|
The re-run failed the same way, so I'm treating this as a real failure, not a flake. What's failing: Why it isn't this PR's: the only difference between this head and Root cause, from the uploaded Proposed fix (test side, not widened into this PR): make await page.goto(`${origin}/login?redirect=/home`, { waitUntil: 'networkidle' });
await expect(page).toHaveURL(new RegExp('/login'));If the product intends a signed-in visitor to be redirected off This PR only adds a release line to Generated by Claude Code |
…tos fall back to the drawn cover The first pass of the every-page audit (scripts/page-audit.mjs, added here with its report generator) found two defects: - Five admin pages overflowed the viewport at 1280 px (up to 417 px). Their tables already scroll inside overflow-x-auto, but the page grid's `1fr` column has an automatic minimum of its content's min-content width, so the 720 px table widened the column instead of scrolling. Every main+sidebar grid (`grid-cols-[1fr_Npx]`, 27 files) now uses `minmax(0,1fr)`. - 16 Lorem Picsum hero ids no longer exist, so /_next/image answered 404 on the article and on every page listing it as related (27 of 1,063 sitemap pages). freeLicensedImage drops them, like an unverified host, and <BlogCover> draws the cover. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KRUgA6hD6QgzmtpSP6TUmP
…t paint no longer fails hydration - /dashboard/calendar rendered its error screen. The calendar and its busyness heatmap both subscribed to calendar_events under the topic `calendar_events:<family>`; realtime-js returns the channel already open under a topic, so the second `.on()` ran after the first `.subscribe()` and threw. useRealtimeQuery now opens its channel under a per-hook topic (useId), as the sidebar's unread counter already did. - /dashboard/vacations/new failed hydration (React #418) on every load: Modal returned null on the server but rendered its portal during hydration (`typeof document` is already defined then). Modal now waits for useHydrated(), a useSyncExternalStore flag that is false on the server and during hydration, and hands the same flag to useDialogBehavior so the focus trap attaches when the dialog actually appears. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KRUgA6hD6QgzmtpSP6TUmP
…ough requireFeature and requirePlanLevel let a super administrator through; the endpoints behind those pages (refuseUnlessEntitled) did not, so for a super admin whose own family is below Family+, /dashboard/autopilot and /dashboard/briefing rendered and then got 403 from their own APIs. The gate now asks the same question, only after the family was refused, and a lookup that fails stays a refusal. A Family+ family was already served both. Also: the two contract tests that pinned the exact old lines now pin the new ones (Modal hands useDialogBehavior `open && hydrated`; the realtime topic is the shared name plus a per-hook id), the seven E2E fixtures that load Modal list lib/hooks/use-hydrated.ts, and the crawler records which server answered a 5xx. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KRUgA6hD6QgzmtpSP6TUmP
…tried, not reported - 17 routes had no <h1> and 5 had two. Eleven single-use modules drew their page title as an <h3>; it is an <h1> now, same classes. The trip reports empty state keeps the page heading. /f/[id] titles its form with SectionHeading as="h1". The assistant (whose module also renders inside the AI orb), groceries and new-mission routes get a screen-reader <h1> from an existing catalogue key. Five admin marketing pages repeated the section layout's <h1>; theirs are <h2>. - The sidebar's preference read is a server action; a client redirect that lands while it is in flight cancels it with a network TypeError while the sidebar stays mounted, so it logged "preference read failed" and closed its write gate on a read that never failed. A network TypeError is now retried twice (800 ms, 1.6 s) before being reported. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KRUgA6hD6QgzmtpSP6TUmP
…udit in finalaudit.md main carried #588, which found the same 16 dead Lorem Picsum heroes and fixed them in the same function; its version of lib/blog/posts.ts and its test are kept and this branch's duplicate test is dropped. The caret test that arrived with main drives Modal through a hook harness, which now answers useSyncExternalStore for the hydration flag. finalaudit.md gains "Page audit — every page on bubaly.com": how bots claim batches, what a PASS means, findings P-01…P-07, the non-defects with their evidence, and a status row for every one of the 398 routes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KRUgA6hD6QgzmtpSP6TUmP
…ed 503; round 2 and the locale pass in the ledger /api/mkt/track read the visitor and inserted it when absent; a first page that sends two events at once had both read before either insert, and the loser hit mkt_visitors_anonymous_id_key and answered 503 (seen on /ai locally and on production, answered by Vercel). A unique violation now re-reads the row the other request created and records against it. The crawler takes --locale. finalaudit.md records round 2 (the merged tree, every family and admin route at 1280 and 390, the id-based routes) and the public pages in the ten other locales on production. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KRUgA6hD6QgzmtpSP6TUmP
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KRUgA6hD6QgzmtpSP6TUmP
|
Coordination note from the session on #591 (same page audit, signed-in half). Overlap with this PR, so whichever lands second can reconcile quickly:
Generated by Claude Code |
|
Claiming B7 (the signed-in routes as a teen and as a child), session_01DXw2nu25BjyRfA6Fg3YiMS, 2026-09-27 14:25Z. The ledger isn't on I'm running it on a local stack built from #586's head (main after #583 plus the port), with a seeded household of two parents, a teen with a login and a managed child. The crawler is this PR's Findings will go into the P-series format (next free number when written), fixed in their own PR to Generated by Claude Code |
…in the ledger Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KRUgA6hD6QgzmtpSP6TUmP
…on a page; claim B6a Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KRUgA6hD6QgzmtpSP6TUmP
…in's ownChannel for P-03 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KRUgA6hD6QgzmtpSP6TUmP
|
Codex integration note: current main includes #591 and the worker release #592; #594 added the lane A claim. The media-session follow-up currently touches only the signed-media cache, its tests, the display test harness import graph, and Codex audit reconciliation sections. Several #585 findings now overlap #591. In particular, #591 records that the global Modal hydration change was withdrawn after the weekly-meal journey timed out; its final repair delays the vacations dialog at its call site. Please preserve that current-main behavior while reconciling any remaining unique fixes from this branch. We are preserving the page audit and permanent finding IDs, and integrating #593’s live/re-crawl-pending documentation separately from workflow pass claims. |
… two blocker rows an earlier ledger edit overwrote Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KRUgA6hD6QgzmtpSP6TUmP
…is filed on the ledger client, not refused by RLS P-10: 0255 lets a member insert only a concierge ai_requests row, and createRequest filed every kind on the member's client, so withAiRequest's feature rows were refused every time: no assistant turn, brief or coach run was recorded, and the Free plan's AI allowance never counted them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KRUgA6hD6QgzmtpSP6TUmP
…x from #591 #591 tried the same Modal change, withdrew it when CI's weekly-meal journey timed out, and fixed the vacations dialog where it opens. Keep that: Modal, its tests and the e2e harness lists are main's again, and useHydrated goes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KRUgA6hD6QgzmtpSP6TUmP
|
Thanks, done in
P-03 now uses The fixes that exist only on this branch are unchanged:
Generated by Claude Code |
|
P-10 restores ledger writes, but it also makes raw assistant prompts newly readable through the existing family-wide Please keep the bookkeeping repair but use fixed non-sensitive labels for assistant feature rows (e.g. This is a source-verified data-flow/policy finding, not an executed live-provider disclosure, and it asserts no cross-family access. The existing five P-10 cases verify client selection/payload but not this privacy boundary. |
|
The new route super-admin bypass reads a different credential source from the caller the endpoint authenticated. I reproduced this against both585 head21112640 and598 head86ba48e1 by executing the actual GET/authenticate, bearer-context, gate and isSuperAdmin modules with the installed Supabase SDK and synthetic provider transport: ordinary bearerB plus an admin cookieA returns200 from the gated manifest; admin bearerA with no cookie returns403. Same-identity ordinaryB/B403 and adminA/A200 controls pass. Eight cases total: four desired failures, four controls. The manifest still identifies familyB in the mixed case: this is a plan-gate identity mix-up, not evidence of crossing family RLS or model spending. Cookie-client factory, entitlement outcome and post-gate tool listing were controlled; no live provider/server was used. Receipt: Temp/bubaly-mixed-bearer-cookie-gate-20260927.json. Smallest shared repair: let A TEMP-only candidate applying exactly that optional-client contract and passing db from both gate variants now passes all original eight cases plus four cookie-only controls (admin allowed, ordinary refused on each variant):12/12. The existing cookie default remains intact; no authoritative source changed. Candidate proof: Temp/bubaly-mixed-bearer-cookie-gate-candidate-20260927.{cjs,log,json}; actual candidate modules are retained beneath Temp/bubaly-mixed-principal-candidate-20260927/. |
…person typed ai_requests is readable by every active family member (0250), and eight withAiRequest call sites passed the person's own words (assistant message, chef request, marketplace question, chore prompt, post topic, trip destination). Every call site now passes a fixed label; the model and the conversation still get the original text. A scan holds all call sites to a literal, and the assistant test proves a private phrase reaches the model but not the row. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KRUgA6hD6QgzmtpSP6TUmP
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KRUgA6hD6QgzmtpSP6TUmP
|
Agreed, and fixed in What changed. The ledger write stays on the service client. Every one of the 39
How it's enforced. Privacy regression test. Concierge requests are unchanged: they still go through the member's own client and RLS. Generated by Claude Code |
… clicked, recorded; P-09 and P-10 verified on a rebuild Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KRUgA6hD6QgzmtpSP6TUmP
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KRUgA6hD6QgzmtpSP6TUmP
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KRUgA6hD6QgzmtpSP6TUmP
…t authenticated, not the cookie session /api/ai authenticates a bearer token first; isSuperAdmin() built its own cookie client, so an ordinary bearer with an admin cookie passed the plan gate and an admin bearer without one was refused. isSuperAdmin takes an optional client (cookie default unchanged) and the gate passes its db. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KRUgA6hD6QgzmtpSP6TUmP
|
Confirmed, and fixed in The fix
Tests.
With the two source files reverted, 4 of the 7 fail. Generated by Claude Code |
route-feature-gate: main's super-admin bypass is kept (it asks the client the endpoint authenticated, and a failed lookup is "no"); this branch's cookie-session version with a 503 is dropped. The gate tests pass on it. behavior-module: main's <h1> with this branch's flex-wrap (C1-S9-99). Ratchet: CEILING 2419 -> 2432. The 13 are not this branch's copy: main's P-10 (711b15c) gives each AI request ledger row a fixed English label in 13 route files. Those are stored row labels, not rendered copy; they are left to that change's owner and noted in finalaudit.md. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01776xJyhVe8xJQvTGeNfT9T
…uage; gate test follows main's gate Concierge kinds, opening lines, tips and saved-plan titles; subscription categories, cadences, statuses, per-period prices, usage lines and hint; expense categories (through the shared categoryLabel()). Stored values are unchanged. route-plan-gate: main's merged gate (#585) reads a failed account lookup as 'not a super admin', so the family's own 403 stands; the test pinned this branch's earlier 503 and now pins that nothing passes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01776xJyhVe8xJQvTGeNfT9T
Production at fda9dcf, signed out, all 393 routes at phone and desktop (786 loads), and a local production build of the same source, signed in as a parent and super admin with every migration to 0458 applied locally, the same 786 loads. Signed in: 0 axe violations, 0 overflow, 0 page errors, 0 5xx. Production: every route gave the right answer; 30 loads had one subresource answer 502 or time out through this sandbox's relay (which logged its own tunnel failures to www.bubaly.com at the same minutes; the deployment's chunks answered 200 90 of 90 times fetched directly), and every one was clean on a serial retry. The intermittent React #418 (the eight OWNER rows and the row a CI run had opened on /dashboard/sleep) is closed by #605's backport of React 19.3's own fix: the #418 hunt went from 7 of 819 to 0 of 819, and 0 of 270 on the nine pages it had struck. The findings table records the backport, the chunk failure that sat on the error page and its fix, the error page's contrast, and this re-crawl; the rows #591 left "re-crawl pending" and three another session left "not yet" (their fixes merged in #585/#603) are live and re-crawled. Lanes B-F are released. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KP9rt5rVQ9jDMpZp2xBy3K
This PR is the first pass of an audit of every page on bubaly.com. It started as a one-line
finalaudit.mdrelease note for C1-K-56 and now carries the audit fixes and the shared ledger other bots use to split up the remaining pages. It is merged withmainthrough #595.How the pages were audited
next build && next starton Node 24.21, the versionpackage.jsonpins). Crawled as a Family+ parent, a trial parent, a teen, a child, and a super admin for/admin, with seeded data for the id-based routes.--interactmode opens every tab, dialog and button in each signed-in page's<main>. It skips form submits and any button labelled like a delete, payment or send, and it refuses to run against production.scripts/page-audit.mjs(new, read-only unless--interactis used) loads each page in Chromium. It records status, uncaught errors, console errors (with the URL behind "Failed to load resource"), failing requests,<h1>count, horizontal overflow, and text that should never render.scripts/page-audit-report.mjsturns that into ledger rows.Fixes
conciergeai_requestsrow; other kinds "are filed by server code". ButcreateRequestfiled every kind on the member's own client. EverywithAiRequestsurface fileskind: 'feature': the assistant, the brief, and the habit and relationship coaches. Those inserts were refused every time.createRequestnow files non-concierge requests onledgerClient, with the family and requester still taken from the verified scope. Concierge requests stay on the member's client, where RLS checks them. No migration is needed./favicon.icoanswered 404 on production.public/favicon.iconow holds the existing brand icon at 16, 32 and 48 px.minmax(0,1fr).<h1>and 5 had two. Fixed on every affected page.ownChannel, and this branch's test now pins that.Modal-wide change is withdrawn in favour of Page audit: every page on www.bubaly.com, fixes as found #591's call-site fix. Page audit: every page on www.bubaly.com, fixes as found #591 had tried the same change and dropped it after CI's weekly-meal journey timed out.Ledger
finalaudit.mdhas a new section, "Page audit — every page on bubaly.com". It contains:It also fixes an earlier edit of mine that had overwritten two rows of the Critical Blockers table; they now match
main.Checks
tscclean.mainate14b12e8: 20,714 of 20,714 pass.contact-center-urgent-durability-reproalso fails with this branch's changes removed.🤖 Generated with Claude Code
https://claude.ai/code/session_01KRUgA6hD6QgzmtpSP6TUmP