2025-07-29 15:19:48 -04:00
|
|
|
import { useState, useEffect } from 'react';
|
|
|
|
|
import { useRouter } from 'next/router';
|
|
|
|
|
import Link from 'next/link';
|
|
|
|
|
import Layout from '../../components/Layout';
|
2026-06-13 02:11:50 -04:00
|
|
|
import DeckDetailCardList from '../../components/DeckDetailCardList';
|
2026-06-13 01:44:16 -04:00
|
|
|
import DeckDetailStatsSidebar from '../../components/DeckDetailStatsSidebar';
|
refactor(design): site-wide sweep — broken Tailwind tokens, rounded corners, SearchBar primitive (#117) (#117)
Comprehensive design sweep across the rest of the app following the
shipped Liquid Glass + corner-border-light system (#116).
## Three classes of finding
### 1. Broken Tailwind token classes (HIGH — pages were unstyled)
The decks / deck-builder / deck-detail cluster relied on Tailwind
classes that don't exist in `tailwind.config.js` (no `bg-bg-*`,
`text-text-*`, `border-border`, `bg-accent-ember`,
`focus:ring-accent-ember`, `hover:bg-accent-ember-dark`). Those classes
produced ZERO CSS — backgrounds were transparent, borders invisible,
hover states absent.
Rewrote with inline `style={{ ... CSS vars ... }}` + the `<Button>` /
`<SearchBar>` primitives + `glass-panel` surfaces:
- `pages/decks.js` (full page)
- `pages/deck/[id].js` (header, stats sidebar, group-by controls,
card list)
- `pages/deck-builder.js` (loading spinner)
- `components/DeckBuilderView.js` (toolbar + main panel)
- `components/DeckBuilderCardBrowser.js` (full rewrite; integrated
`<SearchBar>` for the card-picker input)
- `components/DeckBuilderDeckList.js` (full rewrite)
- `components/DeckBuilderStatsBar.js`
- `components/ManaSymbolSettings.js`
- `components/ManaSymbols.js` (single `text-text-secondary`)
- `pages/admin/card-editor.js` cluster was already clean
### 2. Duplicative / stale page searches
Replaced raw `<input>` search controls with the `<SearchBar>` primitive
(adds clear button, ember focus ring, system-consistent rounded
corners). Kept page-specific filter searches (they filter the visible
list — distinct from the global TopSearchBar command palette):
- `pages/my-cards.js`
- `pages/community/collections.js`
- `components/CardsPageView.js`
- `components/CollectionPageView.js`
- `components/DeckBuilderCardBrowser.js`
`pages/my-cards.js` filter wrapper also lifted into a `glass-panel`
chip instead of a solid `var(--bg-primary)` band.
### 3. Square corners + stale palette in shared views
- `components/CollectionPageView.js`: 10 action buttons (`rounded-lg`
+ `hover:bg-gray-50`) → `rounded-xl` + `nav-item-hover`; 4 filter
selects (`focus:ring-purple-500 rounded-lg`) → `.input-field`;
view-mode toggle (`bg-white text-gray-900` — invisible in dark mode)
→ tokenised; SYSTEM badge gradient (`from-blue-500 to-purple-600`)
→ ember↔flame; tooltip (`bg-gray-900`) → `glass-panel-strong`;
search-results dropdown (`bg-white border-gray-200` — invisible in
dark mode) → `glass-panel-strong`; Activity / game-count /
TCG-game badges palette-aligned.
- `components/CardsPageView.js`: "Load More Cards" button
(`bg-gradient-to-r from-blue-500 to-purple-600 rounded-lg`) →
`<Button variant="primary" size="lg">`.
- `components/CollectionsPageView.js`: matching SYSTEM badge +
tooltip cleanup.
- `components/ShareModal.js`: user-search dropdown
(`border-gray-200 hover:bg-gray-50`) and email-invite card moved
onto `glass-panel` + `nav-item-hover`; social-share buttons
`rounded-lg hover:bg-gray-50` → `rounded-xl nav-item-hover`.
- `components/Layout.js`: profile-menu dropdown row
(`hover:bg-gray-50 dark:hover:bg-gray-700`) → `nav-item-hover`.
- `components/CardItem.js`: bulk-select checkbox
`focus:ring-purple-500` → ember.
### 4. `dark:` modifier classes (broken with `[data-theme]` theming)
This app uses `[data-theme="dark"]` CSS selector theming, not
Tailwind's `class` strategy, so `dark:bg-green-900/20` etc. produced
no CSS in dark mode. Affected alerts on `pages/settings.js` and
`pages/profile.js` — replaced with `glass-panel` + semantic border
colour (flame for success, #dc2626 for error).
`pages/settings.js` sidebar nav also moved off its hardcoded full-ember
fill onto the system `nav-item` / `nav-item-active` / `nav-item-hover`
pattern for consistency with the global sidebar.
## Verification
- `npm run build` — green (Next 16 + Turbopack)
- `npm run lint` — 0 errors, 1 unrelated pre-existing warning
- `npm run test:run` — 113/113 pass (no test changes needed)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-04 15:06:22 -04:00
|
|
|
import { Button } from '../../components/ui';
|
2026-06-13 01:44:16 -04:00
|
|
|
import { computeDeckStats } from '../../lib/deck-builder-stats.js';
|
|
|
|
|
import { groupDeckCards } from '../../lib/deck-detail-grouping.js';
|
|
|
|
|
import { getDeckFormatIcon } from '../../lib/deck-format-utils.js';
|
refactor(auth): collapse lib/auth-context.js + lib/admin-auth.js onto lib/use-auth.js (#31)
`lib/use-auth.js` is now the sole client-side auth surface (P1 §9 of
`.convoys/ship-readiness.md`). The legacy `lib/auth-context.js`
(`AuthProvider` + `useAuth`) and `lib/admin-auth.js` (`AdminProvider` +
`useAdmin` + `useIsAdmin`) are deleted; every importer is migrated to
the canonical hook. Pre-convoy a worst-case page mount issued THREE
identical `GET /api/auth/verify` requests (one per provider/hook); the
post-convoy floor is one verify per page mount (3 → 1 on
`pages/card/[id].js`, 2 → 1 elsewhere).
Importer inventory swept (7 source files):
- `pages/_app.js` — removed `<AuthProvider>` wrapper; `<ThemeProvider>`
is now the only top-level provider. `lib/use-auth.js` is hook-only,
no replacement provider needed.
- `pages/index.js`, `pages/scanner.js`, `pages/decks.js`,
`pages/deck/[id].js`, `pages/deck-builder.js` — `import { useAuth }`
path swap from `../lib/auth-context` to `../lib/use-auth`. All five
pages destructured only `{ user }` or `{ user, loading }`; verified
no consumer reads `login` / `register` from useAuth (those flows are
in `pages/login.js` / `pages/signup.js` which call the API directly),
so no shape-parity gap on `lib/use-auth.js`.
- `pages/card/[id].js` — replaced `useIsAdmin()` (the only consumer of
`lib/admin-auth.js` anywhere in the tree) with synchronous
`user?.role === 'admin'` derived from the existing `useAuth()` call.
Render condition at line 524 stays byte-identical.
Decisions documented in `.convoys/single-auth-provider.md`:
- D1: no extension to `lib/use-auth.js` (zero call sites for `login` /
`register` from useAuth — those flows are direct fetches in
`login.js` / `signup.js`).
- D2: `useIsAdmin()` collapses onto `useAuth()`; no separate hook.
- D3: provider tree `<ThemeProvider><AuthProvider>{children}</AuthProvider></ThemeProvider>`
→ `<ThemeProvider>{children}</ThemeProvider>`.
- D4: 3 → 1 verify roundtrip on `card/[id].js`; 2 → 1 on every other
page-load.
- D5: zero test files modified; the 21-test vitest suite is server-
side or prop-driven (`Layout.test.js` passes `user` as a prop, never
imports the legacy hooks).
Doc / config updates so the deletion lands cleanly:
- `.github/CODEOWNERS` — drop the two CODEOWNERS lines for the deleted
files.
- `AGENTS.md` § 2 architecture row + § 3 "Auth (client)" bullet —
rewritten for the post-convoy single-surface state.
- `.cursor/rules/auth-and-permissions.mdc` — § "Legacy" reframed to
"deleted by this convoy"; § "Authentication state on the client"
updated to the post-convoy `useAuth()` shape and the direct-fetch
login flow used by `login.js` / `signup.js`.
- `.cursor/rules/no-go-zones.mdc` — auth-refactors bullet drops the
deleted files from the canonical list.
- `.cursor/skills/add-page/SKILL.md` — checklist + anti-pattern row
refer to the deletion.
Verification:
- `rg "lib/auth-context|lib/admin-auth" --type js` → 0 hits in source.
- `npm run lint` → 128 → 125 problems (3 fewer errors from the deleted
unused-import lines; no regression).
- `npm run test:run` → 21/21 pass (including the 5 Layout regression
locks from `fix-layout-default-user`, which are prop-driven and
unaffected).
- `npm run build` → all 26 pages compile end-to-end; no SSR / static-
generation breakage that would have surfaced if a page tried to use
the legacy context hook unwrapped.
- Manual smoke deferred to operator post-merge per convoy doc.
Risks (full discussion in convoy file):
- R1 shape parity gap — verified zero consumers of legacy-only
surface; mitigated.
- R2 SSR mismatch from removing `<AuthProvider>` — `useEffect`-
guarded `localStorage` read; identical SSR shape pre/post; build
passes.
- R3 missed importer — post-delete grep + build pass would surface
any miss.
- R5 stale `useAuth` cache across components — pre-existing
pattern, called out as follow-up rather than addressed here.
Out of scope: any change to `lib/permission-middleware.js` (server-
side; resolved P0 #1), `lib/auth-secret.js` (resolved P0 #2),
`pages/api/**` route handlers, login / register API contracts, or
the seeded admin account flow.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-26 23:58:08 -04:00
|
|
|
import { useAuth } from '../../lib/use-auth';
|
2025-07-29 15:19:48 -04:00
|
|
|
|
refactor(design): site-wide sweep — broken Tailwind tokens, rounded corners, SearchBar primitive (#117) (#117)
Comprehensive design sweep across the rest of the app following the
shipped Liquid Glass + corner-border-light system (#116).
## Three classes of finding
### 1. Broken Tailwind token classes (HIGH — pages were unstyled)
The decks / deck-builder / deck-detail cluster relied on Tailwind
classes that don't exist in `tailwind.config.js` (no `bg-bg-*`,
`text-text-*`, `border-border`, `bg-accent-ember`,
`focus:ring-accent-ember`, `hover:bg-accent-ember-dark`). Those classes
produced ZERO CSS — backgrounds were transparent, borders invisible,
hover states absent.
Rewrote with inline `style={{ ... CSS vars ... }}` + the `<Button>` /
`<SearchBar>` primitives + `glass-panel` surfaces:
- `pages/decks.js` (full page)
- `pages/deck/[id].js` (header, stats sidebar, group-by controls,
card list)
- `pages/deck-builder.js` (loading spinner)
- `components/DeckBuilderView.js` (toolbar + main panel)
- `components/DeckBuilderCardBrowser.js` (full rewrite; integrated
`<SearchBar>` for the card-picker input)
- `components/DeckBuilderDeckList.js` (full rewrite)
- `components/DeckBuilderStatsBar.js`
- `components/ManaSymbolSettings.js`
- `components/ManaSymbols.js` (single `text-text-secondary`)
- `pages/admin/card-editor.js` cluster was already clean
### 2. Duplicative / stale page searches
Replaced raw `<input>` search controls with the `<SearchBar>` primitive
(adds clear button, ember focus ring, system-consistent rounded
corners). Kept page-specific filter searches (they filter the visible
list — distinct from the global TopSearchBar command palette):
- `pages/my-cards.js`
- `pages/community/collections.js`
- `components/CardsPageView.js`
- `components/CollectionPageView.js`
- `components/DeckBuilderCardBrowser.js`
`pages/my-cards.js` filter wrapper also lifted into a `glass-panel`
chip instead of a solid `var(--bg-primary)` band.
### 3. Square corners + stale palette in shared views
- `components/CollectionPageView.js`: 10 action buttons (`rounded-lg`
+ `hover:bg-gray-50`) → `rounded-xl` + `nav-item-hover`; 4 filter
selects (`focus:ring-purple-500 rounded-lg`) → `.input-field`;
view-mode toggle (`bg-white text-gray-900` — invisible in dark mode)
→ tokenised; SYSTEM badge gradient (`from-blue-500 to-purple-600`)
→ ember↔flame; tooltip (`bg-gray-900`) → `glass-panel-strong`;
search-results dropdown (`bg-white border-gray-200` — invisible in
dark mode) → `glass-panel-strong`; Activity / game-count /
TCG-game badges palette-aligned.
- `components/CardsPageView.js`: "Load More Cards" button
(`bg-gradient-to-r from-blue-500 to-purple-600 rounded-lg`) →
`<Button variant="primary" size="lg">`.
- `components/CollectionsPageView.js`: matching SYSTEM badge +
tooltip cleanup.
- `components/ShareModal.js`: user-search dropdown
(`border-gray-200 hover:bg-gray-50`) and email-invite card moved
onto `glass-panel` + `nav-item-hover`; social-share buttons
`rounded-lg hover:bg-gray-50` → `rounded-xl nav-item-hover`.
- `components/Layout.js`: profile-menu dropdown row
(`hover:bg-gray-50 dark:hover:bg-gray-700`) → `nav-item-hover`.
- `components/CardItem.js`: bulk-select checkbox
`focus:ring-purple-500` → ember.
### 4. `dark:` modifier classes (broken with `[data-theme]` theming)
This app uses `[data-theme="dark"]` CSS selector theming, not
Tailwind's `class` strategy, so `dark:bg-green-900/20` etc. produced
no CSS in dark mode. Affected alerts on `pages/settings.js` and
`pages/profile.js` — replaced with `glass-panel` + semantic border
colour (flame for success, #dc2626 for error).
`pages/settings.js` sidebar nav also moved off its hardcoded full-ember
fill onto the system `nav-item` / `nav-item-active` / `nav-item-hover`
pattern for consistency with the global sidebar.
## Verification
- `npm run build` — green (Next 16 + Turbopack)
- `npm run lint` — 0 errors, 1 unrelated pre-existing warning
- `npm run test:run` — 113/113 pass (no test changes needed)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-04 15:06:22 -04:00
|
|
|
/* 2026-06-04 design-sweep pass: this page used the broken Tailwind
|
|
|
|
|
token classes (bg-bg-*, text-text-*, bg-accent-ember,
|
|
|
|
|
hover:bg-accent-ember-dark, border-accent-ember) that don't
|
|
|
|
|
resolve in tailwind.config.js. Sweep replaces them with inline
|
|
|
|
|
CSS variables + Button primitive + glass-panel surface + the
|
|
|
|
|
nav-item-active / nav-item-hover utilities so the page renders
|
|
|
|
|
with the Liquid Glass design system. */
|
|
|
|
|
|
2025-07-29 15:19:48 -04:00
|
|
|
export default function DeckDetail() {
|
|
|
|
|
const { user } = useAuth();
|
|
|
|
|
const router = useRouter();
|
|
|
|
|
const { id: deckId } = router.query;
|
|
|
|
|
|
|
|
|
|
const [deck, setDeck] = useState(null);
|
|
|
|
|
const [loading, setLoading] = useState(true);
|
|
|
|
|
const [groupBy, setGroupBy] = useState('type');
|
|
|
|
|
|
|
|
|
|
const fetchDeck = async () => {
|
|
|
|
|
try {
|
|
|
|
|
const token = localStorage.getItem('auth_token');
|
|
|
|
|
const headers = token ? { 'Authorization': `Bearer ${token}` } : {};
|
|
|
|
|
|
|
|
|
|
const response = await fetch(`/api/decks/${deckId}`, { headers });
|
|
|
|
|
|
|
|
|
|
if (response.ok) {
|
|
|
|
|
const data = await response.json();
|
|
|
|
|
setDeck(data);
|
|
|
|
|
} else {
|
|
|
|
|
console.error('Failed to fetch deck');
|
|
|
|
|
router.push('/decks');
|
|
|
|
|
}
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('Error fetching deck:', error);
|
|
|
|
|
router.push('/decks');
|
|
|
|
|
} finally {
|
|
|
|
|
setLoading(false);
|
|
|
|
|
}
|
2026-06-02 02:03:39 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
if (deckId) {
|
|
|
|
|
// eslint-disable-next-line react-hooks/set-state-in-effect -- load deck when route id changes
|
|
|
|
|
fetchDeck();
|
|
|
|
|
}
|
|
|
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps -- refetch when route deck id changes
|
|
|
|
|
}, [deckId]);
|
|
|
|
|
|
2025-07-29 15:19:48 -04:00
|
|
|
if (loading) {
|
|
|
|
|
return (
|
fix(layout+pages): default user=null + page audit sweep (P0 #7) (#15)
* 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>
* architect(fix-layout-default-user): plan + briefs 1-2 (Layout fix + page audit)
2 briefs, single PR. ~12 files net (down from the 18 in the original scope —
10 of the 17 Layout-importing pages already pass user explicitly).
Brief 1: components/Layout.js default user=null + Sign-in CTA branch in
UserProfileDropdown when logged out. Adds first jsdom test in the repo
at test/components/Layout.test.js (Decision D2) with 5 regression-lock
assertions. devDeps: jsdom@^29, @testing-library/react@^16.
Brief 2: page audit sweep — 7 pages need code changes:
- Pass user={user} to Layout: scanner.js, deck-builder.js (×4),
deck/[id].js (×3), decks.js (×3)
- Replace page-level useState({email: 'me@...'}) → useState(null) +
null-guards: profile.js, settings.js
- Replace hardcoded const user = {email: 'me@...'} with useAuth():
card/[id].js
Discovered second anti-pattern: profile.js, settings.js, card/[id].js
seed page-level state with the maintainer email. Folded into Brief 2 since
success metric "no real email address remains in any component default-prop"
reads naturally to include page-level seed values.
Decisions:
A1 — Sign-in CTA replaces avatar+email+dropdown when user===null;
hides auth-only dropdown (Profile/Settings/Logout/Admin);
keeps public + community nav visible
B — Per-page bucket assignment (10 already correct, 7 need fix);
full per-page table with justification in convoy file
C2 — Two briefs in one PR (Brief 1 = Layout + test; Brief 2 = page
sweep depends on Brief 1). C1 buries the conceptual change under
mechanical edits; C3 is over-orchestrated for this scope
D2 — vitest lock-in; first jsdom test in repo; same negative-regression
style as test/lib/permission-middleware.test.js (synthetic-admin
shape). devDeps jsdom + @testing-library/react
Risks tracked R1-R8. Biggest: R2 (useState(null) null-deref in 3 leaky
pages — mitigated by audit-pass mandate + manual smoke).
MobileNavigation deliberately NOT folded in: its user prop is dead code
(never reads user.*); different bug class; cleanup queued separately to
avoid scope expansion.
Flagged-but-deferred:
- 4 pages still import useAuth from lib/auth-context.js
→ single-auth-provider (queued P1 #9)
- Layout headers still render "Deck Hearth" / "DH" branding
→ pick-a-name (queued P1 #12)
- MobileNavigation dead user prop → cleanup-mobile-nav-dead-props
or fold into god-component-split
addresses: P0 #7 from .convoys/ship-readiness.md (last P0 ship-blocker)
parent: ship-readiness
Co-authored-by: Cursor <cursoragent@cursor.com>
* feat(layout): default user=null + Sign-in CTA when logged out (Brief 1 of fix-layout-default-user)
Closes the source-side half of P0 #7 from .convoys/ship-readiness.md.
The page-side sweep (Brief 2) follows in a separate commit.
components/Layout.js:
- Default user prop is now null (was hardcoded to
{ email: 'me@randallstillwell.com', role: 'user' })
- UserProfileDropdown renders a "Sign in" link to /login when
user === null instead of the maintainer's email + auth-only menu
items (Decision A1)
- All user.* accesses guarded with optional chaining or null checks
- useState hook stays above the new null-user early return to satisfy
rules-of-hooks (boot-the-brief caught this on the first try;
see AGENTS.md Gotcha #11.5)
test/components/Layout.test.js (new):
- First jsdom test in the repo (Decision D2)
- 5 regression-lock assertions: no maintainer email ever rendered
(prop omitted, prop=null), Sign-in link exists with href=/login,
supplied email renders when prop is set, no "Guest" placeholder
(locks A1 copy choice)
- Mocks next/link, next/router (prefetch, replace, events, query),
and theme-context.useTheme for jsdom safety under Next 16
package.json + package-lock.json:
- Add jsdom@^29 and @testing-library/react@^16 to devDependencies
- @testing-library/dom@^10 added explicitly (peer auto-install
skipped it under npm 11; brief anticipated this fallback)
vitest.config.js (deviation from brief — see PR description):
- Add esbuild { loader: 'jsx', jsx: 'automatic' } so vitest can
parse JSX in .js files. Required to import any React component
written in the repo's Next.js pages-router .js convention
(AGENTS.md Gotcha #9). The brief said "no change" to this file,
but JSX-in-.js parsing is a hard prerequisite for the new test
to import components/Layout.js — the alternatives (rename test
to .test.jsx; rewrite test in React.createElement) either break
the test glob or still hit the same Layout.js parse failure.
Other tests are unaffected (they import non-JSX modules).
Smoke output: see PR description.
addresses: P0 #7 from .convoys/ship-readiness.md (last P0 ship-blocker)
Co-authored-by: Cursor <cursoragent@cursor.com>
* feat(pages): pass user explicitly + null-guard leaky page seeds (Brief 2 of fix-layout-default-user)
Closes the page-side half of P0 #7 from .convoys/ship-readiness.md.
Brief 1 (commit ddf8fd2) handled the Layout-side fix.
Per the architect's per-page bucket table (Decision B in
.convoys/fix-layout-default-user.md), 7 pages needed code changes;
the other 10 of 17 Layout-importing pages already pass `user` correctly.
Pass user={user} to Layout (4 pages, 11 call sites):
- pages/scanner.js (1 call)
- pages/decks.js (3 calls)
- pages/deck-builder.js (4 calls)
- pages/deck/[id].js (3 calls)
(All four still import useAuth from lib/auth-context.js — that's
intentional and stays as-is until the single-auth-provider convoy
collapses the three parallel auth surfaces.)
Replace leaky page-level seed values with useState(null) + null guards
(2 pages, R2 mitigation):
- pages/profile.js: useState({email: 'me@...', role: 'user', ...})
→ useState(null) + ?. on every sync user.* read
+ early-return guards in getDisplayName/getInitials
+ conditional render around the "Member since" block
so formatDate(undefined) never runs
- pages/settings.js: same pattern (single user.email reader guarded)
Replace hardcoded const with useAuth from lib/use-auth.js (1 page):
- pages/card/[id].js: const user = {email: 'me@...'}
→ const { user } = useAuth() (called unconditionally
at the top of the component; rules-of-hooks safe)
Verification:
- grep 'me@randallstillwell.com' pages/ → 0 hits
- 21/21 vitest tests pass (16 pre-existing + 5 from Brief 1)
- npm run lint matches baseline (128 problems pre, 128 post; verified
via git stash before/after)
- Manual static read-through of every diff; ReadLints clean on the 7
files
- Dev-server smoke: /cards anonymous returned HTTP 200 with 0
'me@randallstillwell' matches before the user's shared dev server
became unresponsive mid-session (same dev-server-shared-by-user
constraint flagged in Brief 1); interactive logged-in smoke is
parent/operator gated
Flagged-but-deferred (untouched per scope):
- 4 pages still import useAuth from lib/auth-context.js
→ single-auth-provider (queued P1 #9)
- components/MobileNavigation.js still receives dead user prop
→ cleanup-mobile-nav-dead-props (or fold into god-component-split)
addresses: P0 #7 from .convoys/ship-readiness.md (last P0 ship-blocker)
Co-authored-by: Cursor <cursoragent@cursor.com>
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-24 15:31:37 -04:00
|
|
|
<Layout user={user}>
|
2025-07-29 15:19:48 -04:00
|
|
|
<div className="flex items-center justify-center min-h-screen">
|
refactor(design): site-wide sweep — broken Tailwind tokens, rounded corners, SearchBar primitive (#117) (#117)
Comprehensive design sweep across the rest of the app following the
shipped Liquid Glass + corner-border-light system (#116).
## Three classes of finding
### 1. Broken Tailwind token classes (HIGH — pages were unstyled)
The decks / deck-builder / deck-detail cluster relied on Tailwind
classes that don't exist in `tailwind.config.js` (no `bg-bg-*`,
`text-text-*`, `border-border`, `bg-accent-ember`,
`focus:ring-accent-ember`, `hover:bg-accent-ember-dark`). Those classes
produced ZERO CSS — backgrounds were transparent, borders invisible,
hover states absent.
Rewrote with inline `style={{ ... CSS vars ... }}` + the `<Button>` /
`<SearchBar>` primitives + `glass-panel` surfaces:
- `pages/decks.js` (full page)
- `pages/deck/[id].js` (header, stats sidebar, group-by controls,
card list)
- `pages/deck-builder.js` (loading spinner)
- `components/DeckBuilderView.js` (toolbar + main panel)
- `components/DeckBuilderCardBrowser.js` (full rewrite; integrated
`<SearchBar>` for the card-picker input)
- `components/DeckBuilderDeckList.js` (full rewrite)
- `components/DeckBuilderStatsBar.js`
- `components/ManaSymbolSettings.js`
- `components/ManaSymbols.js` (single `text-text-secondary`)
- `pages/admin/card-editor.js` cluster was already clean
### 2. Duplicative / stale page searches
Replaced raw `<input>` search controls with the `<SearchBar>` primitive
(adds clear button, ember focus ring, system-consistent rounded
corners). Kept page-specific filter searches (they filter the visible
list — distinct from the global TopSearchBar command palette):
- `pages/my-cards.js`
- `pages/community/collections.js`
- `components/CardsPageView.js`
- `components/CollectionPageView.js`
- `components/DeckBuilderCardBrowser.js`
`pages/my-cards.js` filter wrapper also lifted into a `glass-panel`
chip instead of a solid `var(--bg-primary)` band.
### 3. Square corners + stale palette in shared views
- `components/CollectionPageView.js`: 10 action buttons (`rounded-lg`
+ `hover:bg-gray-50`) → `rounded-xl` + `nav-item-hover`; 4 filter
selects (`focus:ring-purple-500 rounded-lg`) → `.input-field`;
view-mode toggle (`bg-white text-gray-900` — invisible in dark mode)
→ tokenised; SYSTEM badge gradient (`from-blue-500 to-purple-600`)
→ ember↔flame; tooltip (`bg-gray-900`) → `glass-panel-strong`;
search-results dropdown (`bg-white border-gray-200` — invisible in
dark mode) → `glass-panel-strong`; Activity / game-count /
TCG-game badges palette-aligned.
- `components/CardsPageView.js`: "Load More Cards" button
(`bg-gradient-to-r from-blue-500 to-purple-600 rounded-lg`) →
`<Button variant="primary" size="lg">`.
- `components/CollectionsPageView.js`: matching SYSTEM badge +
tooltip cleanup.
- `components/ShareModal.js`: user-search dropdown
(`border-gray-200 hover:bg-gray-50`) and email-invite card moved
onto `glass-panel` + `nav-item-hover`; social-share buttons
`rounded-lg hover:bg-gray-50` → `rounded-xl nav-item-hover`.
- `components/Layout.js`: profile-menu dropdown row
(`hover:bg-gray-50 dark:hover:bg-gray-700`) → `nav-item-hover`.
- `components/CardItem.js`: bulk-select checkbox
`focus:ring-purple-500` → ember.
### 4. `dark:` modifier classes (broken with `[data-theme]` theming)
This app uses `[data-theme="dark"]` CSS selector theming, not
Tailwind's `class` strategy, so `dark:bg-green-900/20` etc. produced
no CSS in dark mode. Affected alerts on `pages/settings.js` and
`pages/profile.js` — replaced with `glass-panel` + semantic border
colour (flame for success, #dc2626 for error).
`pages/settings.js` sidebar nav also moved off its hardcoded full-ember
fill onto the system `nav-item` / `nav-item-active` / `nav-item-hover`
pattern for consistency with the global sidebar.
## Verification
- `npm run build` — green (Next 16 + Turbopack)
- `npm run lint` — 0 errors, 1 unrelated pre-existing warning
- `npm run test:run` — 113/113 pass (no test changes needed)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-04 15:06:22 -04:00
|
|
|
<div
|
|
|
|
|
className="animate-spin rounded-full h-32 w-32 border-b-2"
|
|
|
|
|
style={{ borderColor: 'var(--accent-ember)' }}
|
|
|
|
|
/>
|
2025-07-29 15:19:48 -04:00
|
|
|
</div>
|
|
|
|
|
</Layout>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!deck) {
|
|
|
|
|
return (
|
fix(layout+pages): default user=null + page audit sweep (P0 #7) (#15)
* 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>
* architect(fix-layout-default-user): plan + briefs 1-2 (Layout fix + page audit)
2 briefs, single PR. ~12 files net (down from the 18 in the original scope —
10 of the 17 Layout-importing pages already pass user explicitly).
Brief 1: components/Layout.js default user=null + Sign-in CTA branch in
UserProfileDropdown when logged out. Adds first jsdom test in the repo
at test/components/Layout.test.js (Decision D2) with 5 regression-lock
assertions. devDeps: jsdom@^29, @testing-library/react@^16.
Brief 2: page audit sweep — 7 pages need code changes:
- Pass user={user} to Layout: scanner.js, deck-builder.js (×4),
deck/[id].js (×3), decks.js (×3)
- Replace page-level useState({email: 'me@...'}) → useState(null) +
null-guards: profile.js, settings.js
- Replace hardcoded const user = {email: 'me@...'} with useAuth():
card/[id].js
Discovered second anti-pattern: profile.js, settings.js, card/[id].js
seed page-level state with the maintainer email. Folded into Brief 2 since
success metric "no real email address remains in any component default-prop"
reads naturally to include page-level seed values.
Decisions:
A1 — Sign-in CTA replaces avatar+email+dropdown when user===null;
hides auth-only dropdown (Profile/Settings/Logout/Admin);
keeps public + community nav visible
B — Per-page bucket assignment (10 already correct, 7 need fix);
full per-page table with justification in convoy file
C2 — Two briefs in one PR (Brief 1 = Layout + test; Brief 2 = page
sweep depends on Brief 1). C1 buries the conceptual change under
mechanical edits; C3 is over-orchestrated for this scope
D2 — vitest lock-in; first jsdom test in repo; same negative-regression
style as test/lib/permission-middleware.test.js (synthetic-admin
shape). devDeps jsdom + @testing-library/react
Risks tracked R1-R8. Biggest: R2 (useState(null) null-deref in 3 leaky
pages — mitigated by audit-pass mandate + manual smoke).
MobileNavigation deliberately NOT folded in: its user prop is dead code
(never reads user.*); different bug class; cleanup queued separately to
avoid scope expansion.
Flagged-but-deferred:
- 4 pages still import useAuth from lib/auth-context.js
→ single-auth-provider (queued P1 #9)
- Layout headers still render "Deck Hearth" / "DH" branding
→ pick-a-name (queued P1 #12)
- MobileNavigation dead user prop → cleanup-mobile-nav-dead-props
or fold into god-component-split
addresses: P0 #7 from .convoys/ship-readiness.md (last P0 ship-blocker)
parent: ship-readiness
Co-authored-by: Cursor <cursoragent@cursor.com>
* feat(layout): default user=null + Sign-in CTA when logged out (Brief 1 of fix-layout-default-user)
Closes the source-side half of P0 #7 from .convoys/ship-readiness.md.
The page-side sweep (Brief 2) follows in a separate commit.
components/Layout.js:
- Default user prop is now null (was hardcoded to
{ email: 'me@randallstillwell.com', role: 'user' })
- UserProfileDropdown renders a "Sign in" link to /login when
user === null instead of the maintainer's email + auth-only menu
items (Decision A1)
- All user.* accesses guarded with optional chaining or null checks
- useState hook stays above the new null-user early return to satisfy
rules-of-hooks (boot-the-brief caught this on the first try;
see AGENTS.md Gotcha #11.5)
test/components/Layout.test.js (new):
- First jsdom test in the repo (Decision D2)
- 5 regression-lock assertions: no maintainer email ever rendered
(prop omitted, prop=null), Sign-in link exists with href=/login,
supplied email renders when prop is set, no "Guest" placeholder
(locks A1 copy choice)
- Mocks next/link, next/router (prefetch, replace, events, query),
and theme-context.useTheme for jsdom safety under Next 16
package.json + package-lock.json:
- Add jsdom@^29 and @testing-library/react@^16 to devDependencies
- @testing-library/dom@^10 added explicitly (peer auto-install
skipped it under npm 11; brief anticipated this fallback)
vitest.config.js (deviation from brief — see PR description):
- Add esbuild { loader: 'jsx', jsx: 'automatic' } so vitest can
parse JSX in .js files. Required to import any React component
written in the repo's Next.js pages-router .js convention
(AGENTS.md Gotcha #9). The brief said "no change" to this file,
but JSX-in-.js parsing is a hard prerequisite for the new test
to import components/Layout.js — the alternatives (rename test
to .test.jsx; rewrite test in React.createElement) either break
the test glob or still hit the same Layout.js parse failure.
Other tests are unaffected (they import non-JSX modules).
Smoke output: see PR description.
addresses: P0 #7 from .convoys/ship-readiness.md (last P0 ship-blocker)
Co-authored-by: Cursor <cursoragent@cursor.com>
* feat(pages): pass user explicitly + null-guard leaky page seeds (Brief 2 of fix-layout-default-user)
Closes the page-side half of P0 #7 from .convoys/ship-readiness.md.
Brief 1 (commit ddf8fd2) handled the Layout-side fix.
Per the architect's per-page bucket table (Decision B in
.convoys/fix-layout-default-user.md), 7 pages needed code changes;
the other 10 of 17 Layout-importing pages already pass `user` correctly.
Pass user={user} to Layout (4 pages, 11 call sites):
- pages/scanner.js (1 call)
- pages/decks.js (3 calls)
- pages/deck-builder.js (4 calls)
- pages/deck/[id].js (3 calls)
(All four still import useAuth from lib/auth-context.js — that's
intentional and stays as-is until the single-auth-provider convoy
collapses the three parallel auth surfaces.)
Replace leaky page-level seed values with useState(null) + null guards
(2 pages, R2 mitigation):
- pages/profile.js: useState({email: 'me@...', role: 'user', ...})
→ useState(null) + ?. on every sync user.* read
+ early-return guards in getDisplayName/getInitials
+ conditional render around the "Member since" block
so formatDate(undefined) never runs
- pages/settings.js: same pattern (single user.email reader guarded)
Replace hardcoded const with useAuth from lib/use-auth.js (1 page):
- pages/card/[id].js: const user = {email: 'me@...'}
→ const { user } = useAuth() (called unconditionally
at the top of the component; rules-of-hooks safe)
Verification:
- grep 'me@randallstillwell.com' pages/ → 0 hits
- 21/21 vitest tests pass (16 pre-existing + 5 from Brief 1)
- npm run lint matches baseline (128 problems pre, 128 post; verified
via git stash before/after)
- Manual static read-through of every diff; ReadLints clean on the 7
files
- Dev-server smoke: /cards anonymous returned HTTP 200 with 0
'me@randallstillwell' matches before the user's shared dev server
became unresponsive mid-session (same dev-server-shared-by-user
constraint flagged in Brief 1); interactive logged-in smoke is
parent/operator gated
Flagged-but-deferred (untouched per scope):
- 4 pages still import useAuth from lib/auth-context.js
→ single-auth-provider (queued P1 #9)
- components/MobileNavigation.js still receives dead user prop
→ cleanup-mobile-nav-dead-props (or fold into god-component-split)
addresses: P0 #7 from .convoys/ship-readiness.md (last P0 ship-blocker)
Co-authored-by: Cursor <cursoragent@cursor.com>
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-24 15:31:37 -04:00
|
|
|
<Layout user={user}>
|
2025-07-29 15:19:48 -04:00
|
|
|
<div className="flex items-center justify-center min-h-screen">
|
|
|
|
|
<div className="text-center">
|
refactor(design): site-wide sweep — broken Tailwind tokens, rounded corners, SearchBar primitive (#117) (#117)
Comprehensive design sweep across the rest of the app following the
shipped Liquid Glass + corner-border-light system (#116).
## Three classes of finding
### 1. Broken Tailwind token classes (HIGH — pages were unstyled)
The decks / deck-builder / deck-detail cluster relied on Tailwind
classes that don't exist in `tailwind.config.js` (no `bg-bg-*`,
`text-text-*`, `border-border`, `bg-accent-ember`,
`focus:ring-accent-ember`, `hover:bg-accent-ember-dark`). Those classes
produced ZERO CSS — backgrounds were transparent, borders invisible,
hover states absent.
Rewrote with inline `style={{ ... CSS vars ... }}` + the `<Button>` /
`<SearchBar>` primitives + `glass-panel` surfaces:
- `pages/decks.js` (full page)
- `pages/deck/[id].js` (header, stats sidebar, group-by controls,
card list)
- `pages/deck-builder.js` (loading spinner)
- `components/DeckBuilderView.js` (toolbar + main panel)
- `components/DeckBuilderCardBrowser.js` (full rewrite; integrated
`<SearchBar>` for the card-picker input)
- `components/DeckBuilderDeckList.js` (full rewrite)
- `components/DeckBuilderStatsBar.js`
- `components/ManaSymbolSettings.js`
- `components/ManaSymbols.js` (single `text-text-secondary`)
- `pages/admin/card-editor.js` cluster was already clean
### 2. Duplicative / stale page searches
Replaced raw `<input>` search controls with the `<SearchBar>` primitive
(adds clear button, ember focus ring, system-consistent rounded
corners). Kept page-specific filter searches (they filter the visible
list — distinct from the global TopSearchBar command palette):
- `pages/my-cards.js`
- `pages/community/collections.js`
- `components/CardsPageView.js`
- `components/CollectionPageView.js`
- `components/DeckBuilderCardBrowser.js`
`pages/my-cards.js` filter wrapper also lifted into a `glass-panel`
chip instead of a solid `var(--bg-primary)` band.
### 3. Square corners + stale palette in shared views
- `components/CollectionPageView.js`: 10 action buttons (`rounded-lg`
+ `hover:bg-gray-50`) → `rounded-xl` + `nav-item-hover`; 4 filter
selects (`focus:ring-purple-500 rounded-lg`) → `.input-field`;
view-mode toggle (`bg-white text-gray-900` — invisible in dark mode)
→ tokenised; SYSTEM badge gradient (`from-blue-500 to-purple-600`)
→ ember↔flame; tooltip (`bg-gray-900`) → `glass-panel-strong`;
search-results dropdown (`bg-white border-gray-200` — invisible in
dark mode) → `glass-panel-strong`; Activity / game-count /
TCG-game badges palette-aligned.
- `components/CardsPageView.js`: "Load More Cards" button
(`bg-gradient-to-r from-blue-500 to-purple-600 rounded-lg`) →
`<Button variant="primary" size="lg">`.
- `components/CollectionsPageView.js`: matching SYSTEM badge +
tooltip cleanup.
- `components/ShareModal.js`: user-search dropdown
(`border-gray-200 hover:bg-gray-50`) and email-invite card moved
onto `glass-panel` + `nav-item-hover`; social-share buttons
`rounded-lg hover:bg-gray-50` → `rounded-xl nav-item-hover`.
- `components/Layout.js`: profile-menu dropdown row
(`hover:bg-gray-50 dark:hover:bg-gray-700`) → `nav-item-hover`.
- `components/CardItem.js`: bulk-select checkbox
`focus:ring-purple-500` → ember.
### 4. `dark:` modifier classes (broken with `[data-theme]` theming)
This app uses `[data-theme="dark"]` CSS selector theming, not
Tailwind's `class` strategy, so `dark:bg-green-900/20` etc. produced
no CSS in dark mode. Affected alerts on `pages/settings.js` and
`pages/profile.js` — replaced with `glass-panel` + semantic border
colour (flame for success, #dc2626 for error).
`pages/settings.js` sidebar nav also moved off its hardcoded full-ember
fill onto the system `nav-item` / `nav-item-active` / `nav-item-hover`
pattern for consistency with the global sidebar.
## Verification
- `npm run build` — green (Next 16 + Turbopack)
- `npm run lint` — 0 errors, 1 unrelated pre-existing warning
- `npm run test:run` — 113/113 pass (no test changes needed)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-04 15:06:22 -04:00
|
|
|
<h1
|
|
|
|
|
className="text-2xl font-bold mb-4"
|
|
|
|
|
style={{ color: 'var(--text-primary)' }}
|
|
|
|
|
>
|
|
|
|
|
Deck not found
|
|
|
|
|
</h1>
|
|
|
|
|
<Link
|
|
|
|
|
href="/decks"
|
|
|
|
|
className="hover:underline"
|
|
|
|
|
style={{ color: 'var(--accent-ember)' }}
|
|
|
|
|
>
|
2025-07-29 15:19:48 -04:00
|
|
|
Back to Decks
|
|
|
|
|
</Link>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</Layout>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-13 01:44:16 -04:00
|
|
|
const stats = computeDeckStats(deck.cards || []);
|
|
|
|
|
const groupedCards = groupDeckCards(deck.cards, groupBy);
|
2025-07-29 15:19:48 -04:00
|
|
|
const isOwner = user && deck.user_id === user.userId;
|
|
|
|
|
|
|
|
|
|
return (
|
fix(layout+pages): default user=null + page audit sweep (P0 #7) (#15)
* 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>
* architect(fix-layout-default-user): plan + briefs 1-2 (Layout fix + page audit)
2 briefs, single PR. ~12 files net (down from the 18 in the original scope —
10 of the 17 Layout-importing pages already pass user explicitly).
Brief 1: components/Layout.js default user=null + Sign-in CTA branch in
UserProfileDropdown when logged out. Adds first jsdom test in the repo
at test/components/Layout.test.js (Decision D2) with 5 regression-lock
assertions. devDeps: jsdom@^29, @testing-library/react@^16.
Brief 2: page audit sweep — 7 pages need code changes:
- Pass user={user} to Layout: scanner.js, deck-builder.js (×4),
deck/[id].js (×3), decks.js (×3)
- Replace page-level useState({email: 'me@...'}) → useState(null) +
null-guards: profile.js, settings.js
- Replace hardcoded const user = {email: 'me@...'} with useAuth():
card/[id].js
Discovered second anti-pattern: profile.js, settings.js, card/[id].js
seed page-level state with the maintainer email. Folded into Brief 2 since
success metric "no real email address remains in any component default-prop"
reads naturally to include page-level seed values.
Decisions:
A1 — Sign-in CTA replaces avatar+email+dropdown when user===null;
hides auth-only dropdown (Profile/Settings/Logout/Admin);
keeps public + community nav visible
B — Per-page bucket assignment (10 already correct, 7 need fix);
full per-page table with justification in convoy file
C2 — Two briefs in one PR (Brief 1 = Layout + test; Brief 2 = page
sweep depends on Brief 1). C1 buries the conceptual change under
mechanical edits; C3 is over-orchestrated for this scope
D2 — vitest lock-in; first jsdom test in repo; same negative-regression
style as test/lib/permission-middleware.test.js (synthetic-admin
shape). devDeps jsdom + @testing-library/react
Risks tracked R1-R8. Biggest: R2 (useState(null) null-deref in 3 leaky
pages — mitigated by audit-pass mandate + manual smoke).
MobileNavigation deliberately NOT folded in: its user prop is dead code
(never reads user.*); different bug class; cleanup queued separately to
avoid scope expansion.
Flagged-but-deferred:
- 4 pages still import useAuth from lib/auth-context.js
→ single-auth-provider (queued P1 #9)
- Layout headers still render "Deck Hearth" / "DH" branding
→ pick-a-name (queued P1 #12)
- MobileNavigation dead user prop → cleanup-mobile-nav-dead-props
or fold into god-component-split
addresses: P0 #7 from .convoys/ship-readiness.md (last P0 ship-blocker)
parent: ship-readiness
Co-authored-by: Cursor <cursoragent@cursor.com>
* feat(layout): default user=null + Sign-in CTA when logged out (Brief 1 of fix-layout-default-user)
Closes the source-side half of P0 #7 from .convoys/ship-readiness.md.
The page-side sweep (Brief 2) follows in a separate commit.
components/Layout.js:
- Default user prop is now null (was hardcoded to
{ email: 'me@randallstillwell.com', role: 'user' })
- UserProfileDropdown renders a "Sign in" link to /login when
user === null instead of the maintainer's email + auth-only menu
items (Decision A1)
- All user.* accesses guarded with optional chaining or null checks
- useState hook stays above the new null-user early return to satisfy
rules-of-hooks (boot-the-brief caught this on the first try;
see AGENTS.md Gotcha #11.5)
test/components/Layout.test.js (new):
- First jsdom test in the repo (Decision D2)
- 5 regression-lock assertions: no maintainer email ever rendered
(prop omitted, prop=null), Sign-in link exists with href=/login,
supplied email renders when prop is set, no "Guest" placeholder
(locks A1 copy choice)
- Mocks next/link, next/router (prefetch, replace, events, query),
and theme-context.useTheme for jsdom safety under Next 16
package.json + package-lock.json:
- Add jsdom@^29 and @testing-library/react@^16 to devDependencies
- @testing-library/dom@^10 added explicitly (peer auto-install
skipped it under npm 11; brief anticipated this fallback)
vitest.config.js (deviation from brief — see PR description):
- Add esbuild { loader: 'jsx', jsx: 'automatic' } so vitest can
parse JSX in .js files. Required to import any React component
written in the repo's Next.js pages-router .js convention
(AGENTS.md Gotcha #9). The brief said "no change" to this file,
but JSX-in-.js parsing is a hard prerequisite for the new test
to import components/Layout.js — the alternatives (rename test
to .test.jsx; rewrite test in React.createElement) either break
the test glob or still hit the same Layout.js parse failure.
Other tests are unaffected (they import non-JSX modules).
Smoke output: see PR description.
addresses: P0 #7 from .convoys/ship-readiness.md (last P0 ship-blocker)
Co-authored-by: Cursor <cursoragent@cursor.com>
* feat(pages): pass user explicitly + null-guard leaky page seeds (Brief 2 of fix-layout-default-user)
Closes the page-side half of P0 #7 from .convoys/ship-readiness.md.
Brief 1 (commit ddf8fd2) handled the Layout-side fix.
Per the architect's per-page bucket table (Decision B in
.convoys/fix-layout-default-user.md), 7 pages needed code changes;
the other 10 of 17 Layout-importing pages already pass `user` correctly.
Pass user={user} to Layout (4 pages, 11 call sites):
- pages/scanner.js (1 call)
- pages/decks.js (3 calls)
- pages/deck-builder.js (4 calls)
- pages/deck/[id].js (3 calls)
(All four still import useAuth from lib/auth-context.js — that's
intentional and stays as-is until the single-auth-provider convoy
collapses the three parallel auth surfaces.)
Replace leaky page-level seed values with useState(null) + null guards
(2 pages, R2 mitigation):
- pages/profile.js: useState({email: 'me@...', role: 'user', ...})
→ useState(null) + ?. on every sync user.* read
+ early-return guards in getDisplayName/getInitials
+ conditional render around the "Member since" block
so formatDate(undefined) never runs
- pages/settings.js: same pattern (single user.email reader guarded)
Replace hardcoded const with useAuth from lib/use-auth.js (1 page):
- pages/card/[id].js: const user = {email: 'me@...'}
→ const { user } = useAuth() (called unconditionally
at the top of the component; rules-of-hooks safe)
Verification:
- grep 'me@randallstillwell.com' pages/ → 0 hits
- 21/21 vitest tests pass (16 pre-existing + 5 from Brief 1)
- npm run lint matches baseline (128 problems pre, 128 post; verified
via git stash before/after)
- Manual static read-through of every diff; ReadLints clean on the 7
files
- Dev-server smoke: /cards anonymous returned HTTP 200 with 0
'me@randallstillwell' matches before the user's shared dev server
became unresponsive mid-session (same dev-server-shared-by-user
constraint flagged in Brief 1); interactive logged-in smoke is
parent/operator gated
Flagged-but-deferred (untouched per scope):
- 4 pages still import useAuth from lib/auth-context.js
→ single-auth-provider (queued P1 #9)
- components/MobileNavigation.js still receives dead user prop
→ cleanup-mobile-nav-dead-props (or fold into god-component-split)
addresses: P0 #7 from .convoys/ship-readiness.md (last P0 ship-blocker)
Co-authored-by: Cursor <cursoragent@cursor.com>
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-24 15:31:37 -04:00
|
|
|
<Layout user={user}>
|
2025-07-29 15:19:48 -04:00
|
|
|
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
|
|
|
|
{/* Header */}
|
|
|
|
|
<div className="flex justify-between items-start mb-8">
|
|
|
|
|
<div>
|
|
|
|
|
<div className="flex items-center space-x-3 mb-2">
|
refactor(design): site-wide sweep — broken Tailwind tokens, rounded corners, SearchBar primitive (#117) (#117)
Comprehensive design sweep across the rest of the app following the
shipped Liquid Glass + corner-border-light system (#116).
## Three classes of finding
### 1. Broken Tailwind token classes (HIGH — pages were unstyled)
The decks / deck-builder / deck-detail cluster relied on Tailwind
classes that don't exist in `tailwind.config.js` (no `bg-bg-*`,
`text-text-*`, `border-border`, `bg-accent-ember`,
`focus:ring-accent-ember`, `hover:bg-accent-ember-dark`). Those classes
produced ZERO CSS — backgrounds were transparent, borders invisible,
hover states absent.
Rewrote with inline `style={{ ... CSS vars ... }}` + the `<Button>` /
`<SearchBar>` primitives + `glass-panel` surfaces:
- `pages/decks.js` (full page)
- `pages/deck/[id].js` (header, stats sidebar, group-by controls,
card list)
- `pages/deck-builder.js` (loading spinner)
- `components/DeckBuilderView.js` (toolbar + main panel)
- `components/DeckBuilderCardBrowser.js` (full rewrite; integrated
`<SearchBar>` for the card-picker input)
- `components/DeckBuilderDeckList.js` (full rewrite)
- `components/DeckBuilderStatsBar.js`
- `components/ManaSymbolSettings.js`
- `components/ManaSymbols.js` (single `text-text-secondary`)
- `pages/admin/card-editor.js` cluster was already clean
### 2. Duplicative / stale page searches
Replaced raw `<input>` search controls with the `<SearchBar>` primitive
(adds clear button, ember focus ring, system-consistent rounded
corners). Kept page-specific filter searches (they filter the visible
list — distinct from the global TopSearchBar command palette):
- `pages/my-cards.js`
- `pages/community/collections.js`
- `components/CardsPageView.js`
- `components/CollectionPageView.js`
- `components/DeckBuilderCardBrowser.js`
`pages/my-cards.js` filter wrapper also lifted into a `glass-panel`
chip instead of a solid `var(--bg-primary)` band.
### 3. Square corners + stale palette in shared views
- `components/CollectionPageView.js`: 10 action buttons (`rounded-lg`
+ `hover:bg-gray-50`) → `rounded-xl` + `nav-item-hover`; 4 filter
selects (`focus:ring-purple-500 rounded-lg`) → `.input-field`;
view-mode toggle (`bg-white text-gray-900` — invisible in dark mode)
→ tokenised; SYSTEM badge gradient (`from-blue-500 to-purple-600`)
→ ember↔flame; tooltip (`bg-gray-900`) → `glass-panel-strong`;
search-results dropdown (`bg-white border-gray-200` — invisible in
dark mode) → `glass-panel-strong`; Activity / game-count /
TCG-game badges palette-aligned.
- `components/CardsPageView.js`: "Load More Cards" button
(`bg-gradient-to-r from-blue-500 to-purple-600 rounded-lg`) →
`<Button variant="primary" size="lg">`.
- `components/CollectionsPageView.js`: matching SYSTEM badge +
tooltip cleanup.
- `components/ShareModal.js`: user-search dropdown
(`border-gray-200 hover:bg-gray-50`) and email-invite card moved
onto `glass-panel` + `nav-item-hover`; social-share buttons
`rounded-lg hover:bg-gray-50` → `rounded-xl nav-item-hover`.
- `components/Layout.js`: profile-menu dropdown row
(`hover:bg-gray-50 dark:hover:bg-gray-700`) → `nav-item-hover`.
- `components/CardItem.js`: bulk-select checkbox
`focus:ring-purple-500` → ember.
### 4. `dark:` modifier classes (broken with `[data-theme]` theming)
This app uses `[data-theme="dark"]` CSS selector theming, not
Tailwind's `class` strategy, so `dark:bg-green-900/20` etc. produced
no CSS in dark mode. Affected alerts on `pages/settings.js` and
`pages/profile.js` — replaced with `glass-panel` + semantic border
colour (flame for success, #dc2626 for error).
`pages/settings.js` sidebar nav also moved off its hardcoded full-ember
fill onto the system `nav-item` / `nav-item-active` / `nav-item-hover`
pattern for consistency with the global sidebar.
## Verification
- `npm run build` — green (Next 16 + Turbopack)
- `npm run lint` — 0 errors, 1 unrelated pre-existing warning
- `npm run test:run` — 113/113 pass (no test changes needed)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-04 15:06:22 -04:00
|
|
|
<Link
|
|
|
|
|
href="/decks"
|
|
|
|
|
className="hover:underline"
|
|
|
|
|
style={{ color: 'var(--accent-ember)' }}
|
|
|
|
|
>
|
2025-07-29 15:19:48 -04:00
|
|
|
← Back to Decks
|
|
|
|
|
</Link>
|
|
|
|
|
</div>
|
|
|
|
|
<div className="flex items-center space-x-3 mb-2">
|
2026-06-13 01:44:16 -04:00
|
|
|
<span className="text-3xl">{getDeckFormatIcon(deck.format)}</span>
|
refactor(design): site-wide sweep — broken Tailwind tokens, rounded corners, SearchBar primitive (#117) (#117)
Comprehensive design sweep across the rest of the app following the
shipped Liquid Glass + corner-border-light system (#116).
## Three classes of finding
### 1. Broken Tailwind token classes (HIGH — pages were unstyled)
The decks / deck-builder / deck-detail cluster relied on Tailwind
classes that don't exist in `tailwind.config.js` (no `bg-bg-*`,
`text-text-*`, `border-border`, `bg-accent-ember`,
`focus:ring-accent-ember`, `hover:bg-accent-ember-dark`). Those classes
produced ZERO CSS — backgrounds were transparent, borders invisible,
hover states absent.
Rewrote with inline `style={{ ... CSS vars ... }}` + the `<Button>` /
`<SearchBar>` primitives + `glass-panel` surfaces:
- `pages/decks.js` (full page)
- `pages/deck/[id].js` (header, stats sidebar, group-by controls,
card list)
- `pages/deck-builder.js` (loading spinner)
- `components/DeckBuilderView.js` (toolbar + main panel)
- `components/DeckBuilderCardBrowser.js` (full rewrite; integrated
`<SearchBar>` for the card-picker input)
- `components/DeckBuilderDeckList.js` (full rewrite)
- `components/DeckBuilderStatsBar.js`
- `components/ManaSymbolSettings.js`
- `components/ManaSymbols.js` (single `text-text-secondary`)
- `pages/admin/card-editor.js` cluster was already clean
### 2. Duplicative / stale page searches
Replaced raw `<input>` search controls with the `<SearchBar>` primitive
(adds clear button, ember focus ring, system-consistent rounded
corners). Kept page-specific filter searches (they filter the visible
list — distinct from the global TopSearchBar command palette):
- `pages/my-cards.js`
- `pages/community/collections.js`
- `components/CardsPageView.js`
- `components/CollectionPageView.js`
- `components/DeckBuilderCardBrowser.js`
`pages/my-cards.js` filter wrapper also lifted into a `glass-panel`
chip instead of a solid `var(--bg-primary)` band.
### 3. Square corners + stale palette in shared views
- `components/CollectionPageView.js`: 10 action buttons (`rounded-lg`
+ `hover:bg-gray-50`) → `rounded-xl` + `nav-item-hover`; 4 filter
selects (`focus:ring-purple-500 rounded-lg`) → `.input-field`;
view-mode toggle (`bg-white text-gray-900` — invisible in dark mode)
→ tokenised; SYSTEM badge gradient (`from-blue-500 to-purple-600`)
→ ember↔flame; tooltip (`bg-gray-900`) → `glass-panel-strong`;
search-results dropdown (`bg-white border-gray-200` — invisible in
dark mode) → `glass-panel-strong`; Activity / game-count /
TCG-game badges palette-aligned.
- `components/CardsPageView.js`: "Load More Cards" button
(`bg-gradient-to-r from-blue-500 to-purple-600 rounded-lg`) →
`<Button variant="primary" size="lg">`.
- `components/CollectionsPageView.js`: matching SYSTEM badge +
tooltip cleanup.
- `components/ShareModal.js`: user-search dropdown
(`border-gray-200 hover:bg-gray-50`) and email-invite card moved
onto `glass-panel` + `nav-item-hover`; social-share buttons
`rounded-lg hover:bg-gray-50` → `rounded-xl nav-item-hover`.
- `components/Layout.js`: profile-menu dropdown row
(`hover:bg-gray-50 dark:hover:bg-gray-700`) → `nav-item-hover`.
- `components/CardItem.js`: bulk-select checkbox
`focus:ring-purple-500` → ember.
### 4. `dark:` modifier classes (broken with `[data-theme]` theming)
This app uses `[data-theme="dark"]` CSS selector theming, not
Tailwind's `class` strategy, so `dark:bg-green-900/20` etc. produced
no CSS in dark mode. Affected alerts on `pages/settings.js` and
`pages/profile.js` — replaced with `glass-panel` + semantic border
colour (flame for success, #dc2626 for error).
`pages/settings.js` sidebar nav also moved off its hardcoded full-ember
fill onto the system `nav-item` / `nav-item-active` / `nav-item-hover`
pattern for consistency with the global sidebar.
## Verification
- `npm run build` — green (Next 16 + Turbopack)
- `npm run lint` — 0 errors, 1 unrelated pre-existing warning
- `npm run test:run` — 113/113 pass (no test changes needed)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-04 15:06:22 -04:00
|
|
|
<h1
|
|
|
|
|
className="text-3xl font-bold"
|
|
|
|
|
style={{ color: 'var(--text-primary)' }}
|
|
|
|
|
>
|
|
|
|
|
{deck.name}
|
|
|
|
|
</h1>
|
2025-07-29 15:19:48 -04:00
|
|
|
{deck.is_public && (
|
refactor(design): site-wide sweep — broken Tailwind tokens, rounded corners, SearchBar primitive (#117) (#117)
Comprehensive design sweep across the rest of the app following the
shipped Liquid Glass + corner-border-light system (#116).
## Three classes of finding
### 1. Broken Tailwind token classes (HIGH — pages were unstyled)
The decks / deck-builder / deck-detail cluster relied on Tailwind
classes that don't exist in `tailwind.config.js` (no `bg-bg-*`,
`text-text-*`, `border-border`, `bg-accent-ember`,
`focus:ring-accent-ember`, `hover:bg-accent-ember-dark`). Those classes
produced ZERO CSS — backgrounds were transparent, borders invisible,
hover states absent.
Rewrote with inline `style={{ ... CSS vars ... }}` + the `<Button>` /
`<SearchBar>` primitives + `glass-panel` surfaces:
- `pages/decks.js` (full page)
- `pages/deck/[id].js` (header, stats sidebar, group-by controls,
card list)
- `pages/deck-builder.js` (loading spinner)
- `components/DeckBuilderView.js` (toolbar + main panel)
- `components/DeckBuilderCardBrowser.js` (full rewrite; integrated
`<SearchBar>` for the card-picker input)
- `components/DeckBuilderDeckList.js` (full rewrite)
- `components/DeckBuilderStatsBar.js`
- `components/ManaSymbolSettings.js`
- `components/ManaSymbols.js` (single `text-text-secondary`)
- `pages/admin/card-editor.js` cluster was already clean
### 2. Duplicative / stale page searches
Replaced raw `<input>` search controls with the `<SearchBar>` primitive
(adds clear button, ember focus ring, system-consistent rounded
corners). Kept page-specific filter searches (they filter the visible
list — distinct from the global TopSearchBar command palette):
- `pages/my-cards.js`
- `pages/community/collections.js`
- `components/CardsPageView.js`
- `components/CollectionPageView.js`
- `components/DeckBuilderCardBrowser.js`
`pages/my-cards.js` filter wrapper also lifted into a `glass-panel`
chip instead of a solid `var(--bg-primary)` band.
### 3. Square corners + stale palette in shared views
- `components/CollectionPageView.js`: 10 action buttons (`rounded-lg`
+ `hover:bg-gray-50`) → `rounded-xl` + `nav-item-hover`; 4 filter
selects (`focus:ring-purple-500 rounded-lg`) → `.input-field`;
view-mode toggle (`bg-white text-gray-900` — invisible in dark mode)
→ tokenised; SYSTEM badge gradient (`from-blue-500 to-purple-600`)
→ ember↔flame; tooltip (`bg-gray-900`) → `glass-panel-strong`;
search-results dropdown (`bg-white border-gray-200` — invisible in
dark mode) → `glass-panel-strong`; Activity / game-count /
TCG-game badges palette-aligned.
- `components/CardsPageView.js`: "Load More Cards" button
(`bg-gradient-to-r from-blue-500 to-purple-600 rounded-lg`) →
`<Button variant="primary" size="lg">`.
- `components/CollectionsPageView.js`: matching SYSTEM badge +
tooltip cleanup.
- `components/ShareModal.js`: user-search dropdown
(`border-gray-200 hover:bg-gray-50`) and email-invite card moved
onto `glass-panel` + `nav-item-hover`; social-share buttons
`rounded-lg hover:bg-gray-50` → `rounded-xl nav-item-hover`.
- `components/Layout.js`: profile-menu dropdown row
(`hover:bg-gray-50 dark:hover:bg-gray-700`) → `nav-item-hover`.
- `components/CardItem.js`: bulk-select checkbox
`focus:ring-purple-500` → ember.
### 4. `dark:` modifier classes (broken with `[data-theme]` theming)
This app uses `[data-theme="dark"]` CSS selector theming, not
Tailwind's `class` strategy, so `dark:bg-green-900/20` etc. produced
no CSS in dark mode. Affected alerts on `pages/settings.js` and
`pages/profile.js` — replaced with `glass-panel` + semantic border
colour (flame for success, #dc2626 for error).
`pages/settings.js` sidebar nav also moved off its hardcoded full-ember
fill onto the system `nav-item` / `nav-item-active` / `nav-item-hover`
pattern for consistency with the global sidebar.
## Verification
- `npm run build` — green (Next 16 + Turbopack)
- `npm run lint` — 0 errors, 1 unrelated pre-existing warning
- `npm run test:run` — 113/113 pass (no test changes needed)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-04 15:06:22 -04:00
|
|
|
<span
|
|
|
|
|
className="px-2 py-1 rounded-full text-xs font-medium"
|
|
|
|
|
style={{
|
|
|
|
|
backgroundColor: 'var(--bg-tertiary)',
|
|
|
|
|
color: 'var(--accent-ember)',
|
|
|
|
|
}}
|
|
|
|
|
>
|
2025-07-29 15:19:48 -04:00
|
|
|
Public
|
|
|
|
|
</span>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
refactor(design): site-wide sweep — broken Tailwind tokens, rounded corners, SearchBar primitive (#117) (#117)
Comprehensive design sweep across the rest of the app following the
shipped Liquid Glass + corner-border-light system (#116).
## Three classes of finding
### 1. Broken Tailwind token classes (HIGH — pages were unstyled)
The decks / deck-builder / deck-detail cluster relied on Tailwind
classes that don't exist in `tailwind.config.js` (no `bg-bg-*`,
`text-text-*`, `border-border`, `bg-accent-ember`,
`focus:ring-accent-ember`, `hover:bg-accent-ember-dark`). Those classes
produced ZERO CSS — backgrounds were transparent, borders invisible,
hover states absent.
Rewrote with inline `style={{ ... CSS vars ... }}` + the `<Button>` /
`<SearchBar>` primitives + `glass-panel` surfaces:
- `pages/decks.js` (full page)
- `pages/deck/[id].js` (header, stats sidebar, group-by controls,
card list)
- `pages/deck-builder.js` (loading spinner)
- `components/DeckBuilderView.js` (toolbar + main panel)
- `components/DeckBuilderCardBrowser.js` (full rewrite; integrated
`<SearchBar>` for the card-picker input)
- `components/DeckBuilderDeckList.js` (full rewrite)
- `components/DeckBuilderStatsBar.js`
- `components/ManaSymbolSettings.js`
- `components/ManaSymbols.js` (single `text-text-secondary`)
- `pages/admin/card-editor.js` cluster was already clean
### 2. Duplicative / stale page searches
Replaced raw `<input>` search controls with the `<SearchBar>` primitive
(adds clear button, ember focus ring, system-consistent rounded
corners). Kept page-specific filter searches (they filter the visible
list — distinct from the global TopSearchBar command palette):
- `pages/my-cards.js`
- `pages/community/collections.js`
- `components/CardsPageView.js`
- `components/CollectionPageView.js`
- `components/DeckBuilderCardBrowser.js`
`pages/my-cards.js` filter wrapper also lifted into a `glass-panel`
chip instead of a solid `var(--bg-primary)` band.
### 3. Square corners + stale palette in shared views
- `components/CollectionPageView.js`: 10 action buttons (`rounded-lg`
+ `hover:bg-gray-50`) → `rounded-xl` + `nav-item-hover`; 4 filter
selects (`focus:ring-purple-500 rounded-lg`) → `.input-field`;
view-mode toggle (`bg-white text-gray-900` — invisible in dark mode)
→ tokenised; SYSTEM badge gradient (`from-blue-500 to-purple-600`)
→ ember↔flame; tooltip (`bg-gray-900`) → `glass-panel-strong`;
search-results dropdown (`bg-white border-gray-200` — invisible in
dark mode) → `glass-panel-strong`; Activity / game-count /
TCG-game badges palette-aligned.
- `components/CardsPageView.js`: "Load More Cards" button
(`bg-gradient-to-r from-blue-500 to-purple-600 rounded-lg`) →
`<Button variant="primary" size="lg">`.
- `components/CollectionsPageView.js`: matching SYSTEM badge +
tooltip cleanup.
- `components/ShareModal.js`: user-search dropdown
(`border-gray-200 hover:bg-gray-50`) and email-invite card moved
onto `glass-panel` + `nav-item-hover`; social-share buttons
`rounded-lg hover:bg-gray-50` → `rounded-xl nav-item-hover`.
- `components/Layout.js`: profile-menu dropdown row
(`hover:bg-gray-50 dark:hover:bg-gray-700`) → `nav-item-hover`.
- `components/CardItem.js`: bulk-select checkbox
`focus:ring-purple-500` → ember.
### 4. `dark:` modifier classes (broken with `[data-theme]` theming)
This app uses `[data-theme="dark"]` CSS selector theming, not
Tailwind's `class` strategy, so `dark:bg-green-900/20` etc. produced
no CSS in dark mode. Affected alerts on `pages/settings.js` and
`pages/profile.js` — replaced with `glass-panel` + semantic border
colour (flame for success, #dc2626 for error).
`pages/settings.js` sidebar nav also moved off its hardcoded full-ember
fill onto the system `nav-item` / `nav-item-active` / `nav-item-hover`
pattern for consistency with the global sidebar.
## Verification
- `npm run build` — green (Next 16 + Turbopack)
- `npm run lint` — 0 errors, 1 unrelated pre-existing warning
- `npm run test:run` — 113/113 pass (no test changes needed)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-04 15:06:22 -04:00
|
|
|
<p className="mb-2" style={{ color: 'var(--text-secondary)' }}>
|
2025-07-29 15:19:48 -04:00
|
|
|
by {deck.creator_username} • {deck.format} • {stats.totalCards} cards
|
|
|
|
|
</p>
|
|
|
|
|
{deck.description && (
|
refactor(design): site-wide sweep — broken Tailwind tokens, rounded corners, SearchBar primitive (#117) (#117)
Comprehensive design sweep across the rest of the app following the
shipped Liquid Glass + corner-border-light system (#116).
## Three classes of finding
### 1. Broken Tailwind token classes (HIGH — pages were unstyled)
The decks / deck-builder / deck-detail cluster relied on Tailwind
classes that don't exist in `tailwind.config.js` (no `bg-bg-*`,
`text-text-*`, `border-border`, `bg-accent-ember`,
`focus:ring-accent-ember`, `hover:bg-accent-ember-dark`). Those classes
produced ZERO CSS — backgrounds were transparent, borders invisible,
hover states absent.
Rewrote with inline `style={{ ... CSS vars ... }}` + the `<Button>` /
`<SearchBar>` primitives + `glass-panel` surfaces:
- `pages/decks.js` (full page)
- `pages/deck/[id].js` (header, stats sidebar, group-by controls,
card list)
- `pages/deck-builder.js` (loading spinner)
- `components/DeckBuilderView.js` (toolbar + main panel)
- `components/DeckBuilderCardBrowser.js` (full rewrite; integrated
`<SearchBar>` for the card-picker input)
- `components/DeckBuilderDeckList.js` (full rewrite)
- `components/DeckBuilderStatsBar.js`
- `components/ManaSymbolSettings.js`
- `components/ManaSymbols.js` (single `text-text-secondary`)
- `pages/admin/card-editor.js` cluster was already clean
### 2. Duplicative / stale page searches
Replaced raw `<input>` search controls with the `<SearchBar>` primitive
(adds clear button, ember focus ring, system-consistent rounded
corners). Kept page-specific filter searches (they filter the visible
list — distinct from the global TopSearchBar command palette):
- `pages/my-cards.js`
- `pages/community/collections.js`
- `components/CardsPageView.js`
- `components/CollectionPageView.js`
- `components/DeckBuilderCardBrowser.js`
`pages/my-cards.js` filter wrapper also lifted into a `glass-panel`
chip instead of a solid `var(--bg-primary)` band.
### 3. Square corners + stale palette in shared views
- `components/CollectionPageView.js`: 10 action buttons (`rounded-lg`
+ `hover:bg-gray-50`) → `rounded-xl` + `nav-item-hover`; 4 filter
selects (`focus:ring-purple-500 rounded-lg`) → `.input-field`;
view-mode toggle (`bg-white text-gray-900` — invisible in dark mode)
→ tokenised; SYSTEM badge gradient (`from-blue-500 to-purple-600`)
→ ember↔flame; tooltip (`bg-gray-900`) → `glass-panel-strong`;
search-results dropdown (`bg-white border-gray-200` — invisible in
dark mode) → `glass-panel-strong`; Activity / game-count /
TCG-game badges palette-aligned.
- `components/CardsPageView.js`: "Load More Cards" button
(`bg-gradient-to-r from-blue-500 to-purple-600 rounded-lg`) →
`<Button variant="primary" size="lg">`.
- `components/CollectionsPageView.js`: matching SYSTEM badge +
tooltip cleanup.
- `components/ShareModal.js`: user-search dropdown
(`border-gray-200 hover:bg-gray-50`) and email-invite card moved
onto `glass-panel` + `nav-item-hover`; social-share buttons
`rounded-lg hover:bg-gray-50` → `rounded-xl nav-item-hover`.
- `components/Layout.js`: profile-menu dropdown row
(`hover:bg-gray-50 dark:hover:bg-gray-700`) → `nav-item-hover`.
- `components/CardItem.js`: bulk-select checkbox
`focus:ring-purple-500` → ember.
### 4. `dark:` modifier classes (broken with `[data-theme]` theming)
This app uses `[data-theme="dark"]` CSS selector theming, not
Tailwind's `class` strategy, so `dark:bg-green-900/20` etc. produced
no CSS in dark mode. Affected alerts on `pages/settings.js` and
`pages/profile.js` — replaced with `glass-panel` + semantic border
colour (flame for success, #dc2626 for error).
`pages/settings.js` sidebar nav also moved off its hardcoded full-ember
fill onto the system `nav-item` / `nav-item-active` / `nav-item-hover`
pattern for consistency with the global sidebar.
## Verification
- `npm run build` — green (Next 16 + Turbopack)
- `npm run lint` — 0 errors, 1 unrelated pre-existing warning
- `npm run test:run` — 113/113 pass (no test changes needed)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-04 15:06:22 -04:00
|
|
|
<p
|
|
|
|
|
className="max-w-2xl"
|
|
|
|
|
style={{ color: 'var(--text-secondary)' }}
|
|
|
|
|
>
|
|
|
|
|
{deck.description}
|
|
|
|
|
</p>
|
2025-07-29 15:19:48 -04:00
|
|
|
)}
|
|
|
|
|
</div>
|
refactor(design): site-wide sweep — broken Tailwind tokens, rounded corners, SearchBar primitive (#117) (#117)
Comprehensive design sweep across the rest of the app following the
shipped Liquid Glass + corner-border-light system (#116).
## Three classes of finding
### 1. Broken Tailwind token classes (HIGH — pages were unstyled)
The decks / deck-builder / deck-detail cluster relied on Tailwind
classes that don't exist in `tailwind.config.js` (no `bg-bg-*`,
`text-text-*`, `border-border`, `bg-accent-ember`,
`focus:ring-accent-ember`, `hover:bg-accent-ember-dark`). Those classes
produced ZERO CSS — backgrounds were transparent, borders invisible,
hover states absent.
Rewrote with inline `style={{ ... CSS vars ... }}` + the `<Button>` /
`<SearchBar>` primitives + `glass-panel` surfaces:
- `pages/decks.js` (full page)
- `pages/deck/[id].js` (header, stats sidebar, group-by controls,
card list)
- `pages/deck-builder.js` (loading spinner)
- `components/DeckBuilderView.js` (toolbar + main panel)
- `components/DeckBuilderCardBrowser.js` (full rewrite; integrated
`<SearchBar>` for the card-picker input)
- `components/DeckBuilderDeckList.js` (full rewrite)
- `components/DeckBuilderStatsBar.js`
- `components/ManaSymbolSettings.js`
- `components/ManaSymbols.js` (single `text-text-secondary`)
- `pages/admin/card-editor.js` cluster was already clean
### 2. Duplicative / stale page searches
Replaced raw `<input>` search controls with the `<SearchBar>` primitive
(adds clear button, ember focus ring, system-consistent rounded
corners). Kept page-specific filter searches (they filter the visible
list — distinct from the global TopSearchBar command palette):
- `pages/my-cards.js`
- `pages/community/collections.js`
- `components/CardsPageView.js`
- `components/CollectionPageView.js`
- `components/DeckBuilderCardBrowser.js`
`pages/my-cards.js` filter wrapper also lifted into a `glass-panel`
chip instead of a solid `var(--bg-primary)` band.
### 3. Square corners + stale palette in shared views
- `components/CollectionPageView.js`: 10 action buttons (`rounded-lg`
+ `hover:bg-gray-50`) → `rounded-xl` + `nav-item-hover`; 4 filter
selects (`focus:ring-purple-500 rounded-lg`) → `.input-field`;
view-mode toggle (`bg-white text-gray-900` — invisible in dark mode)
→ tokenised; SYSTEM badge gradient (`from-blue-500 to-purple-600`)
→ ember↔flame; tooltip (`bg-gray-900`) → `glass-panel-strong`;
search-results dropdown (`bg-white border-gray-200` — invisible in
dark mode) → `glass-panel-strong`; Activity / game-count /
TCG-game badges palette-aligned.
- `components/CardsPageView.js`: "Load More Cards" button
(`bg-gradient-to-r from-blue-500 to-purple-600 rounded-lg`) →
`<Button variant="primary" size="lg">`.
- `components/CollectionsPageView.js`: matching SYSTEM badge +
tooltip cleanup.
- `components/ShareModal.js`: user-search dropdown
(`border-gray-200 hover:bg-gray-50`) and email-invite card moved
onto `glass-panel` + `nav-item-hover`; social-share buttons
`rounded-lg hover:bg-gray-50` → `rounded-xl nav-item-hover`.
- `components/Layout.js`: profile-menu dropdown row
(`hover:bg-gray-50 dark:hover:bg-gray-700`) → `nav-item-hover`.
- `components/CardItem.js`: bulk-select checkbox
`focus:ring-purple-500` → ember.
### 4. `dark:` modifier classes (broken with `[data-theme]` theming)
This app uses `[data-theme="dark"]` CSS selector theming, not
Tailwind's `class` strategy, so `dark:bg-green-900/20` etc. produced
no CSS in dark mode. Affected alerts on `pages/settings.js` and
`pages/profile.js` — replaced with `glass-panel` + semantic border
colour (flame for success, #dc2626 for error).
`pages/settings.js` sidebar nav also moved off its hardcoded full-ember
fill onto the system `nav-item` / `nav-item-active` / `nav-item-hover`
pattern for consistency with the global sidebar.
## Verification
- `npm run build` — green (Next 16 + Turbopack)
- `npm run lint` — 0 errors, 1 unrelated pre-existing warning
- `npm run test:run` — 113/113 pass (no test changes needed)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-04 15:06:22 -04:00
|
|
|
|
2025-07-29 15:19:48 -04:00
|
|
|
{isOwner && (
|
|
|
|
|
<div className="flex space-x-3">
|
refactor(design): site-wide sweep — broken Tailwind tokens, rounded corners, SearchBar primitive (#117) (#117)
Comprehensive design sweep across the rest of the app following the
shipped Liquid Glass + corner-border-light system (#116).
## Three classes of finding
### 1. Broken Tailwind token classes (HIGH — pages were unstyled)
The decks / deck-builder / deck-detail cluster relied on Tailwind
classes that don't exist in `tailwind.config.js` (no `bg-bg-*`,
`text-text-*`, `border-border`, `bg-accent-ember`,
`focus:ring-accent-ember`, `hover:bg-accent-ember-dark`). Those classes
produced ZERO CSS — backgrounds were transparent, borders invisible,
hover states absent.
Rewrote with inline `style={{ ... CSS vars ... }}` + the `<Button>` /
`<SearchBar>` primitives + `glass-panel` surfaces:
- `pages/decks.js` (full page)
- `pages/deck/[id].js` (header, stats sidebar, group-by controls,
card list)
- `pages/deck-builder.js` (loading spinner)
- `components/DeckBuilderView.js` (toolbar + main panel)
- `components/DeckBuilderCardBrowser.js` (full rewrite; integrated
`<SearchBar>` for the card-picker input)
- `components/DeckBuilderDeckList.js` (full rewrite)
- `components/DeckBuilderStatsBar.js`
- `components/ManaSymbolSettings.js`
- `components/ManaSymbols.js` (single `text-text-secondary`)
- `pages/admin/card-editor.js` cluster was already clean
### 2. Duplicative / stale page searches
Replaced raw `<input>` search controls with the `<SearchBar>` primitive
(adds clear button, ember focus ring, system-consistent rounded
corners). Kept page-specific filter searches (they filter the visible
list — distinct from the global TopSearchBar command palette):
- `pages/my-cards.js`
- `pages/community/collections.js`
- `components/CardsPageView.js`
- `components/CollectionPageView.js`
- `components/DeckBuilderCardBrowser.js`
`pages/my-cards.js` filter wrapper also lifted into a `glass-panel`
chip instead of a solid `var(--bg-primary)` band.
### 3. Square corners + stale palette in shared views
- `components/CollectionPageView.js`: 10 action buttons (`rounded-lg`
+ `hover:bg-gray-50`) → `rounded-xl` + `nav-item-hover`; 4 filter
selects (`focus:ring-purple-500 rounded-lg`) → `.input-field`;
view-mode toggle (`bg-white text-gray-900` — invisible in dark mode)
→ tokenised; SYSTEM badge gradient (`from-blue-500 to-purple-600`)
→ ember↔flame; tooltip (`bg-gray-900`) → `glass-panel-strong`;
search-results dropdown (`bg-white border-gray-200` — invisible in
dark mode) → `glass-panel-strong`; Activity / game-count /
TCG-game badges palette-aligned.
- `components/CardsPageView.js`: "Load More Cards" button
(`bg-gradient-to-r from-blue-500 to-purple-600 rounded-lg`) →
`<Button variant="primary" size="lg">`.
- `components/CollectionsPageView.js`: matching SYSTEM badge +
tooltip cleanup.
- `components/ShareModal.js`: user-search dropdown
(`border-gray-200 hover:bg-gray-50`) and email-invite card moved
onto `glass-panel` + `nav-item-hover`; social-share buttons
`rounded-lg hover:bg-gray-50` → `rounded-xl nav-item-hover`.
- `components/Layout.js`: profile-menu dropdown row
(`hover:bg-gray-50 dark:hover:bg-gray-700`) → `nav-item-hover`.
- `components/CardItem.js`: bulk-select checkbox
`focus:ring-purple-500` → ember.
### 4. `dark:` modifier classes (broken with `[data-theme]` theming)
This app uses `[data-theme="dark"]` CSS selector theming, not
Tailwind's `class` strategy, so `dark:bg-green-900/20` etc. produced
no CSS in dark mode. Affected alerts on `pages/settings.js` and
`pages/profile.js` — replaced with `glass-panel` + semantic border
colour (flame for success, #dc2626 for error).
`pages/settings.js` sidebar nav also moved off its hardcoded full-ember
fill onto the system `nav-item` / `nav-item-active` / `nav-item-hover`
pattern for consistency with the global sidebar.
## Verification
- `npm run build` — green (Next 16 + Turbopack)
- `npm run lint` — 0 errors, 1 unrelated pre-existing warning
- `npm run test:run` — 113/113 pass (no test changes needed)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-04 15:06:22 -04:00
|
|
|
<Link href={`/deck-builder?deck=${deck.id}`}>
|
|
|
|
|
<Button variant="primary">Edit Deck</Button>
|
2025-07-29 15:19:48 -04:00
|
|
|
</Link>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div className="grid grid-cols-1 lg:grid-cols-4 gap-6">
|
2026-06-13 01:44:16 -04:00
|
|
|
<DeckDetailStatsSidebar
|
|
|
|
|
deck={deck}
|
|
|
|
|
stats={stats}
|
|
|
|
|
groupBy={groupBy}
|
|
|
|
|
onGroupByChange={setGroupBy}
|
|
|
|
|
/>
|
2025-07-29 15:19:48 -04:00
|
|
|
|
2026-06-13 02:11:50 -04:00
|
|
|
<DeckDetailCardList cards={deck.cards} groupedCards={groupedCards} />
|
2025-07-29 15:19:48 -04:00
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</Layout>
|
|
|
|
|
);
|
|
|
|
|
}
|