--- convoy: fix-layout-default-user brief_number: 1 depends_on: [] files: - components/Layout.js - test/components/Layout.test.js - package.json - package-lock.json cross_brief_commitments: - brief: 2 description: | Brief 2 changes the seven pages that today either omit the `user` prop or pass a hardcoded maintainer-email object. Brief 2 assumes Brief 1's new logged-out branch (default `user = null`, "Sign in" CTA in `UserProfileDropdown`) is in place — without it, those pages would render "Sign in" before their own auth state resolved, but Layout's default would silently rewrite that back to the maintainer email. Ship Brief 1 first in the diff. --- # Brief 1: Default Layout's `user` to null + render a logged-out branch + lock the contract with a vitest test ## Goal (1 sentence) Change `components/Layout.js`'s `user` default from `{ email: 'me@randallstillwell.com', role: 'user' }` to `null`, replace `UserProfileDropdown`'s avatar+email+menu with a "Sign in" link to `/login` when `user === null`, and add a vitest test under `test/components/Layout.test.js` that locks in the contract by asserting the rendered tree never contains `me@randallstillwell.com` for the no-user / null-user branches. ## Files in scope (do not edit anything else) - `components/Layout.js` — modified (default-prop fix + logged-out `UserProfileDropdown` branch). - `test/components/Layout.test.js` — new (regression test). - `package.json` — modified (add `jsdom` and `@testing-library/react` to `devDependencies`). - `package-lock.json` — regenerated by `npm install`. ## Conventions to follow - **`.cursor/rules/ui-and-theming.mdc` § Component conventions.** Already documents the intent: "Avoid hardcoded default values for `user` props. … New components must default to `null` and render a logged-out state." This brief is the first concrete application of that rule. - **`.cursor/rules/auth-and-permissions.mdc` § Authentication state on the client.** `useAuth()` returns `{ user, loading, … }` where `user === null` means logged out. Layout's null-user rendering must be safe for that case; do **not** add new logic that throws on `user === null`. - **`.cursor/rules/no-go-zones.mdc`.** Do not edit `components/Layout.js.backup`. Do not edit anything under `lib/**` or `.github/**`. Do not touch `.cursor/rules/**`. - **Brief size discipline.** This brief is one component change, one new test, two devDeps. **Do NOT**: - Split the Layout god-component (`god-component-split` convoy owns that). - Migrate Layout off `lib/auth-context.js` / `lib/admin-auth.js` (Layout doesn't import either today; both legacy providers are queued for `single-auth-provider`). - Rename "Deck Hearth" or "DH" to "TCG Vault" (`pick-a-name` convoy). - Remove the dead `user` prop on `MobileNavigation` (deferred follow-up, see convoy file § "Anything flagged but not acted on"). - Touch `components/MobileNavigation.js` at all. - **Vitest test patterns.** Match `test/lib/permission-middleware.test.js`'s shape: `describe` block per behavior cluster, `vi.mock(...)` for module dependencies, plain `expect()` matchers (no jest-dom required). The new test uses a per-file `// @vitest-environment jsdom` directive at the top (vitest v3 supports this) so `vitest.config.js`'s global `environment: 'node'` does not need to change. ## Acceptance criteria ### `components/Layout.js` - [ ] **Change the default-prop on line 562.** Before: ```js export default function Layout({ children, user = { email: 'me@randallstillwell.com', role: 'user' }, showSearch = false }) { ``` After: ```js export default function Layout({ children, user = null, showSearch = false }) { ``` No other change to that line. - [ ] **Add a logged-out branch to `UserProfileDropdown`.** The branch MUST be placed **after** the existing `useState(false)` call (rules of hooks: hooks must be called in the same order every render — moving the early return above `useState` would throw "Rendered more hooks than during the previous render" the moment `user` flips from `null` to an object on a subsequent render). Verbatim shape — the implementer MAY adjust class names to match neighboring sidebar items, but every prop / behavior must be present: ```js function UserProfileDropdown({ user, onMobileMenuClose }) { // Hook order is fixed for both branches; do not move this below the // null-user early return — see rules-of-hooks (AGENTS.md Gotcha #11.5). const [isDropdownOpen, setIsDropdownOpen] = useState(false); // Logged-out: replace avatar + email + dropdown with a Sign-in CTA. if (!user) { return (