Commit graph

127 commits

Author SHA1 Message Date
Randall Stillwell
6b70fd9bd1 refactor(scanner): extract session and route API libs (page Brief 1)
Move scanner session persistence, queue merge helpers, and destination
routing fetch calls into lib/scanner-session.js and lib/scanner-route-api.js.
Load collections/decks on mount (were defined but never invoked).
Remove unused mana-symbol imports and dead select-all helpers.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-02 16:07:33 -05:00
varutasu
b615fac865
refactor(auth): add withAdmin() wrapper for admin API routes. (#68)
Extract shared 401/403 gate into permission-middleware and sweep the
four inline admin checks (import MTG/Pokemon, sync-catalog, card-submissions).

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-02 12:42:22 -05:00
varutasu
c32bbd19b6
fix(lint): clear ESLint baseline in pages/ (#62)
Resolve react-hooks, no-unescaped-entities, and no-img-element findings
under pages/ with lint-only changes so npx eslint pages/ exits clean.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-02 01:03:39 -05:00
varutasu
8262fec3e8
Remove dead Lorcana import route and CLI script (#59)
* Remove dead Lorcana import route and CLI script

The admin card-import UI never wired Lorcana; catalog sync uses
pages/api/admin instead. Drop the unused API route, CLI helper, and
stale docs references to import-lorcana.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Remove orphaned import-lorcana-simple CLI script.

It POSTed to the deleted /api/cards/import-lorcana endpoint; no remaining callers.

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-02 00:42:18 -05:00
varutasu
e0218e4b05
Remove Quick Login + scanner a11y polish (#56)
Drop alice/bob password prefill from the login page, add a regression
test, and improve bulk-toolbar and disambiguation accessible names.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-29 22:58:41 -05:00
varutasu
c197dc61ed
Vocabulary cleanup follow-up (#55)
Correct dashboard title (My Collection overview, not Lists), sweep
remaining marketing/auth copy, update system-list seed description,
add vocabulary unit tests, and close the convoy record.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-29 10:01:03 -05:00
varutasu
fd781140e5
Align UI copy: My Collection vs Lists (#54)
* Align UI copy with My Collection vs Lists vocabulary.

Replace stale ownership/list labels across pages and components, add
lib/collection-vocabulary.js as the single copy source, document the
taxonomy in AGENTS.md, and gate retired strings in CI.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Fix remaining list/collection copy gaps from review.

Sweep community, settings, share modal, scanner create-list modal,
and invite flows for vocabulary consistency before merge.

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-29 09:53:40 -05:00
varutasu
c51ec6a04c
Normalize collector numbers in catalog match and harden scanner adds. (#53)
Share card-number normalization across reconcile and identify paths, retry set/name matches when OCR uses leading-zero collector numbers, and extend in-flight locks to all scanner destination actions with disabled Mark Owned feedback.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-28 14:45:55 -05:00
varutasu
cf5c0558f1
Auto-link pending scan submissions after catalog sync imports. (#51)
When a set lands via runCatalogSync, match pending card_submissions by set/name/number to catalog rows and approve them with promoted_card_id instead of leaving them in the admin queue.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-28 09:47:59 -05:00
varutasu
8f3fbe70a2
Add admin panel button to trigger catalog sync. (#50)
Expose POST /api/admin/sync-catalog for authenticated admins (import rate limit, 300s timeout) and wire a Run catalog sync control on /admin/card-import.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-28 09:18:01 -05:00
varutasu
0a47362103
feat(catalog): weekly Vercel Cron sync for MTG and Pokémon sets (#48)
Extract shared import logic into lib/card-import, discover missing sets via
Scryfall/Pokémon TCG APIs, and expose GET /api/cron/sync-catalog protected
by CRON_SECRET (max 3 sets/run, paced imports).

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-27 14:59:59 -05:00
varutasu
30b21b42c5
fix(api): validate quantity on POST /api/user-cards (#46)
Match decks handler parseInt/NaN guard so non-numeric quantities cannot
corrupt user_cards row counts from scanner add paths.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-27 14:25:20 -05:00
varutasu
66717c4198
fix(scanner): close redesign a11y audit findings (#45)
Add focus traps for modals, accessible names for icon/select controls,
ownership badge role=status, list semantics for the scan queue, and
aria-live updates for the card count.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-27 14:22:18 -05:00
varutasu
673af83519
feat(scanner): persist scan captures to Blob (Brief 3) (#44)
Upload confirmed scan frames to Vercel Blob and store the URL on user_cards
when routing to owned cards, completing the redesign-scanner-flow convoy.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-27 13:57:26 -05:00
varutasu
24c9da4095
feat(scanner): condition, foil, quantity, and ownership badge (Brief 2) (#43)
Extract ScannedCardItem with per-card metadata controls and ownership
lookup via GET /api/cards/[id]/ownership. Propagate condition, foil,
and quantity through owned/collection/deck POST paths.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-27 13:54:33 -05:00
varutasu
47a1abbe4d
feat(scanner): stack-destination picker and auto-route (Brief 1) (#42)
Add ScannerDestinationPicker with game filter and owned/collection/deck
targets. Persist session destination in localStorage and auto-add each
identified scan to the active destination.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-27 13:50:54 -05:00
varutasu
55af7e3c90
fix(scanner): idempotent Mark-Owned and reliable bulk actions (#41)
Add per-row in-flight locks so double-tap cannot duplicate owned POSTs.
Pass bulk action/target directly instead of setTimeout state races.
Log collection card adds via logCollectionActivity and fix rows.length
checks in the collection cards POST handler.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-27 13:44:47 -05:00
varutasu
a251dacbd3
fix(scanner): catalog gap review path + not-listed disambiguation (#40)
When vision reads a set+number missing from the catalog, route to
card_submissions rather than sibling disambiguation. Adds a not-listed
modal action, background vision refine, foil-friendly prompt, and
submit-for-review API. Queues catalog-sync-vercel-cron convoy for later.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-27 13:42:51 -05:00
varutasu
d798e284c3
feat(scanner): AI Gateway vision + Layer-1 Tesseract/pg_trgm OCR (#38)
Route Layer-2 identification through Vercel AI Gateway (AI_GATEWAY_API_KEY,
default google/gemini-2.5-flash-lite). Add Layer-1 browser Tesseract name-strip
OCR with pg_trgm fuzzy catalog match via /api/cards/identify-by-text before
escalating to vision.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-27 12:59:59 -05:00
varutasu
be5dd8027f
fix(scanner): gemini-2.5-flash model + actionable scan error messages (#37)
Use the same vision model as the deleted browser client, surface Gemini
quota/denial/migration failures as 502/503 with readable text, and stop
scan_attempts telemetry from blocking identification.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-27 12:42:37 -05:00
varutasu
ecb3ee12fc
fix(auth): wait for verify before scanner redirect; fix admin hooks (#36)
Scanner was sending logged-in users to /login while useAuth was still
loading. Admin card-editor/card-import crashed on login because hooks
ran after a mounted early return (Rules of Hooks violation).

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-27 12:36:58 -05:00
varutasu
e81dd49752
feat(scanner): server-side scan pipeline (convoy #2) (#35)
* feat(scanner): move card identification server-side (convoy #2)

Replace browser Gemini/OCR with POST /api/scan/identify, add card_submissions
review queue, remove user-writable cards INSERT, and surface disambiguation
when catalog matching is ambiguous.

Co-authored-by: Cursor <cursoragent@cursor.com>

* ci: allowlist server-only lib/scan-gemini.js in LLM key gate

The scan pipeline helper lives under lib/ but is imported exclusively
from pages/api/scan/identify — exclude it from the client-side URL scan.

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-27 08:47:05 -05:00
varutasu
8c58990fd9
fix(security): stop leaking Gemini API key to browsers (#34)
Delete the public /api/config/gemini endpoint and remove client auto-load
paths so GEMINI_AI_API_KEY stays server-side only. Add a scan rate-limit
class for the upcoming server-side identify route and a CI gate that blocks
reintroducing config key leaks or new browser LLM URLs.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-27 08:41:48 -05:00
varutasu
0668b0c7d0
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 22:58:08 -05:00
varutasu
c403ea49e8
refactor(db): collapse @neondatabase/serverless onto @vercel/postgres + delete lib/database.js (#30)
Convoy: single-sql-client (P1 quality, launch sequence step 8)
Addresses: AGENTS.md Gotcha #1, .convoys/ship-readiness.md P1 #8

## Decisions

- D1: Caller inventory = 2 files (1 source + 1 test), not "~3 based on graph".
  Only pages/api/auth-utils.js imports `db`; test/api/auth-utils.test.js mocks
  it purely to satisfy the import graph (the 5 tests exercise
  generateToken/verifyToken, not isAdmin/getUserById).
- D2: Migrate both call sites (isAdmin, getUserById) to @vercel/postgres
  tagged-template SQL. Queries are SELECT-only, single-table,
  single-numeric-parameter — byte-equivalent translation; same result shape
  ({rows, rowCount}); no transaction or pool semantics differ.
- D3: KEEP @neondatabase/serverless as a dep. 11 scripts/* files still use
  `neon()` directly (setup-neon-db.js, migrations/, reset-db.js, 8 historical
  add-*/fix-*/seed-* jobs). They are out of scope per the no-go-zones rule
  and the convoy spec; purging the dep entirely would be its own convoy
  (queued as `purge-neondatabase-serverless-fully`, blocked on migration-tool).
- D4: sql.unsafe audit — NOT a real injection vector with current callers
  (userId comes from a verified JWT, is a numeric SERIAL id). Security
  finding: NO. Pure refactor + foot-gun removal that prevents the FUTURE
  caller that would have been the incident.
- D5: Test mock cleanup — drop the now-unneeded `vi.mock('../../lib/database.js')`
  call + unused `vi` import. Test count + assertions unchanged (5/5).

## Per-file changes

- pages/api/auth-utils.js: swap `import { db } from '../../lib/database.js'`
  for `import { sql } from '@vercel/postgres'`; rewrite isAdmin's
  `db.query(SELECT … WHERE id = $1, [userId])` and getUserById's same shape
  to `sql\`SELECT … WHERE id = ${userId}\``. Same try/catch, same
  result.rows[0] access, same error returns.
- test/api/auth-utils.test.js: drop vi.mock for lib/database.js + the unused
  `vi` import. 5/5 tests still pass.
- lib/database.js: DELETED (47 lines removed; manual-interpolation + sql.unsafe
  wrapper is gone).
- .convoys/single-sql-client.md: NEW (the convoy file documenting all
  decisions + caller inventory + verification + risks + follow-ups).

## Verification

- npm run lint → 128 problems (baseline preserved, no regression)
- npm run test:run → 21/21 pass (vitest)
- Grep "lib/database" --type js -l → 0 hits anywhere
- Grep "@neondatabase/serverless" --type js -l → still matches the 11
  scripts/* sites (expected; out of scope per D3)
- node --check pages/api/auth-utils.js → exit 0

## Scope note

This convoy collapses the lib/database.js abstraction onto the canonical
@vercel/postgres surface for pages/api/**. It does NOT eliminate
@neondatabase/serverless from the dependency tree — that would require
migrating the scripts/* helpers, which is out of scope here (no-go-zones
rule + convoy spec). Queued as a follow-up.

## Live smoke

Deferred. The two migrated functions (isAdmin, getUserById) are only
reachable via pages/api/admin/index.js which requires an admin Bearer
token and a populated users table in prod Neon. Byte-equivalent SQL +
identical result shape gives high confidence; rollback is a single-commit
revert if a post-merge admin action 500s.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-26 22:54:01 -05:00
varutasu
9abbab6c21
feat(brand): unify on Deck Hearth across in-repo strings + infra (P1 brand decision)
Resolves the launch-blocking 'TCG Vault vs Deck Hearth' inconsistency called out in AGENTS.md line 5 since project setup. Operator gate-0 decision: Deck Hearth wins. Two briefs applied serially. B1 (mechanical): 7-file display + comment sweep. B2 (infrastructure): Redis prefix rename in lib/rate-limit.js (5 prefixes, accept one-time counter reset), package.json + lockfile regen (STOP-on-churn confirmed only name lines changed), admin/alice/bob email rename in seed scripts + login pre-fill + NEW idempotent migration script scripts/migrations/2026-05-24-rename-admin-email.js. Risk 4 PRESERVE applied: test/lib/permission-middleware.test.js retains admin@tcgvault.com literal with 7-line architect-authored why comment (documents pre-fix-auth-bypass bug shape; preserves historical truth per project's gotcha-documentation convention). All 5 D-decisions ratified at gate-1 (Deck Hearth / deck-hearth / deckhearth / admin@deckhearth.com / full deckhearth Redis prefix). Local: lint 128 baseline (B1 + B2), vitest 21/21 (B1 + B2). CI all green: Playwright smoke 3/3 against rebranded preview in 1m4s, forbidden-cors-headers pass, forbidden-endpoints pass, Screenshot diff pass, Vercel deployment complete. Cross-validation lineage: 4th convoy where the same 3-test smoke spec defends auth surface through sweeping change (after PR #15 Layout default-user, PR #19 CORS, PR #20 rate-limit, now this PR #21 brand rename). OPERATOR POST-MERGE ACTION REQUIRED: run 'node scripts/migrations/2026-05-24-rename-admin-email.js' against prod Neon DB before next admin login (ordering: migration FIRST, then any subsequent setup-db invocation). Migration is ESM, idempotent, UNIQUE-collision-safe. PR #21 architect-commit 50ce9ab, B1 ac8c998, B2 1c18d21.
2026-05-25 02:28:29 -05:00
varutasu
708ef45a96
feat(security): rate-limit search/upload/import + gate import routes (P0 #6 - closes last P0)
Closes P0 #6 from PARTIAL to RESOLVED. 8/8 P0s now closed. Extends lib/rate-limit.js from single-class to 5 named limiters (auth/search/upload/generate/import). Atomically gates the 3 import routes (auth + admin-role check + rate limit) and fixes pages/admin/card-import.js's missing Bearer header in the same commit (architect's critical discovery: API gating alone would have broken the admin UI). Per Decision 1 Option A. 10 files +185/-23. Local: lint 128 baseline, vitest 21/21. CI: Playwright smoke 3/3 in 3.8s, forbidden-cors-headers pass, all gates green. PR #20 architect-commit 60b842e, implementer-commit 51a3a97. Brief 4's login.js + register.js byte-identical.
2026-05-24 22:59:59 -05:00
varutasu
da50d78406
fix(security): drop wildcard CORS + redundant OPTIONS from 24 API routes (P0 #5)
Closes P0 #5 from PARTIAL to RESOLVED. Sweeps the remaining 24 pages/api/** handlers that carried the identical scaffolded wildcard-CORS + OPTIONS preflight pattern (Brief 4 cleaned login + register; this finishes the job). Adds a blocking forbidden-cors-headers CI job modeled on forbidden-endpoints to lock the cleanup against future regression. 25 files changed (+29/-261). Local: lint 128 baseline, vitest 21/21, zero CORS matches, YAML valid. CI: Playwright smoke 3/3 in 3.3s against post-removal preview (login/verify flow still works), new forbidden-cors-headers job passes in 4s, all gates green. PR #19 architect-commit ec22b70, implementer-commit a843736.
2026-05-24 20:41:38 -05:00
varutasu
ca302a89c1
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 14:31:37 -05:00
Randall Stillwell
1fca3aa1ca fix(api): return 401 (not 500) on unauthenticated cards-collection writes
Follow-up to fix-auth-bypass Brief 2 (commit 258e479). Brief 2 made
getUserFromRequest return null for unauthenticated requests. POST, PUT,
and DELETE branches of pages/api/collections/[identifier]/cards.js
were dereferencing user.userId without a guard → NPE → HTTP 500.

Security side was already fixed by Brief 2 (no more
anonymous-write-as-admin on collections owned by userId: 1). This patch
adds the cosmetic 500 → 401 cleanup the Brief 2 reviewer flagged.

Three identical 'if (!user) return 401' guards added, one per write
branch. GET branch was already guarded via the ternary pattern.

Sibling endpoints under pages/api/collections/** were re-audited by the
implementer and confirmed correctly guarded (thumbnails, permissions,
activity all have early null checks; [identifier].js uses optional
chaining throughout). No further hotfixes needed for that route group.

Convoy: fix-auth-bypass / Brief 6 (post-architect hotfix)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-23 11:04:55 -05:00
Randall Stillwell
297afca1ae fix(auth): tighten public auth surface — CORS + rate limit (Brief 4 of fix-auth-bypass)
Adds rate limiting to /api/auth/login and /api/auth/register and removes
their wide-open CORS allowlist.

Rate limiting (@upstash/ratelimit + @upstash/redis):
  - 5 attempts per 15-minute sliding window per IP, prefix "tcgvault:auth"
  - new lib/rate-limit.js, lazy singleton, single source of truth
  - reads KV_REST_API_URL / KV_REST_API_TOKEN (Vercel Upstash Marketplace
    convention — auto-provisioned, no manual env-var setup needed)
  - fail-closed in production if env vars are missing (better to error
    one login than silently disable brute-force protection on live)
  - fail-open in dev/test if env vars are missing (single console.warn)
  - fail-open on Upstash backend outage (defense-in-depth — don't lock
    the entire userbase out if Upstash is down)
  - IP extracted from x-forwarded-for first hop, with socket fallback;
    NOT req.body.email (rotates) or Authorization header (absent on
    unauthenticated login)

CORS:
  - Removed Access-Control-Allow-Origin: * + companion headers + OPTIONS
    preflight from login.js and register.js
  - These are first-party endpoints called from the same-origin SPA; the
    "*" allowlist was a development convenience that shipped to prod
  - verify.js is OUT OF SCOPE per architect's "cors-tighten" deferral
    (see convoy plan § Architect's calls)

Other handler ordering preserved verbatim per brief: method gate first,
then rate-limit check (returns 429 with Retry-After header), then the
existing try/catch + body parsing + DB work.

Pre-merge requirements: KV_REST_API_URL + KV_REST_API_TOKEN must be set
in Vercel Production (already done — Upstash marketplace integration
auto-provisioned both, confirmed by maintainer 2026-05-23).

Convoy: fix-auth-bypass / Brief 4
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-23 10:57:49 -05:00
Randall Stillwell
258e479dc5 fix(auth): remove synthetic-admin bypass (Brief 2 of fix-auth-bypass)
Closes AGENTS.md gotcha #2: getUserFromRequest no longer returns a
hardcoded { userId: 1, email: 'admin@tcgvault.com', role: 'admin' }
when the Authorization header is missing or malformed.

lib/permission-middleware.js
  - getUserFromRequest now returns null for missing/malformed Bearer
    headers. No console.warn, no NODE_ENV gate — the fallback is gone,
    period.
  - Token-verify path and DB lookup unchanged.

pages/api/auth/verify.js
  - No-token branch now returns 401 instead of fetching the seed admin
    via `WHERE email = 'admin@tcgvault.com'`. Closes the admin-record-
    leak side of the same bypass.
  - JWT-verify branch unchanged.

Known follow-up (flagged but NOT addressed in this PR):
  pages/api/collections/[identifier]/cards.js POST/PUT/DELETE handlers
  dereference user.userId without a null guard. Previously masked by
  the synthetic admin (anonymous-write-as-admin on collections owned
  by user 1 was the security hole). Now degrades to NPE → 500 instead
  of a clean 401. Security is improved either way; cosmetic 500-vs-401
  fix lives in a separate one-line follow-up PR.

Convoy: fix-auth-bypass / Brief 2
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-23 10:57:23 -05:00
Randall Stillwell
4a10dcedd3 fix(auth): centralize JWT secret + 24h TTL (Brief 1 of fix-auth-bypass)
- New `lib/auth-secret.js` is the single source of truth for `JWT_SECRET`
  and the canonical `JWT_TOKEN_TTL = '24h'`. Module throws at import time
  if `process.env.JWT_SECRET` is unset — no silent fallback to the literal
  `'your-secret-key-change-in-production'`.

- 7 callers refactored to import from the helper:
    lib/permission-middleware.js
    pages/api/auth-utils.js   (also drops unused `'7d'` → JWT_TOKEN_TTL)
    pages/api/auth/login.js   (also routes via auth-utils.generateToken)
    pages/api/auth/register.js (same)
    pages/api/auth/verify.js  (Brief 2 still owns the no-token admin branch)
    pages/api/favorites.js
    pages/api/users/search.js

- `process.env.JWT_SECRET` now appears exactly once in the JS source
  (lib/auth-secret.js). `your-secret-key-change-in-production` is gone.

- TTL drift reconciled: auth-utils used `'7d'`, login/register used
  inline `'24h'`. Both now route through imported `JWT_TOKEN_TTL` (24h).

Pre-deploy reminder: Vercel must have `JWT_SECRET` set before merge or
serverless functions refuse to boot. Existing tokens (signed against the
fallback literal) will be invalidated — users will need to log in again.

Resolves AGENTS.md gotcha #3. Brief 2/3/4/5 still pending in convoy.

Convoy: fix-auth-bypass / Brief 1
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-23 10:40:50 -05:00
Randall Stillwell
fc0dd73fdc fix(api): delete dev endpoints + CI guard (Brief 3 of fix-auth-bypass)
Removes four unauthenticated dev endpoints that were shipped to production:

- pages/api/simple.js          (info leak)
- pages/api/test-auth.js       (auth diagnostic / token-mint side door)
- pages/api/test-db.js         (DB connection diagnostic)
- pages/api/setup-database.js  (public POST that ran DDL + seeded admin)

setup-database is the highest-impact removal: it was a public endpoint
that triggered schema bootstrap and seeded the default admin credentials
(admin@tcgvault.com / admin123). AGENTS.md gotcha #5.

Also adds a new `forbidden-endpoints` job to .github/workflows/ci.yml
that fails the build if any of the four deleted paths re-appear OR if
any new pages/api/test-*.js file is added. Cheap insurance against a
future agent re-introducing a dev endpoint from an outdated tutorial.

README: drops the single `GET /api/test-db` line under "Health Check".
Rest of the API list is intentionally left for the doc-writer pass.

Verified locally:
- npm run build exits 0 (no source callers — confirmed via grep across
  pages/, components/, lib/)
- CI guard local simulation: clean → OK; with test-fake.js → FAIL; OK
  after cleanup

Resolves AGENTS.md gotcha #5. Brief 1/2/4/5 still pending in convoy.

Convoy: fix-auth-bypass / Brief 3
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-23 10:40:44 -05:00
Randall Stillwell
061fb90b0a Marker: Fixed card queue property mismatch - cards now properly separate in scanner queue 2025-08-01 17:41:42 -05:00
Randall Stillwell
afb79c57d9 Major Scanner Improvements
🔧 Gemini AI Integration:
- Added Google Gemini API as default OCR service
- Auto-configures from GEMINI_AI_API_KEY environment variable
- Fixed Puter.js authentication issues
- Enhanced OCR settings with connection testing

🎨 Redesigned Scanner Queue:
- New thumbnail + content layout with checkbox overlay
- Smart quantity management (duplicates increment quantity)
- Complete card information display from database
- Two-row action layout (primary/secondary actions)
- Floating bottom toolbar for bulk actions
- Real card images from database

�� Enhanced User Experience:
- Fixed Canvas2D performance warnings
- Better error handling and fallbacks
- Improved responsive design
- Database confirmation indicators
- Professional card scanning workflow

📱 Mobile Ready:
- Optimized layouts for mobile scanning
- Touch-friendly controls and interactions
- Improved visual feedback and status indicators
2025-07-29 14:19:48 -05:00
Randall Stillwell
b3240dbb3c 🎨 Enhanced Signup with Username & Profile Images
 New Signup Features:
- Added username field with validation (3+ chars, alphanumeric + underscore)
- Profile image upload with file validation (5MB max)
- DiceBear Adventurer Neutral API integration for random avatars
- Generate new random avatar button with dice emoji
- Initial random avatar generation on page load

🔧 Backend Updates:
- Updated registration API to handle all new fields
- Username uniqueness validation with specific error messages
- Profile image URL storage in database
- Enhanced user response with all profile data

🗄️ Database Migration:
- Added first_name, last_name, username, profile_image_url columns
- Unique constraint on username field
- Migration script with existing user updates
- Default values for existing accounts

🎯 User Experience:
- Real-time form validation with error states
- Loading states for image upload/generation
- File type and size validation
- Clean profile image preview with rounded borders
- Consistent styling with existing theme

Ready for enhanced user profiles! 🚀
2025-07-28 11:18:58 -05:00
Randall Stillwell
887a9bc285 Clean Up Login & Add Signup Flow
🧹 Login Page Cleanup:
- Removed admin login account (keeping Alice & Bob for testing)
- Deleted the Testing Accounts box at the bottom
- Improved quick login button layout (2 columns instead of 3)
- Added signup link with consistent styling

📝 New Signup Page:
- Complete registration form with validation
- First name, last name, email, password fields
- Password confirmation with matching validation
- Real-time form validation with error messages
- Consistent styling with login page
- Link back to login page

🎨 Enhanced UX:
- Form validation with red borders for errors
- Loading states for both login and signup
- Proper error handling and display
- Clean navigation between login/signup
- Consistent gradient text styling

Ready for user registration! 🚀
2025-07-28 11:09:58 -05:00
Randall Stillwell
cdb2e5f8ac 🔥 Add Animated Fire Logo Component
 Beautiful Animated Fire Logo:
- Created AnimatedFireLogo component based on CodePen animation
- Realistic fire flickering with multiple flame layers
- Theme-aware colors (bright for dark mode, warm for light mode)
- Floating particle effects with individual animations
- Scalable size prop for different use cases

🎨 Enhanced Login Experience:
- Replaced static fire emoji with animated logo
- 100px size for prominent branding
- Smooth flickering animations at different speeds
- Wood base and floating sparks for realism
- Perfect integration with fire glow background

🌙 Theme Support:
- Dark mode: Bright yellows and oranges for visibility
- Light mode: Warm browns and golds for elegance
- Consistent with Deck Hearth fire theme
- CSS-in-JS for dynamic theming

The login page now has a mesmerizing animated fire logo that perfectly captures the Deck Hearth brand
2025-07-28 10:08:24 -05:00
Randall Stillwell
2163b9ea0e 🔥 Add Fire Glow Login Background
 Beautiful Animated Fire Glow:
- Slow-moving fire gradient background with light/dark modes
- Floating ember particles with realistic animation
- 12-second background animation cycle with subtle color shifts
- Theme-aware gradient colors (warm daylight vs cozy evening)

🎨 Enhanced Login Experience:
- Updated branding to Deck Hearth with fire emoji
- Backdrop blur effects on form elements
- Semi-transparent containers for depth
- Orange focus states to match fire theme
- Enhanced shadows and glow effects

🌙 Theme Support:
- Light mode: Warm daylight fire with golden embers
- Dark mode: Cozy evening fire with bright orange flames
- RGB color variables for backdrop-blur compatibility
- Gradient-bg-ember class for consistent fire theming

The login page now perfectly captures the warm, inviting Deck Hearth atmosphere
2025-07-28 09:51:39 -05:00
Randall Stillwell
308d2de365 🧹 Major Codebase Cleanup: Remove Legacy React Code
🗑️ Removed Unused Files (11 files):
- 6 temporary import result JSON files
- 3 placeholder pages (community.js, analytics.js, decks.js)
- 3 unused components (CollaborationManager, ActivityLog, BulkInviteModal)
- 2 TypeScript config files (tsconfig.json, next-env.d.ts)

📦 Cleaned Up Dependencies:
- Removed 5 unused TypeScript packages
- Kept resend for future invite/notification features
- Removed 8 packages total, reduced bundle size

 Benefits:
- Cleaner codebase with only active files
- Reduced build time and bundle size
- Eliminated TypeScript overhead (project uses only JS)
- Removed legacy React patterns and unused components
- Better maintainability and clarity

The codebase is now lean and focused on active features
2025-07-28 09:07:36 -05:00
Randall Stillwell
6097af75a4 🎯 Refine Rarity Effects: Card-Focused Glow + Hero Particles
 Perfect Balance Achieved:
- Removed bold rarity gradient from hero background
- Hero now uses neutral theme-based gradient
- Added beautiful rarity glow effect around card image only
- Kept magical particle effects in full hero space

🎨 Card Glow System:
- Subtle blur glow behind card with rarity colors
- Enhanced box-shadow with rarity-specific colors
- Gentle pulsing animation for mystical effect
- Proper scaling and positioning for perfect visual balance

🌟 Improved Readability:
- Hero text now uses consistent theme colors
- No more contrast issues with bold backgrounds
- Clean, professional appearance with magical touches

The result: Subtle, elegant rarity indication focused on the card itself while maintaining the magical particle atmosphere
2025-07-28 08:56:42 -05:00
Randall Stillwell
2867a8ff23 Add Rarity-Based Gradients & Animated Particles
🎨 Rarity-Based Visual System:
- Replaced TCG-based gradients with subtle rarity-based backgrounds
- Common: Subtle gray gradient (no particles)
- Uncommon: Subtle green gradient (15 particles)
- Rare: Subtle gold gradient (25 particles)
- Mythic: Rich gold gradient (40 particles)
- Holographic: Subtle pink gradient (50 particles)
- Enchanted: Subtle purple gradient (60 particles)
- Super Rare: Subtle blue gradient (45 particles)
- Legendary: Vibrant gold gradient (80 particles)

 Animated Particle Effects:
- Floating particle animation with random positioning
- Particles match rarity colors with glowing effects
- Random animation delays and durations for natural movement
- More rare cards = more magical particle effects
- Particles are pointer-events-none (do not interfere with UI)

🎯 Smart Text Contrast:
- Light rarities (common/uncommon/rare) use dark text
- Dark rarities (mythic+) use white text for readability
- Automatic contrast adaptation based on background

🌟 Enhanced Atmosphere:
- Subtle background patterns (reduced opacity)
- Rarity-appropriate visual hierarchy
- Immersive, magical feel for rare cards

The hero section now creates a truly magical experience
2025-07-27 21:46:48 -05:00
Randall Stillwell
36fe4a6f0f 🔧 Fix Card Hero Section Invisible Text Issue
🐛 Root Cause:
- getTCGGradient() returned Tailwind classes (from-purple-600)
- CSS linear-gradient() received invalid syntax after string replacement
- Background gradient wasn't rendering, leaving white text on light background
- Text became completely invisible (only visible when highlighted)

 Solution:
- Fixed getTCGGradient() to return proper CSS color values
- Removed broken string replacement logic
- Used proper CSS gradient syntax: linear-gradient(135deg, #color1, #color2, #color3)
- White text now properly visible on colored gradient backgrounds

🎨 Color Improvements:
- MTG: Purple gradient (#9333ea, #8b5cf6, #4f46e5)
- Pokemon: Blue gradient (#2563eb, #3b82f6, #0891b2)
- Lorcana: Pink/Purple gradient (#db2777, #ec4899, #a855f7)
- Default: Gray gradient for unknown games

The card hero section should now be fully visible in all themes! 🌟
2025-07-27 21:43:06 -05:00
Randall Stillwell
b39f8b052d 🎨 Fix Card Detail Page Light Theme Issues
🐛 Theme Problems Fixed:
- Added proper background styling to ensure theme colors are applied
- Added missing gradient-text classes (gold, flame, ember) for fire theme
- Wrapped entire page in themed background container
- Ensured content tabs section uses theme colors

 Improvements:
- Added gradient-text-gold for price displays
- Added gradient-text-flame and gradient-text-ember for consistency
- Proper min-height to cover full viewport
- Background colors now properly inherit theme variables

🎯 Light Theme Fix:
- Text should now be properly dark in light mode
- Backgrounds use theme variables instead of defaults
- All sections properly themed for both light and dark modes

The card detail page should now be fully legible in light theme! 🌞
2025-07-27 21:36:40 -05:00
Randall Stillwell
eeee1c1f6b 🔧 Fix 'All My Cards' Collection Access Issue
🐛 Root Cause:
- Collection detail page was not sending auth token in API requests
- This caused the API to fallback to admin user authentication
- Bob's 'All My Cards' collection was inaccessible to admin user

 Solution:
- Added Authorization header to fetchCollectionData() function
- Added Authorization header to collection cards fetch request
- Both requests now properly authenticate as the logged-in user

🔍 Debug Results:
- Token verification was working correctly for other API calls
- Only the main collection fetch was missing authentication
- This explains the 404 error for system collections

The 'All My Cards' collection should now be accessible! 🚀
2025-07-27 21:30:32 -05:00
Randall Stillwell
7d385d1fa7 Enhanced Collection Detail Page
🎯 Edit/Delete Functionality:
- Edit/Delete buttons now visible in collection header
- Hidden for system collections (All My Cards)
- Only shown for collection owners
- Proper permission checks in place

🃏 Consistent Card Display:
- Replaced basic card tiles with full CardItem components
- Same hover effects and interactions as /cards page
- Selection, favorites, and action buttons work
- Responsive grid layout (2-7 columns based on screen size)
- Proper card interactions (favorite, select, add to collection/deck)

🔒 System Collection Styling:
- Added prominent SYSTEM badge in collection header
- Informative tooltip explaining auto-sync behavior
- Consistent styling with collections list page
- Clear visual distinction from regular collections

🎨 UI/UX Improvements:
- Better responsive grid layout for cards
- Proper state management for card interactions
- Consistent theming and styling
- Enhanced user feedback and visual hierarchy

Cards in collections now have the same rich interactions as the main cards page! 🚀
2025-07-27 20:58:18 -05:00
Randall Stillwell
fba8af1fe1 🔧 Fix SQL Template Literal Syntax Error
🐛 Critical SQL Fix:
- Fixed malformed template literal concatenation in /api/collections
- Replaced dynamic sql template concatenation with parameterized query
- Used sql.query() with proper parameter binding (, , )
- Resolved 'syntax error at or near ' database error

🎯 Query Structure:
- Maintains all existing functionality
- Proper excludeSystem parameter handling
- Clean parameterized query approach
- Better SQL injection protection

Collections page should now load properly! 🚀
2025-07-27 20:00:20 -05:00
Randall Stillwell
4689424f3a 🔧 Fix System Collections & Database Schema Issues
🐛 Database Schema Fixes:
- Removed non-existent 'updated_at' column from collection_cards operations
- Fixed SQL queries in card ownership API and seeding scripts
- Resolved column does not exist errors

🚫 Hide System Collections from Selection:
- Added 'excludeSystem' parameter to /api/collections endpoint
- Updated CollectionSelectionModal to exclude system collections
- 'All My Cards' no longer appears in card addition modals

 Enhanced System Collection Styling:
- Upgraded system collection badge with gradient styling
- Added 🔒 SYSTEM badge with blue-purple gradient
- Added informative tooltip: 'Automatically syncs with your owned cards'
- Made system collections visually distinct and educational

🎯 User Experience Improvements:
- System collections are now clearly identified as special
- Users understand they can't manually add cards to system collections
- Better visual hierarchy and information architecture
- Automatic sync behavior is now clearly communicated

Card ownership should now work without errors! 🚀
2025-07-27 19:17:55 -05:00
Randall Stillwell
9d7278f8f5 🔧 Fix Card Ownership & Auto-Sync with 'All My Cards'
🐛 Database Fixes:
- Added unique constraint on user_cards (user_id, card_id)
- Added unique constraint on collection_cards (collection_id, card_id)
- Fixed ON CONFLICT clauses in card ownership API

 Auto-Sync Feature:
- Card ownership now automatically syncs with 'All My Cards' collection
- When user marks card as owned → added to system collection
- When user removes ownership → removed from system collection
- Real-time bidirectional sync between user_cards and collection_cards

🔄 Migration Script:
- Cleaned up any duplicate entries
- Added necessary database constraints
- Synced existing owned cards (0 users had existing data)

🎯 API Improvements:
- Simplified card ownership API (removed GET method)
- Better error handling and validation
- Clear success messages for user feedback
- Automatic collection management

Card ownership should now work perfectly! 🚀
2025-07-27 15:21:43 -05:00