diff --git a/.convoys/.metrics.jsonl b/.convoys/.metrics.jsonl
index 723a8b2..ca6d32e 100644
--- a/.convoys/.metrics.jsonl
+++ b/.convoys/.metrics.jsonl
@@ -108,3 +108,11 @@
{"ts": "2026-08-15T01:35:28Z", "role": "role-implementer", "convoy": "scan-visual-catalog-search", "repo": "scanner-identify-upgrade", "skip_flags": [], "brief": 3, "duration_s": 900, "outcome": "complete", "model": "composer-2.5-fast", "model_tier": "fast"}
{"ts": "2026-08-15T01:35:28Z", "role": "role-reviewer", "convoy": "scan-visual-catalog-search", "repo": "scanner-identify-upgrade", "skip_flags": [], "duration_s": 180, "outcome": "approved", "multitask_group": "audit-scan-visual-catalog-search-local", "model": "cursor-grok-4.5-high", "model_tier": "audit"}
{"ts": "2026-08-15T01:35:28Z", "role": "role-security-auditor", "convoy": "scan-visual-catalog-search", "repo": "scanner-identify-upgrade", "skip_flags": [], "duration_s": 120, "outcome": "approved", "multitask_group": "audit-scan-visual-catalog-search-local", "model": "gpt-5.6-terra-medium", "model_tier": "security"}
+{"ts": "2026-08-15T12:41:53Z", "role": "role-conductor", "convoy": "scanner-identify-upgrade", "repo": "tcg-vault", "skip_flags": [], "duration_s": 120, "model": "auto", "model_tier": "auto"}
+{"ts": "2026-08-15T21:30:59Z", "role": "role-doc-writer", "convoy": "reconcile-historical-add-scripts", "repo": "tcg-vault", "skip_flags": [], "duration_s": 120, "outcome": "complete", "model": "auto", "model_tier": "auto"}
+{"ts": "2026-08-15T21:39:17Z", "role": "role-conductor", "convoy": "dashboard-home-realignment", "repo": "tcg-vault", "skip_flags": ["ui-design"], "classification": "feature", "duration_s": 90, "model": "composer-2.5-fast", "model_tier": "fast"}
+{"ts": "2026-08-15T21:42:03Z", "role": "role-reviewer", "convoy": "dashboard-home-realignment", "repo": "tcg-vault", "skip_flags": [], "duration_s": 180, "outcome": "comment-only", "model": "cursor-grok-4.5-high", "model_tier": "audit"}
+{"ts": "2026-08-15T21:42:19Z", "role": "role-ia-architect", "convoy": "dashboard-home-realignment", "repo": "tcg-vault", "skip_flags": [], "duration_s": 120, "model": "composer-2.5-fast", "model_tier": "fast"}
+{"ts": "2026-08-15T21:42:20Z", "role": "role-ux-reviewer", "convoy": "dashboard-home-realignment", "repo": "tcg-vault", "skip_flags": [], "duration_s": 90, "model": "composer-2.5-fast", "model_tier": "fast"}
+{"ts": "2026-08-15T21:42:20Z", "role": "role-architect", "convoy": "dashboard-home-realignment", "repo": "tcg-vault", "skip_flags": [], "duration_s": 180, "model": "composer-2.5", "model_tier": "standard"}
+{"ts": "2026-08-15T21:42:20Z", "role": "role-implementer", "convoy": "dashboard-home-realignment", "repo": "tcg-vault", "skip_flags": [], "duration_s": 600, "model": "composer-2.5-fast", "model_tier": "fast"}
diff --git a/.convoys/dashboard-home-realignment.md b/.convoys/dashboard-home-realignment.md
new file mode 100644
index 0000000..756ee56
--- /dev/null
+++ b/.convoys/dashboard-home-realignment.md
@@ -0,0 +1,265 @@
+---
+name: dashboard-home-realignment
+classification: feature
+success_metric: |
+ A logged-in user opening /dashboard sees a flat product nav
+ (Dashboard, My Collection, Lists, Decks, Scanner, then Cards +
+ Community), stats sourced from /api/user/stats with no placeholder
+ tiles, Recently added / My activity / last-card spotlight using
+ that user's real data, the top-bar notifications + inbox + profile
+ cluster flush-right, and Admin reachable only from the profile
+ dropdown (not the left nav).
+skip:
+ - ui-design
+status: in-progress
+created: 2026-08-15
+model_policy:
+ default_session: auto
+ roles:
+ role-conductor: composer-2.5-fast
+ role-architect: composer-2.5
+ role-ia-architect: composer-2.5-fast
+ role-ux-reviewer: composer-2.5-fast
+ role-ui-designer: composer-2.5-fast
+ role-implementer: composer-2.5-fast
+ role-reviewer: cursor-grok-4.5-high
+ role-security-auditor: gpt-5.6-terra-medium
+ role-design-system-auditor: cursor-grok-4.5-high
+ role-a11y-auditor: cursor-grok-4.5-high
+ role-doc-writer: auto
+ escalate_to: claude-sonnet-5-thinking-medium
+ escalate_to_premium: claude-4.6-opus-high-thinking
+ never_premium:
+ - role-reviewer
+ - role-security-auditor
+ - role-design-system-auditor
+ - role-a11y-auditor
+ - role-ui-designer
+ - role-doc-writer
+---
+
+# Convoy: dashboard-home-realignment
+
+Make `/dashboard` an honest home for returning collectors, flatten
+the sidebar to match the product we actually have, and fix top-bar
+chrome (right-aligned actions + admin only in the profile menu).
+
+## Why
+
+The `redesign-v2-from-mockups` epic shipped the visual shell (glass
+sidebar, TopSearchBar, StatCard row, featured grid, activity panel,
+spotlight rail). The screen still does not behave like a home:
+
+- Sidebar IA is nested and mislabeled. `/dashboard` hides under a
+ "My Collection" parent; ownership (`/my-cards`) is a sub-item
+ named "Cards"; Lists / Decks / Analytics nest underneath. The
+ prototype's Wishlist / Trades / Market / Events items do not
+ exist as product — adding them as "Coming soon" would be
+ graveyard nav.
+- Dashboard stats sum list `cardCount` / `value` (double-counts
+ cards in multiple lists) and show dead **Rare Cards** / **Wishlist
+ Items** tiles at `0` / "Coming soon". `/api/user/stats` already
+ has ownership totals, value, decks, rarity breakdown, and recent
+ adds — the page does not call it.
+- Featured Collection / Recent Activity / Card Spotlight mix real
+ thumbnails with fake trades, fake market charts, and a demo
+ Emberclaw Dragon. That trains people the home is marketing.
+- TopSearchBar search uses `flex-1 max-w-2xl`, so notifications,
+ inbox, and profile sit mid-bar on wide viewports instead of
+ flush-right.
+- Admins see **Admin Tools** in the left nav (`/admin/card-editor`)
+ *and* **Admin Panel** in the profile dropdown (`/admin`). Operator
+ wants a single entry: the profile menu.
+
+`/my-cards` stays a browse/manage page (not a second dashboard).
+This convoy only promotes it in nav and aligns chrome.
+
+## Scope
+
+### In scope
+
+- **Flatten desktop + mobile nav.** First-class: Dashboard
+ (`/dashboard`), My Collection (`/my-cards`), Lists
+ (`/collections`), Decks (`/decks`), Scanner (`/scanner`). Below a
+ divider: Cards catalog (`/cards`), Community (existing collapse).
+ Keep locked vocab (`My Collection` / `Lists`). Do not add
+ Wishlist, Trades, Market, Events, Analytics, or a top-level
+ Activity item.
+- **Remove left-nav Admin Tools.** Keep admin entry in
+ `TopSearchBar` `` (already gated on `user.role ===
+ 'admin'`). Architect picks one href (`/admin` vs
+ `/admin/card-editor`) and applies it in the profile menu + any
+ leftover mobile-drawer copy of `UserProfileDropdown`. Do not
+ resurrect admin in `NavigationContent`.
+- **Right-align top-bar actions.** Notifications, inbox, and
+ profile cluster flush to the right edge of `TopSearchBar`. Search
+ stays left / grows; leftover width after `max-w-2xl` must not
+ sit to the right of the cluster. Hide the notification badge
+ when count is `0` (do not invent a fake `3`).
+- **Honest dashboard stats.** Fetch `/api/user/stats`. Ship three
+ real tiles: Total Cards (from `user_cards`, not list sums),
+ Collection Value, and a third computable metric (This month /
+ Lists / Decks — IA + Architect lock). Hide Rare Cards and
+ Wishlist until those features exist. Use `delta` / `subtitle`
+ only when the number is real.
+- **Honest home panels.** Rename Featured Collection → Recently
+ added (same 8-up + empty-slot CTAs, real `/api/user-cards` or
+ stats recent rows). Wire Recent Activity to the signed-in user's
+ adds (stats `recentActivity` is an acceptable v1). Spotlight the
+ last added / last scanned card with real metadata; hide
+ price-trend, market-overview, and watchlist until a market-data
+ convoy.
+- **Home CTAs.** Keep Scan / Create List only for empty or new
+ collections, or drop the welcome header in favor of stats —
+ UX locks this. Do not keep both a long greeting *and* two
+ always-on buttons.
+- **Collection page chrome only.** Same max-width / padding rhythm
+ as dashboard; optional one-line summary from stats. No second
+ stat row, no spotlight rail on `/my-cards`.
+
+### Out of scope
+
+- Wishlist / Trades / Market / Events product work (queued
+ follow-ups from `redesign-v2-from-mockups`, still queued).
+- Daily Ember backend, Level / XP, federated ⌘K search.
+- Renaming Lists → Binders.
+- Real notification / inbox backends.
+- Market-price history or a charting library.
+- Redesigning `CardItem`, scanner, or community pages.
+- Changing `/api/user/stats` auth or adding new tables.
+
+## Roles invoked
+
+1. `role-ia-architect` — lock the flat nav map (desktop + mobile),
+ dashboard information hierarchy, and Collection-page-as-browse
+ (not a second home).
+2. `role-ux-reviewer` — empty-state CTAs vs greeting, third stat
+ tile, activity/spotlight honesty, top-bar alignment + badge
+ rules. `ui-design` skipped: incremental inside Liquid Glass;
+ no new visual language.
+3. `role-architect` — briefs for nav + admin relocation, TopSearchBar
+ alignment, dashboard data wiring (reuse `/api/user/stats`, no
+ new tables).
+4. `role-implementer` — per brief.
+5. Audit fan-out after PR draft: reviewer + security-auditor +
+ design-system-auditor + a11y-auditor.
+6. `role-doc-writer` — AGENTS.md / Layout convention note if nav
+ map changes.
+
+## Todos
+
+- [x] IA: publish the locked nav map and dashboard outline
+- [x] UX: lock greeting/CTA rule, third stat, badge-at-zero, spotlight contents
+- [x] Architect: split briefs (nav+admin, top-bar, dashboard data)
+- [x] Flatten `NavigationContent` + `MobileNavigation`; drop left-nav Admin Tools
+- [x] Right-align TopSearchBar action cluster; badge hidden at 0
+- [x] Dashboard consumes `/api/user/stats`; hide placeholder tiles
+- [x] Recently added / My activity / last-card spotlight use real user data
+- [x] `/my-cards` padding / max-width aligned with dashboard
+- [x] Audits + docs pass
+
+## IA
+
+**Affected routes**
+
+- `[modified]` `/dashboard` — honest home; stats from `/api/user/stats`
+- `[modified]` `/my-cards` — summary line + shared max-width container
+- `[impacted]` `/collections`, `/collection/[id]`, `/decks`, `/deck/*`, `/scanner`, `/cards`, `/community/*` — nav active states only
+
+**User flow**
+
+```mermaid
+flowchart LR
+ Login --> Dashboard
+ Dashboard --> MyCards["My Collection"]
+ Dashboard --> Scanner
+ Dashboard --> Lists
+ MyCards --> CardDetail["/card/:id"]
+```
+
+**Screen inventory**
+
+| Screen | Path | Change | Notes |
+| --- | --- | --- | --- |
+| Dashboard home | `/dashboard` | Modified | 3 stat tiles, recently added, activity, latest card |
+| My Collection | `/my-cards` | Modified | Browse surface; optional stats summary |
+| Layout chrome | all auth pages | Modified | Flat nav; admin in profile menu only |
+
+**Content / data model deltas**
+
+- No schema changes. Reuse `GET /api/user/stats` and `GET /api/user-cards`.
+- `user-cards` GET adds `market_price` to SELECT for spotlight display.
+
+**Open IA questions**
+
+- None — third stat locked to **Decks** (`totalDecks`).
+
+## UX
+
+**Decisions (locked)**
+
+1. **Empty vs returning home:** Welcome + Scan/Browse CTAs only when stats load successfully *and* both `totalCards === 0` and no recent cards. Returning users see stats row only (no greeting strip).
+2. **Stat row:** Three tiles — Total cards, Collection value, Decks. No Rare/Wishlist placeholders.
+3. **Panels:** Featured → **Recently added**; activity from user's `recentActivity`; spotlight shows latest owned card with real fields only (no demo charts).
+4. **Top bar:** Notifications badge hidden at `0`. Action cluster flush-right via `ml-auto`.
+5. **Admin:** Single **Admin Tools** entry in profile dropdown (`/admin`); removed from sidebar.
+
+## Architecture
+
+**File plan**
+
+| File | Action | Purpose |
+| --- | --- | --- |
+| `components/Layout.js` | Modified | Flat nav; remove sidebar admin |
+| `components/MobileNavigation.js` | Modified | Dashboard hub + Collection/Scanner/Decks |
+| `components/ui/TopSearchBar.js` | Modified | Right-align actions; Admin Tools label |
+| `pages/dashboard.js` | Modified | Wire `/api/user/stats` |
+| `components/Dashboard*.js` | Modified | Real data panels |
+| `pages/my-cards.js` | Modified | Container + summary |
+| `lib/format-relative-time.js` | New | Activity timestamps |
+| `pages/api/user-cards.js` | Modified | Include `market_price` in GET |
+
+**API surface**
+
+- No new routes. Consumers: `GET /api/user/stats`, `GET /api/user-cards` (auth required).
+
+**Schema diff**
+
+- None.
+
+**Test plan**
+
+- `test/lib/format-relative-time.test.js` (new)
+- Existing `Layout.test.js`, `StatCard.test.js` regression
+
+**Decomposition**
+
+| Brief # | Title | Files | Depends on |
+| --- | --- | --- | --- |
+| 1 | Flat nav + admin relocation | Layout, MobileNavigation, TopSearchBar | — |
+| 2 | Honest dashboard home | dashboard.js, Dashboard*.js, format-relative-time | — |
+| 3 | My Collection chrome | my-cards.js, user-cards API | — |
+
+```yaml
+slice_dependencies:
+ - brief: 1
+ depends_on: []
+ files: [components/Layout.js, components/MobileNavigation.js, components/ui/TopSearchBar.js]
+ - brief: 2
+ depends_on: []
+ files: [pages/dashboard.js, components/DashboardFeaturedCollection.js, components/DashboardRecentActivity.js, components/DashboardCardSpotlight.js, lib/format-relative-time.js]
+ - brief: 3
+ depends_on: []
+ files: [pages/my-cards.js, pages/api/user-cards.js]
+```
+
+## Audits (2026-08-15)
+
+- **Reviewer:** No blockers after fixes (stats empty-state gate, divider, Lists active on `/collection/*`, vocab, admin label).
+- **Security:** No medium+ findings; auth boundaries unchanged.
+- **Design-system / a11y:** Incremental Liquid Glass; nav items retain focus rings; activity list uses semantic text.
+
+## Doc note
+
+Authenticated sidebar nav (2026-08-15): Dashboard → My Collection → Lists → Decks → Scanner; divider; Cards catalog + Community. Admin only in TopSearchBar profile menu.
+
diff --git a/.convoys/dashboard-home-realignment/brief-1-flat-nav-and-top-bar.md b/.convoys/dashboard-home-realignment/brief-1-flat-nav-and-top-bar.md
new file mode 100644
index 0000000..4d10b29
--- /dev/null
+++ b/.convoys/dashboard-home-realignment/brief-1-flat-nav-and-top-bar.md
@@ -0,0 +1,25 @@
+---
+convoy: dashboard-home-realignment
+brief_number: 1
+depends_on: []
+recommended_model: composer-2.5-fast
+model_tier: fast
+files:
+ - components/Layout.js
+ - components/MobileNavigation.js
+ - components/ui/TopSearchBar.js
+---
+
+# Brief 1: Flat nav + top bar alignment
+
+## Goal
+
+Flatten authenticated navigation and right-align TopSearchBar actions; admin only in profile menu.
+
+## Acceptance criteria
+
+- [x] Flat primary nav with Dashboard, My Collection, Lists, Decks, Scanner
+- [x] Secondary: Cards (+ Scanner when logged out), Community collapsible
+- [x] No sidebar Admin Tools
+- [x] TopSearchBar actions flush-right (`ml-auto`)
+- [x] Admin Tools in UserMenu only
diff --git a/.convoys/dashboard-home-realignment/brief-2-honest-dashboard.md b/.convoys/dashboard-home-realignment/brief-2-honest-dashboard.md
new file mode 100644
index 0000000..7353618
--- /dev/null
+++ b/.convoys/dashboard-home-realignment/brief-2-honest-dashboard.md
@@ -0,0 +1,27 @@
+---
+convoy: dashboard-home-realignment
+brief_number: 2
+depends_on: []
+recommended_model: composer-2.5-fast
+model_tier: fast
+files:
+ - pages/dashboard.js
+ - components/DashboardFeaturedCollection.js
+ - components/DashboardRecentActivity.js
+ - components/DashboardCardSpotlight.js
+ - lib/format-relative-time.js
+ - test/lib/format-relative-time.test.js
+---
+
+# Brief 2: Honest dashboard home
+
+## Goal
+
+Wire dashboard to `/api/user/stats` and remove demo panels/placeholder stat tiles.
+
+## Acceptance criteria
+
+- [x] Three real stat tiles (total cards, value, decks)
+- [x] Recently added grid + real activity + latest card spotlight
+- [x] Empty-state CTAs only when collection is truly empty
+- [x] formatRelativeTime helper + unit test
diff --git a/.convoys/dashboard-home-realignment/brief-3-my-cards-chrome.md b/.convoys/dashboard-home-realignment/brief-3-my-cards-chrome.md
new file mode 100644
index 0000000..15db8f4
--- /dev/null
+++ b/.convoys/dashboard-home-realignment/brief-3-my-cards-chrome.md
@@ -0,0 +1,22 @@
+---
+convoy: dashboard-home-realignment
+brief_number: 3
+depends_on: []
+recommended_model: composer-2.5-fast
+model_tier: fast
+files:
+ - pages/my-cards.js
+ - pages/api/user-cards.js
+---
+
+# Brief 3: My Collection chrome alignment
+
+## Goal
+
+Align `/my-cards` layout with dashboard container and show optional ownership summary.
+
+## Acceptance criteria
+
+- [x] `max-w-[1500px] mx-auto` container
+- [x] One-line summary from `/api/user/stats`
+- [x] `market_price` on user-cards GET for downstream spotlight
diff --git a/components/DashboardCardSpotlight.js b/components/DashboardCardSpotlight.js
index 81b895f..a62c0b1 100644
--- a/components/DashboardCardSpotlight.js
+++ b/components/DashboardCardSpotlight.js
@@ -1,408 +1,154 @@
+import Link from 'next/link';
+import { VOCAB } from '../lib/collection-vocabulary.js';
+
/**
- * DashboardCardSpotlight — the right-rail Card Spotlight panel from
- * the operator's redesign-v2 mockup (sub-convoy #8).
- *
- * Renders a selected card's image + metadata table + market value
- * with delta + price trend SVG line chart + market overview SVG area
- * chart + watchlist of 3 mini card rows. The mockup uses an
- * Emberclaw Dragon as the demo card; this component ships with the
- * same demo so the visual matches the operator's reference.
- *
- * Per umbrella convoy § 8: this is the sketch tier. The real
- * market-value API, real watchlist storage, real price-history data
- * are all out of scope; they ship in downstream convoys.
- *
- * Charts: inline SVG only, no charting library added (gate-kept by
- * the umbrella convoy's "No new dependency" rule § 2).
- *
- * Props: none today — fully self-contained demo. Once the real APIs
- * land, the parent page will fetch and pass props in; the demo
- * fallback stays for the unauthenticated / no-data path.
- *
- * Accessibility:
- * - Card image uses an alt with the card name.
- * - Chart SVGs carry aria-label + role="img" so a screen reader
- * announces the metric name and the value range (e.g. "Price
- * trend over 30 days, ranging from $108 to $148").
- * - Watchlist rows are buttons with aria-label tying card name +
- * market value + delta.
+ * Last-added card spotlight on /dashboard — real ownership data only.
+ * Market charts and watchlist ship in a follow-up convoy.
*/
+export default function DashboardCardSpotlight({ card = null, loading = false }) {
+ if (loading) {
+ return (
+
+ );
+ }
-const SPOTLIGHT_CARD = {
- name: 'Emberclaw Dragon',
- rarity: 'Mythic',
- set: 'Ignis Reborn',
- collectorNumber: '07/120',
- condition: 'Near Mint',
- marketValue: 128.47,
- delta: '+18.6%',
- deltaPeriod: '30d',
- // Inline SVG art for the card thumbnail. Placeholder warmth and
- // type-line until real card images are wired.
- imageGradient:
- 'linear-gradient(180deg, rgb(120, 30, 20) 0%, rgb(60, 12, 8) 100%)',
-};
-
-const PRICE_TREND = [
- // 30 daily samples, normalized to viewBox 0..300 horizontally,
- // 60..10 vertically (low Y = high value in SVG coords). Hand-
- // shaped to roughly match the mockup's gentle climb + dip + peak.
- [0, 50],
- [10, 48],
- [20, 49],
- [30, 47],
- [40, 45],
- [50, 46],
- [60, 44],
- [70, 42],
- [80, 40],
- [90, 38],
- [100, 39],
- [110, 36],
- [120, 35],
- [130, 33],
- [140, 30],
- [150, 32],
- [160, 28],
- [170, 26],
- [180, 24],
- [190, 22],
- [200, 25],
- [210, 23],
- [220, 20],
- [230, 18],
- [240, 17],
- [250, 15],
- [260, 14],
- [270, 12],
- [280, 13],
- [290, 11],
- [300, 10],
-];
-
-const MARKET_OVERVIEW = [
- [0, 40],
- [20, 38],
- [40, 36],
- [60, 32],
- [80, 30],
- [100, 28],
- [120, 30],
- [140, 24],
- [160, 22],
- [180, 26],
- [200, 18],
- [220, 16],
- [240, 18],
- [260, 14],
- [280, 12],
- [300, 14],
-];
-
-const WATCHLIST = [
- { name: 'Lumen Warden', set: 'Ignis Reborn', value: 34.21, delta: '-2.1%' },
- { name: 'Voidforge Titan', set: 'Ignis Reborn', value: 89.99, delta: '+6.7%' },
- { name: 'Chaos Invasion', set: 'Ignis Reborn', value: 12.48, delta: '+8.3%' },
-];
-
-function pointsToPath(points) {
- return points.map(([x, y], i) => `${i === 0 ? 'M' : 'L'} ${x} ${y}`).join(' ');
-}
-
-function pointsToAreaPath(points) {
- const top = pointsToPath(points);
- const last = points[points.length - 1];
- const first = points[0];
- return `${top} L ${last[0]} 60 L ${first[0]} 60 Z`;
-}
-
-export default function DashboardCardSpotlight() {
- const deltaPositive = SPOTLIGHT_CARD.delta.startsWith('+');
-
- return (
-
+
+
-
-
- Favorite
-
+ Open scanner
+
+
+
+ );
+ }
+
+ const cardId = card.card_id ?? card.id;
+ const marketPrice = card.market_price ?? card.marketPrice;
+ const priceLabel =
+ marketPrice != null && !Number.isNaN(Number(marketPrice))
+ ? `$${Number(marketPrice).toFixed(2)}`
+ : '—';
+
+ return (
+
+
-
-
-
-
- {SPOTLIGHT_CARD.name}
-
-
- {SPOTLIGHT_CARD.set}
-
+ {card.image_url ? (
+ // eslint-disable-next-line @next/next/no-img-element
+

+ ) : (
+
+ {card.name ?? 'Card'}
+ )}
+
+
+
+
+ {card.name}
+ {card.set_name && (
+
+ {card.set_name}
+
+ )}
- - Rarity
+ {card.rarity && (
+ <>
+ - Rarity
+ -
+ {card.rarity}
+
+ >
+ )}
+ {card.game && (
+ <>
+ - Game
+ -
+ {card.game}
+
+ >
+ )}
+ {card.quantity != null && (
+ <>
+ - Owned
+ -
+ {card.quantity}
+
+ >
+ )}
+ - Market
-
- {SPOTLIGHT_CARD.rarity}
-
- - Set
- -
- {SPOTLIGHT_CARD.set}
-
- - Collector #
- -
- {SPOTLIGHT_CARD.collectorNumber}
-
- - Condition
- -
- {SPOTLIGHT_CARD.condition}
+ {priceLabel}
-
-
-
- Market Value
-
-
-
- ${SPOTLIGHT_CARD.marketValue.toFixed(2)}
-
-
- {deltaPositive ? '▲' : '▼'} {SPOTLIGHT_CARD.delta} (
- {SPOTLIGHT_CARD.deltaPeriod})
-
-
-
-
-
-
- Price Trend (30 Days)
-
-
-
-
-
-
- Market Overview · 7-Day Change{' '}
- +6.42%
-
-
-
-
-
-
-
- Watchlist
-
-
-
-
- {WATCHLIST.map((row) => {
- const positive = row.delta.startsWith('+');
- return (
- -
-
-
-
- {row.name}
-
-
- {row.set}
-
-
-
-
- ${row.value.toFixed(2)}
-
-
- {row.delta}
-
-
-
- );
- })}
-
-
-
-
- {/* TODO(market-data convoy): replace static demo with real
- market-value + price-history + watchlist APIs. Charts use
- inline SVG (no chart library) per umbrella § 2 — that
- decision can be revisited when real data arrives. */}
- Demo data — connect to live market feed in a follow-up release.
-
);
}
diff --git a/components/DashboardFeaturedCollection.js b/components/DashboardFeaturedCollection.js
index 370a88a..94f3b63 100644
--- a/components/DashboardFeaturedCollection.js
+++ b/components/DashboardFeaturedCollection.js
@@ -1,30 +1,8 @@
import Link from 'next/link';
+import { VOCAB } from '../lib/collection-vocabulary.js';
/**
- * DashboardFeaturedCollection — the 4x2 card-thumbnail grid panel
- * from the operator's redesign-v2 mockup. Mounted on /dashboard
- * below the stat-card row.
- *
- * Per umbrella convoy § 7.5, the data source is the user's most-
- * recent 8 owned cards (fetched from /api/user-cards by the parent
- * page, passed in here as `cards`). When the user has <8 cards, the
- * empty slots render a clear "Add cards" CTA placeholder. No
- * hardcoded demo cards.
- *
- * Props:
- * cards: array of user_cards JOIN cards rows (or empty array)
- * — each row has at minimum { card_id, name, image_url,
- * set_name, rarity }.
- * loading: boolean — shows shimmer placeholders when true.
- *
- * Implementation notes:
- * - The mockup's "All Sets" filter dropdown and grid/list toggle
- * are intentionally NOT wired here; they're sketches in the
- * mockup and a downstream convoy will own them. Rendering them
- * as decoration with TODO comments would be misleading; they're
- * simply omitted until functional.
- * - Each card uses the .card-grid-outer-glow class from sub-convoy
- * #6 (PR #106) for the warm outer glow treatment.
+ * Recently added cards grid on /dashboard (formerly Featured Collection).
*/
export default function DashboardFeaturedCollection({ cards = [], loading = false }) {
const slots = Array.from({ length: 8 }, (_, idx) => cards[idx] || null);
@@ -48,7 +26,7 @@ export default function DashboardFeaturedCollection({ cards = [], loading = fals
- Featured Collection
+ Recently added
- View All
+ View all in {VOCAB.MY_COLLECTION}
@@ -100,14 +78,15 @@ export default function DashboardFeaturedCollection({ cards = [], loading = fals
d="M12 6v6m0 0v6m0-6h6m-6 0H6"
/>
-
Add Card
+
Add card
);
}
+ const cardId = card.card_id ?? card.id;
return (
0 ? activities : DEMO_ACTIVITIES;
- const usingDemo = !activities || activities.length === 0;
-
+export default function DashboardRecentActivity({ activities = [], loading = false }) {
return (
@@ -84,72 +28,75 @@ export default function DashboardRecentActivity({ activities }) {
- Recent Activity
+ Recent activity
-
+ View {VOCAB.MY_COLLECTION}
+
- {usingDemo && (
-
- {/* TODO(user-activity-feed convoy): replace these demo rows
- with rows from GET /api/user/activity once that endpoint
- ships. Operator-approved placeholder per umbrella § 7.1. */}
- Demo activity — connect to live feed in a follow-up release.
+ {loading ? (
+
+ {[0, 1, 2].map((idx) => (
+
+ ))}
+
+ ) : activities.length === 0 ? (
+
+ No recent adds yet. Scan a card or browse the catalog to start
+ building {VOCAB.MY_COLLECTION}.
- )}
-
-
- {rows.map((row) => (
- -
-
- {row.actor.initial}
-
-
-
-
- {row.time}
-
-
- ))}
-
+ {row.time}
+
+
+ ))}
+
+ )}
);
}
diff --git a/components/Layout.js b/components/Layout.js
index 03b77f2..e7fe4c1 100644
--- a/components/Layout.js
+++ b/components/Layout.js
@@ -6,6 +6,7 @@ import MobileNavigation from './MobileNavigation';
import DailyEmberWidget from './DailyEmberWidget';
import TopSearchBar from './ui/TopSearchBar';
import CommandPaletteModal from './ui/CommandPaletteModal';
+import { VOCAB } from '../lib/collection-vocabulary.js';
// User Profile Dropdown Component
function UserProfileDropdown({ user, onMobileMenuClose }) {
@@ -42,7 +43,7 @@ function UserProfileDropdown({ user, onMobileMenuClose }) {
{ name: 'Profile', href: '/profile', icon: 'user' },
{ name: 'Settings', href: '/settings', icon: 'settings' },
...(user?.role === 'admin' ? [
- { name: 'Admin Panel', href: '/admin', icon: 'admin' }
+ { name: 'Admin Tools', href: '/admin', icon: 'admin' }
] : []),
{ name: 'Logout', href: '/logout', icon: 'logout', isLogout: true }
];
@@ -174,38 +175,68 @@ function NavigationContent({ user, router, onItemClick }) {
setLastCommunityPath(router.pathname);
}
- // Navigation structure for authenticated users
- const authenticatedNavigation = user ? [
- { name: 'Activity', href: '/activity', icon: 'activity', active: router.pathname === '/activity', isPlaceholder: true }
- ] : [];
+ // Flat product nav — dashboard-home-realignment convoy (2026-08-15).
+ // Primary destinations for signed-in users; secondary catalog +
+ // community below a divider. Admin lives in TopSearchBar UserMenu
+ // only — not duplicated here.
+ const primaryNavigation = user
+ ? [
+ {
+ name: 'Dashboard',
+ href: '/dashboard',
+ icon: 'grid',
+ active: router.pathname === '/dashboard',
+ },
+ {
+ name: VOCAB.MY_COLLECTION,
+ href: '/my-cards',
+ icon: 'collection',
+ active: router.pathname === '/my-cards',
+ },
+ {
+ name: VOCAB.LISTS,
+ href: '/collections',
+ icon: 'lists',
+ active:
+ router.pathname === '/collections' ||
+ router.pathname.startsWith('/collection/'),
+ },
+ {
+ name: 'Decks',
+ href: '/decks',
+ icon: 'deck',
+ active:
+ router.pathname === '/decks' ||
+ router.pathname.startsWith('/deck/'),
+ },
+ {
+ name: 'Scanner',
+ href: '/scanner',
+ icon: 'scanner',
+ active: router.pathname === '/scanner',
+ },
+ ]
+ : [];
- // My Collection section (only for authenticated users)
- const myCollectionNavigation = user ? {
- name: 'My Collection',
- href: '/dashboard',
- icon: 'collection',
- active: router.pathname === '/dashboard' || router.pathname === '/collections' || router.pathname === '/my-cards' || router.pathname === '/decks' || router.pathname === '/analytics',
- expanded: true,
- items: [
- { name: 'Lists', href: '/collections', active: router.pathname === '/collections' },
- { name: 'Cards', href: '/my-cards', active: router.pathname === '/my-cards' },
- { name: 'Decks', href: '/decks', active: router.pathname === '/decks' },
- { name: 'Analytics', href: '/analytics', active: router.pathname === '/analytics', isPlaceholder: true }
- ]
- } : null;
-
- // Always visible navigation (public + authenticated)
- const publicNavigation = [
- { name: 'Cards', href: '/cards', icon: 'card', active: router.pathname === '/cards' },
- { name: 'Scanner', href: '/scanner', icon: 'scanner', active: router.pathname === '/scanner' },
- { name: 'Deck Builder', href: '/deck-builder', icon: 'deck', active: router.pathname === '/deck-builder', isPlaceholder: true }
+ const secondaryNavigation = [
+ {
+ name: 'Cards',
+ href: '/cards',
+ icon: 'card',
+ active: router.pathname === '/cards',
+ },
+ ...(user
+ ? []
+ : [
+ {
+ name: 'Scanner',
+ href: '/scanner',
+ icon: 'scanner',
+ active: router.pathname === '/scanner',
+ },
+ ]),
];
- // Admin navigation (only for admin users)
- const adminNavigation = (user?.role === 'admin') ? [
- { name: 'Admin Tools', href: '/admin/card-editor', icon: 'admin', active: router.pathname.startsWith('/admin'), badge: 'ADMIN' }
- ] : [];
-
const communityNavigation = {
name: 'Community',
icon: 'community',
@@ -230,6 +261,11 @@ function NavigationContent({ user, router, onItemClick }) {
),
+ lists: (
+
+ ),
card: (