diff --git a/.cursor/rules/ui-and-theming.mdc b/.cursor/rules/ui-and-theming.mdc index 7ab5134..ffafb3f 100644 --- a/.cursor/rules/ui-and-theming.mdc +++ b/.cursor/rules/ui-and-theming.mdc @@ -59,3 +59,11 @@ Don't duplicate navigation in a page — extend `NavigationContent` inside Layou ## Branding The canonical product brand is **Deck Hearth** (two words, internal cap), ratified 2026-05-24 in the `pick-a-name` convoy. The repo directory + GitHub project name remain `tcg-vault` until the queued `rename-repo-and-vercel-project` convoy ships. New UI copy MUST use `Deck Hearth` verbatim — do not introduce a third name, do not abbreviate to "DH" outside the logo glyph (currently used in `components/Layout.js` lines 621, 714 as the sidebar logo monogram). + +## Product vocabulary (ownership vs lists) + +- **My Collection** — global ownership (`user_cards`, `/my-cards`). Never say "Owned Cards" or "Mark Owned" in UI. +- **Lists** — curated binders (`collections` table, `/collections`). Say "List" in buttons/modals; URL stays `/collections` until a future slug convoy. +- **Synced binder** — display name for the system collection (`is_system_collection`); DB literal `'All My Cards'` is internal only. +- Import copy from `lib/collection-vocabulary.js` (`VOCAB`, `collectionDisplayName`, `formatProcessedDestination`). +- CI `forbidden-stale-strings` enforces the three retired phrases in `pages/` + `components/`. diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6e18782..c99febe 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -208,6 +208,40 @@ jobs: fi echo "OK: no client-side LLM key leakage patterns detected." + forbidden-stale-strings: + name: No stale ownership/collection copy + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Fail if forbidden UI strings appear in pages/ or components/ + run: | + # rename-collections-vocabulary convoy — ownership vs curated-list taxonomy. + # API/DB literals (e.g. system collection name) live under pages/api/ only. + PATTERNS=( + 'Mark Owned' + 'Owned Cards' + 'All My Cards' + ) + FOUND=() + for pattern in "${PATTERNS[@]}"; do + while IFS= read -r line; do + FOUND+=("$line") + done < <(grep -rFn "$pattern" pages/ components/ \ + --include='*.js' \ + --exclude-dir=api 2>/dev/null || true) + done + if [ ${#FOUND[@]} -gt 0 ]; then + echo "::error::Forbidden stale UI string(s) in pages/ or components/. Use lib/collection-vocabulary.js labels — see AGENTS.md § Product vocabulary." + printf '%s\n' "${FOUND[@]}" | sort -u | while IFS= read -r line; do + file=$(echo "$line" | cut -d: -f1) + lineno=$(echo "$line" | cut -d: -f2) + text=$(echo "$line" | cut -d: -f3-) + echo "::error file=${file},line=${lineno}::${text}" + done + exit 1 + fi + echo "OK: no forbidden stale strings in pages/ or components/." + test: name: Unit tests (vitest) runs-on: ubuntu-latest diff --git a/AGENTS.md b/AGENTS.md index ca78b54..a34a4fd 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -12,6 +12,20 @@ Guidance for agents and humans working in this repo. Prefer existing patterns ov > as a historical regression-lock per Risk 4 of the pick-a-name > convoy. +## Product vocabulary + +User-facing copy distinguishes **ownership** (everything you own) from **curated lists** (binders/subsets). Import labels from `lib/collection-vocabulary.js` (`VOCAB`, `collectionDisplayName`) rather than hardcoding strings. + +| Concept | UI label | Route / schema | Notes | +| --- | --- | --- | --- | +| Global ownership | **My Collection** | `/my-cards`, `user_cards` | Scanner default destination; replaces "Owned" / "Mark Owned" | +| Curated list / binder | **List** / **Lists** | `/collections`, `/collection/[id]`, `collections` table | URL slug unchanged in v1; nav says "Lists" | +| Auto-sync system list | **Synced binder** (display) | `collections` row with `is_system_collection = true` | DB name stays `'All My Cards'` — never render; use `collectionDisplayName()` | +| Add ownership | **Add to My Collection** | `POST /api/user-cards`, scanner bulk | Replaces "Mark Owned" / "Mark as Owned" | +| Add to curated list | **Add to List** | `POST /api/collections/:id/cards` | Replaces "Add to Collection" in scanner/card flows | + +CI job `forbidden-stale-strings` blocks `"Mark Owned"`, `"Owned Cards"`, and `"All My Cards"` in `pages/` + `components/` (API literals exempt). + ## 1. Project overview A web app for managing trading-card-game collections (Magic, Pokémon, Lorcana). Users authenticate, build collections + decks, scan physical cards via a camera+AI-OCR flow, and share publicly. Admin users curate the card database. diff --git a/components/BulkSelectionToolbar.js b/components/BulkSelectionToolbar.js index f135307..21438a7 100644 --- a/components/BulkSelectionToolbar.js +++ b/components/BulkSelectionToolbar.js @@ -1,4 +1,5 @@ import { useState } from 'react'; +import { VOCAB } from '../lib/collection-vocabulary.js'; export default function BulkSelectionToolbar({ selectedCards, @@ -96,7 +97,7 @@ export default function BulkSelectionToolbar({ - Own + {VOCAB.MY_COLLECTION} @@ -137,7 +138,7 @@ export default function BulkSelectionToolbar({ - Remove from Owned + {VOCAB.REMOVE_FROM_MY_COLLECTION}
diff --git a/components/CardItem.js b/components/CardItem.js index bc2291e..6ce70ee 100644 --- a/components/CardItem.js +++ b/components/CardItem.js @@ -1,5 +1,6 @@ import { useState } from 'react'; import { useRouter } from 'next/router'; +import { VOCAB } from '../lib/collection-vocabulary.js'; // Expanding Add Button Component function ExpandingAddButton({ card, onAddToCollection, onAddToDeck, onMarkAsOwned }) { @@ -31,7 +32,7 @@ function ExpandingAddButton({ card, onAddToCollection, onAddToDeck, onMarkAsOwne onClick={(e) => handleOptionClick(e, () => onAddToCollection([card]))} className="p-1.5 rounded-md shadow-lg transition-all duration-150 hover:scale-105 flex items-center space-x-1 text-xs whitespace-nowrap" style={{ backgroundColor: 'var(--accent-flame)', color: 'white' }} - title="Add to Collection" + title={VOCAB.ADD_TO_LIST} >