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 ( -