The last remaining P0 ship-blocker from .convoys/ship-readiness.md. components/Layout.js line 562 defaults the user prop to a real email address (me@randallstillwell.com); any page that renders Layout without passing user explicitly impersonates the maintainer. Scope: components/Layout.js + audit of 17 pages that import Layout (grep-confirmed list in convoy file). Single PR likely. Auditor cohort skipped (no design-system, IA, or browser-smoke surface). Architect to address: - Q1: logged-out rendering branch design (navbar, mobile-nav, auth-only items treatment) - Q2: page audit triage into always-auth / public-or-auth / anonymous-allowed buckets - Q3: brief decomposition (single brief / 2 briefs in 1 PR / fan-out) - Q4: whether to add vitest coverage for the logged-out branch (recommend yes — small surface, high regression protection) Hard out-of-scope: branding (pick-a-name), auth-provider collapse (single-auth-provider), Layout god-component split (god-component-split). depends_on: bump-next-js (shipped), fix-auth-bypass (shipped), drop-public-setup (shipped) addresses: P0 #7 from .convoys/ship-readiness.md parent: ship-readiness Co-authored-by: Cursor <cursoragent@cursor.com>
5.7 KiB
| name | classification | success_metric | skip | status | created | parent | addresses | depends_on | ||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| fix-layout-default-user | convoy | components/Layout.js's user prop defaults to null. Every page in pages/** that renders Layout either passes a user prop explicitly OR relies on the new logged-out rendering branch. No real email address remains in any component default-prop. Manual smoke: load an unauthenticated session on a page that previously impersonated the maintainer; confirm the navbar / profile dropdown reads "Sign in" rather than "me@randallstillwell.com". |
|
open | 2026-05-23 | ship-readiness | P0 |
|
Fix Layout default user
Close P0 #7 from .convoys/ship-readiness.md (the last remaining P0
ship-blocker). components/Layout.js line 562 defaults the user prop to
{ email: 'me@randallstillwell.com', role: 'user' } — any page that renders
Layout without passing user explicitly displays the maintainer's real
email and impersonates them as the logged-in user.
Scope (verbatim from ship-readiness P0 #7)
components/Layout.js— change theLayout({ children, user = {...} })default touser = null. Add a logged-out rendering branch (navbar / profile dropdown / mobile menu) that handlesuser === nullcleanly — typically "Sign in" CTA replacing the user avatar + dropdown.- 17 pages in
pages/**that import Layout (confirmed via grep):
For each: confirm it passespages/scanner.js pages/collection/[identifier].js pages/card/[id].js pages/my-cards.js pages/cards.js pages/deck-builder.js pages/deck/[id].js pages/decks.js pages/dashboard.js pages/community/collections.js pages/collections.js pages/settings.js pages/profile.js pages/invite/decline.js pages/invite/accept.js pages/admin/card-import.js pages/admin/card-editor.jsuserexplicitly OR triage that it should use the new logged-out branch (e.g. public pages like card/[id].js, community/collections.js may legitimately render Layout for anonymous visitors).
Out of scope
- Branding — Layout still renders "Deck Hearth" / "DH" while the rest of
the repo says "TCG Vault". That mismatch is the queued
pick-a-nameconvoy (P1 #12). Don't fix branding here. - Three parallel auth providers — Layout reads from one of
lib/auth-context.js/lib/admin-auth.js/lib/use-auth.js. Collapsing them is the queuedsingle-auth-providerconvoy (P1 #9). - God-component split —
components/Layout.jsis 700+ lines. Splitting is the queuedgod-component-splitconvoy (P2 #13). Touch only the user- prop default and the logged-out rendering branch in THIS convoy. MobileNavigation— receivesuserfrom Layout. May need a similar default-prop fix if it has the same anti-pattern. Audit during architect pass and decide whether to fold in or queue separately.AGENTS.mdGotcha #8 — will be marked RESOLVED in the post-convoy doc-writer pass; do not pre-emptively edit AGENTS.md here.
Architect's questions
-
Logged-out rendering branch design. When
user === null, what should Layout render?- Q1a: Navbar / profile dropdown — replace the user avatar + email
with a "Sign in" link to
/login? - Q1b: Mobile bottom-nav — same treatment, or hide the user-only items entirely?
- Q1c: Authenticated-only nav items (admin, settings, profile) —
hide them, or show but link to
/login?
- Q1a: Navbar / profile dropdown — replace the user avatar + email
with a "Sign in" link to
-
Page audit triage. For each of the 17 pages, three buckets:
- Always-authenticated (dashboard, my-cards, profile, settings,
scanner, admin/*) — must pass
userexplicitly; pages without it should add it viauseAuth(). - Public-or-authenticated (cards, card/[id], collection/[identifier],
community/collections, deck/[id], collections, decks) — currently
show different views based on auth; the Layout user prop should
come from
useAuth()either way. - Anonymous-allowed (invite/decline, invite/accept) — may legitimately render Layout without a user; rely on the new logged-out branch.
The architect should produce the exact bucket assignment per page and the brief should give the implementer the per-page instruction.
- Always-authenticated (dashboard, my-cards, profile, settings,
scanner, admin/*) — must pass
-
Brief decomposition. Three options:
- Single brief, one PR. All 18 files (Layout + 17 pages) in one diff. Reviewable but big.
- Two briefs, one PR. Brief 1: Layout change + logged-out rendering. Brief 2: page audit (depends on Brief 1). Both ship together.
- Fan-out by page bucket. Brief 1: Layout change. Brief 2: always-auth pages. Brief 3: public-or-auth pages. Brief 4: anonymous-allowed pages. Multitask-friendly via worktrees.
Recommend two briefs in one PR for size + reviewability balance, unless the page audit reveals >10 files needing real changes (in which case fan-out makes sense).
-
Test coverage. Should this convoy add vitest tests that exercise Layout's logged-out branch? The fix-auth-bypass convoy added 16 auth tests (
test/lib/permission-middleware.test.js); a similar lock-in for the user-prop default could prevent regression.Recommend yes — a single test that asserts
Layoutrenders the logged-out shape whenuser === undefinedanduser === nullwould catch any future regression that reintroduces the maintainer-email default. Trivial to write; high value.
Expected size
1-2 briefs, ~18 files total (1 component, 17 pages). Single PR likely.