deckhearth/.convoys/fix-layout-default-user.md
Randall Stillwell e31ad825b4 convoy: scope fix-layout-default-user (P0 #7 — Layout maintainer-email leak)
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>
2026-05-24 14:22:20 -05:00

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".
role-design-system-auditor
role-ia-architect
browser-smoke
open 2026-05-23 ship-readiness P0
bump-next-js (shipped)
fix-auth-bypass (shipped)
drop-public-setup (shipped)

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 the Layout({ children, user = {...} }) default to user = null. Add a logged-out rendering branch (navbar / profile dropdown / mobile menu) that handles user === null cleanly — typically "Sign in" CTA replacing the user avatar + dropdown.
  • 17 pages in pages/** that import Layout (confirmed via grep):
    pages/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.js
    
    For each: confirm it passes user explicitly 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-name convoy (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 queued single-auth-provider convoy (P1 #9).
  • God-component splitcomponents/Layout.js is 700+ lines. Splitting is the queued god-component-split convoy (P2 #13). Touch only the user- prop default and the logged-out rendering branch in THIS convoy.
  • MobileNavigation — receives user from 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.md Gotcha #8 — will be marked RESOLVED in the post-convoy doc-writer pass; do not pre-emptively edit AGENTS.md here.

Architect's questions

  1. 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?
  2. Page audit triage. For each of the 17 pages, three buckets:

    • Always-authenticated (dashboard, my-cards, profile, settings, scanner, admin/*) — must pass user explicitly; pages without it should add it via useAuth().
    • 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.

  3. 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).

  4. 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 Layout renders the logged-out shape when user === undefined and user === null would 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.