From f666a885b9fe9e53a310dc14330c7573523eb103 Mon Sep 17 00:00:00 2001 From: Randall Stillwell Date: Tue, 2 Jun 2026 08:48:42 -0500 Subject: [PATCH] chore(convoys): mark shipped convoys and refresh ship-readiness Close stale convoy frontmatter for merged scanner, lint, and hygiene work; record P1 #11.5 and queued follow-ups as RESOLVED with PR references. Co-authored-by: Cursor --- .convoys/add-real-ocr-layer.md | 4 +- .convoys/bump-next-js.md | 4 +- .convoys/catalog-sync-vercel-cron.md | 4 +- .convoys/drop-public-setup.md | 4 +- .convoys/fix-layout-default-user.md | 4 +- .convoys/lint-against-cjs-in-esm-scripts.md | 2 +- .convoys/purge-quick-login-from-loginpage.md | 6 +- .convoys/rename-collections-vocabulary.md | 5 +- .convoys/scanner-correctness-polish.md | 4 +- .convoys/server-side-scan-pipeline.md | 4 +- .convoys/ship-readiness.md | 100 ++++++------------- .convoys/single-sql-client.md | 2 +- .convoys/test-scanner-redesign-surfaces.md | 4 +- 13 files changed, 63 insertions(+), 84 deletions(-) diff --git a/.convoys/add-real-ocr-layer.md b/.convoys/add-real-ocr-layer.md index 709f155..5083c63 100644 --- a/.convoys/add-real-ocr-layer.md +++ b/.convoys/add-real-ocr-layer.md @@ -6,7 +6,7 @@ success_metric: | zero Gemini calls; scan_attempts.layer distribution proves it. skip: - ia -status: open +status: shipped created: 2026-05-27 depends_on: - server-side-scan-pipeline @@ -14,6 +14,8 @@ depends_on: # Convoy: add-real-ocr-layer +**As-shipped:** PR #38 (+ follow-up polish in PR #39). Layer-1 Tesseract + `pg_trgm` before Gemini escalation. + Add a cheap local OCR + fuzzy DB match layer so most scans never hit Gemini. ## Why diff --git a/.convoys/bump-next-js.md b/.convoys/bump-next-js.md index 896be02..a325e0d 100644 --- a/.convoys/bump-next-js.md +++ b/.convoys/bump-next-js.md @@ -6,12 +6,14 @@ skip: - ia - ux - flag -status: open +status: shipped created: 2026-05-22 --- # Convoy: bump-next-js +**As-shipped:** squash commit `e57ea17` (merged pre-PR-21, 2026-05-23). Closes P0 #8 (Next.js 15.4.3 → 16.2.6). + Closes P0 ship-blocker **#8** from `.convoys/ship-readiness.md`. Highest-priority convoy in the launch sequence — promoted to slot 0 because Vercel is currently refusing to deploy any branch (including `main`) until Next.js is bumped, which makes every downstream `preview-smoke` / `visual-diff` gate non-functional. ## Why diff --git a/.convoys/catalog-sync-vercel-cron.md b/.convoys/catalog-sync-vercel-cron.md index 9214dcc..425902e 100644 --- a/.convoys/catalog-sync-vercel-cron.md +++ b/.convoys/catalog-sync-vercel-cron.md @@ -11,7 +11,7 @@ skip: - visual - a11y - design -status: in-progress +status: shipped created: 2026-05-27 depends_on: - redesign-scanner-flow @@ -23,6 +23,8 @@ blocked_by_policy: | # Convoy: catalog-sync-vercel-cron +**As-shipped:** PRs #48–#52 (2026-05-27–2026-05-29). Weekly Vercel Cron catalog sync, shared import libs, admin trigger, submission auto-link. + Scheduled catalog freshness via **Vercel Cron** (not GitHub Actions — operator preference: already on Vercel paid plan; avoids GitHub Actions minute limits). diff --git a/.convoys/drop-public-setup.md b/.convoys/drop-public-setup.md index c869ba4..2823a0a 100644 --- a/.convoys/drop-public-setup.md +++ b/.convoys/drop-public-setup.md @@ -12,7 +12,7 @@ skip: - role-ux-reviewer - role-ia-architect - browser-smoke -status: open +status: shipped created: 2026-05-23 parent: ship-readiness addresses: P0 #3 @@ -23,6 +23,8 @@ depends_on: # Drop public setup +**As-shipped:** Brief 1 `ff80753` + Brief 2 `b63b509` (2026-05-23). Closes P0 #3. + Close P0 #3 from `.convoys/ship-readiness.md`: remove the hardcoded admin credentials (`admin@tcgvault.com` / `admin123`) from the seed script and the README. diff --git a/.convoys/fix-layout-default-user.md b/.convoys/fix-layout-default-user.md index 8cf20cc..8f75910 100644 --- a/.convoys/fix-layout-default-user.md +++ b/.convoys/fix-layout-default-user.md @@ -12,7 +12,7 @@ skip: - role-design-system-auditor # no design-token changes - role-ia-architect # no URL / IA changes - browser-smoke # local smoke is fine for this scope -status: open +status: shipped created: 2026-05-23 parent: ship-readiness addresses: P0 #7 @@ -24,6 +24,8 @@ depends_on: # Fix Layout default user +**As-shipped:** PR #15, squash `ca302a8` (2026-05-24). Closes P0 #7. + Close P0 #7 from `.convoys/ship-readiness.md` (the **last** remaining P0 ship-blocker). `components/Layout.js` line 562 defaults the `user` prop to `{ email: 'me@randallstillwell.com', role: 'user' }` — any page that renders diff --git a/.convoys/lint-against-cjs-in-esm-scripts.md b/.convoys/lint-against-cjs-in-esm-scripts.md index a5ad267..1d8115b 100644 --- a/.convoys/lint-against-cjs-in-esm-scripts.md +++ b/.convoys/lint-against-cjs-in-esm-scripts.md @@ -2,7 +2,7 @@ name: lint-against-cjs-in-esm-scripts classification: hygiene success_metric: future helper scripts that re-introduce CJS `require()` calls under `package.json` "type": "module" fail at lint time, not at first execution -status: open +status: shipped created: 2026-05-26 --- diff --git a/.convoys/purge-quick-login-from-loginpage.md b/.convoys/purge-quick-login-from-loginpage.md index 98a6c8a..e859160 100644 --- a/.convoys/purge-quick-login-from-loginpage.md +++ b/.convoys/purge-quick-login-from-loginpage.md @@ -4,14 +4,16 @@ classification: fix success_metric: | pages/login.js no longer ships alice123/bob123 in client HTML; smoke sign-in CTA unchanged. -status: closed +status: shipped created: 2026-05-26 closed: 2026-05-29 -pr: pending +pr: 56 --- # Convoy: purge-quick-login-from-loginpage +**As-shipped:** PR #56 (`e0218e4`, 2026-05-29). Quick Login removed from `pages/login.js` (bundled with scanner a11y polish). + Remove production Quick Login buttons that exposed test-user passwords in view-source HTML. diff --git a/.convoys/rename-collections-vocabulary.md b/.convoys/rename-collections-vocabulary.md index 1d10139..ecf56fa 100644 --- a/.convoys/rename-collections-vocabulary.md +++ b/.convoys/rename-collections-vocabulary.md @@ -8,7 +8,7 @@ success_metric: | with vocabulary table. skip: - arch -status: closed +status: shipped created: 2026-05-27 closed: 2026-05-29 pr: 54 @@ -18,8 +18,7 @@ pr: 54 Align user-facing copy with the product taxonomy: owned cards vs curated lists. -**Shipped:** PR #54 (squash `fd78114`, 2026-05-29). Follow-up cleanup tracked in -PR after deep review (dashboard h1, marketing copy, seed description). +**As-shipped:** PR #54 (squash `fd78114`, 2026-05-29) + vocabulary follow-up PR #55 (`c197dc6`, 2026-05-29). ## Why diff --git a/.convoys/scanner-correctness-polish.md b/.convoys/scanner-correctness-polish.md index 09f4bf6..51df8f0 100644 --- a/.convoys/scanner-correctness-polish.md +++ b/.convoys/scanner-correctness-polish.md @@ -12,12 +12,14 @@ skip: - visual - a11y - design -status: open +status: shipped created: 2026-05-27 --- # Convoy: scanner-correctness-polish +**As-shipped:** PR #41 (2026-05-27). Idempotent Mark-Owned, bulk toolbar race fix, `lib/use-auth` import, activity logging. + Fix scanner-page correctness bugs without changing UX or visual design. ## Why diff --git a/.convoys/server-side-scan-pipeline.md b/.convoys/server-side-scan-pipeline.md index 2b0295d..c80ae2e 100644 --- a/.convoys/server-side-scan-pipeline.md +++ b/.convoys/server-side-scan-pipeline.md @@ -7,7 +7,7 @@ success_metric: | user-writable cards INSERT); disambiguation envelope consumed by the client (needsUserSelection no longer silently swallowed). skip: [] -status: open +status: shipped created: 2026-05-27 depends_on: - secure-scanner-gemini-key @@ -15,6 +15,8 @@ depends_on: # Convoy: server-side-scan-pipeline +**As-shipped:** PR #35 (scanner wave, 2026-05-27). Server-owned `/api/scan/identify`, `card_submissions`, disambiguation UI. + Move card identification entirely server-side, stop users from INSERTing into the global `cards` catalog, and surface disambiguation when OCR is ambiguous. diff --git a/.convoys/ship-readiness.md b/.convoys/ship-readiness.md index 5ce1e24..d27240b 100644 --- a/.convoys/ship-readiness.md +++ b/.convoys/ship-readiness.md @@ -31,27 +31,21 @@ Code graph: 122 files, 628 nodes, 5602 edges, 11 communities. Indexed by `user-c **Milestone reached 2026-05-24:** `add-rate-limiting` (PR #20, squash commit `708ef45`) closed P0 #6 — the last open P0 — flipping the ship-blocker set from 7/8 to **8/8 RESOLVED**. The security gate is -closed; remaining launch work is P1 quality bar (single SQL client, -single auth provider, lint baseline cleanup, brand decision, test -coverage expansion) plus the P2 / P3 polish lanes in this file's -Queued convoys section. None of those are P0 ship-blockers. +closed; P1 quality bar is **6/6 RESOLVED** (lint baseline cleared 2026-06-02). +Remaining launch work is P2 / P3 polish in this file's Queued convoys section. +None of those are P0 ship-blockers. -**P1 quality-bar milestone reached 2026-05-26 (7-convoy wave):** 5 of 6 -P1 quality items RESOLVED post the 7-convoy multitask wave that merged -2026-05-26 (PRs #26-#32). The three remaining P1 quality launch-sequence -items shipped in a single wave: +**P1 quality-bar: 6 of 6 RESOLVED** (last item closed 2026-06-02). The +7-convoy multitask wave merged 2026-05-26 (PRs #26–#32) closed P1 #8, #9, +and #11: - **P1 #8 single-sql-client** → RESOLVED 2026-05-26 (PR #30, squash `c403ea4`). - **P1 #9 single-auth-provider** → RESOLVED 2026-05-26 (PR #31, squash `0668b0c`). - **P1 #11 migration-tool** → RESOLVED 2026-05-26 (PR #32, squash `de9f334`). -Combined with prior closures (P1 #10 both steps via `adopt-vitest` -brief of `fix-auth-bypass` + `adopt-playwright-smoke` PR #18, and P1 -#12 brand-consistency via `pick-a-name` PR #21), the P1 lane now -stands at **5 of 6 RESOLVED**. Only `fix-lint-baseline` (P1 #11.5) -remains in the P1 lane — the CI `|| true` wrapper still cushions the -125-problem lint baseline (improved from 128 by `single-auth-provider`, -which deleted 3 unused-import / unused-var lints together with -`lib/auth-context.js` + `lib/admin-auth.js`). +Combined with prior closures (P1 #10 via `adopt-vitest` + `adopt-playwright-smoke` +PR #18; P1 #12 via `pick-a-name` PR #21) and **P1 #11.5 `fix-lint-baseline`** +(PRs #61–#63, 2026-06-02), the P1 lane is complete. Lint is **0 problems**; +CI `lint` is blocking (no `|| true` / `continue-on-error`). ## P0 — ship-blockers (security) @@ -151,7 +145,7 @@ These MUST land before any anonymous traffic touches the production URL. | `checkImportRateLimit` (new) | 5 / 1 hour | user (admin-only) | `cards/import-mtg.js`, `cards/import-pokemon.js`, `cards/import-lorcana.js` | 3. **`extractUserIdentifier(userId)` THROWS** on `null` / `undefined` / `''` / `NaN` (Decision 4 defensive shape). Surfaces gate-ordering bugs at dev time rather than silently falling back to IP and converting a per-user limit into a per-IP limit — which would lock other household members out for one user's behavior. Numeric `0` is intentionally accepted (returns `'user:0'`) for forward-compat. - 4. **Three `pages/api/cards/import-*.js` routes newly auth-gated.** Each grew `getUserFromRequest` + `if (user.role !== 'admin') return 403` + `checkImportRateLimit(req, user.userId)` before the existing `try` block. Closes the publicly-callable anonymous-abuse vector the architect's pre-brief audit flagged (each handler hits Scryfall / Pokémon-TCG / Lorcana APIs and performs UPSERTs into `cards` with no caller throttling pre-convoy). Lorcana was gated defensively despite zero current frontend callers — see § Queued convoys for the `delete-dead-lorcana-import` cleanup follow-up. + 4. **Three `pages/api/cards/import-*.js` routes newly auth-gated.** Each grew `getUserFromRequest` + `if (user.role !== 'admin') return 403` + `checkImportRateLimit(req, user.userId)` before the existing `try` block. Closes the publicly-callable anonymous-abuse vector the architect's pre-brief audit flagged (each handler hits Scryfall / Pokémon-TCG / Lorcana APIs and performs UPSERTs into `cards` with no caller throttling pre-convoy). Lorcana was gated defensively despite zero current frontend callers — route removed in PR #59 (`delete-dead-lorcana-import`, RESOLVED 2026-06-02). 5. **Atomic admin UI fix in `pages/admin/card-import.js`.** Added `'Authorization': \`Bearer ${localStorage.getItem('auth_token')}\`` to the import fetch's headers (one-line addition). **This was the architect's critical pre-brief discovery and the reason Decision 1 routed back to the operator** — gating the import APIs without this matching client fetch fix would have closed P0 #6 but introduced an immediate 401 on every "Import Cards" click, producing a visible UX regression on the only live admin tooling that exercises the gated routes. Shipping the API gate + the client fix in the same atomic PR is what made Decision 1 Option A viable. 6. **`.cursor/rules/api-routes.mdc` § Rate limiting extended** with the per-class table + verbatim call shape + gate-ordering rules (method check first; auth before any user-keyed limiter; admin-role check goes between auth and rate-limit for the import routes) + identifier-extraction documentation + uniform 429 response shape + fail-closed env-var contract + fail-open Upstash-outage behavior. Doc-writer pass verified the implementer's extension is complete; no further touch-ups needed. 7. **All six architect decisions ratified at gate 1.** D1 operator-ratified (Option A — gate all three import routes plus the atomic admin UI fix); D2-D6 architect-self-ratified per the precedent established by `cors-tighten` D2-D5 and `fix-vercel-deployment-protection-in-ci` A/B/D (hybrid named-limiter shape; per-class limit values with tuning evidence; two-extractor shape with defensive THROW; uniform 429 message; no new vitest / playwright specs in this convoy). @@ -207,7 +201,7 @@ These MUST land before any anonymous traffic touches the production URL. - `bump-react` (React 18 → 19) — held until 18.x EOL or until a feature needs it. - App Router migration — multi-month effort; queued indefinitely. - `adopt-vitest` ✅ shipped as `fix-auth-bypass` Brief 5; `adopt-playwright-smoke` partially shipped via the Vercel-bound workflows (CI infra now blocked by `fix-vercel-deployment-protection-in-ci`). - - `fix-lint-baseline` (P1 #11.5) — drop the CI `|| true` wrapper once the 128-problem baseline is cleared. + - `fix-lint-baseline` (P1 #11.5) — **RESOLVED 2026-06-02** (PRs #61–#63); lint baseline **0**; CI lint blocking. - `bump-eslint-10` + `bump-typescript-6` — upstream-blocked on typescript-eslint shipping v10-tested releases. - **Doc drift note:** this resolution was applied as part of the `fix-layout-default-user` post-convoy cleanup (commit reflecting `b7ddd08`'s sibling) — the `bump-next-js` convoy never ran a dedicated doc-writer pass, so this RESOLVED entry was added ~24h after the fix actually shipped. - **Owns:** `role-architect` (pick target version + assess breaking changes) → `role-implementer` (bump + verify dev/build/start + smoke). @@ -298,14 +292,12 @@ These MUST land before any anonymous traffic touches the production URL. - **Spec deviation:** none. All seven decisions landed verbatim from the spec at gate 1. - **Owns:** parent (architect + implementer rolled together per the convoy file's § Subagent / multitask footnote). -### 11.5. Codebase has ~100 pre-existing ESLint errors +### 11.5. Codebase has ~100 pre-existing ESLint errors — **RESOLVED 2026-06-02** -- **Discovered:** 2026-05-22 during the bootstrap PR. The repo had `"lint": "next lint"` in `package.json` but no `.eslintrc.json` — meaning lint was never run. Bootstrap added the config; lint now surfaces ~100 errors. -- **Most serious:** `react-hooks/rules-of-hooks` violations (hooks called conditionally) in several components. These are **real bugs** — React's hook ordering is undefined when hooks are called after early returns. They likely manifest as state-loss / stale-closure bugs in edge cases. -- **Less serious:** `react/no-unescaped-entities` (cosmetic), `react-hooks/exhaustive-deps` (warnings about missing useEffect deps), `@next/next/no-img-element` (cosmetic). -- **Impact:** The L3 CI lint job is currently `continue-on-error: true` (see `.github/workflows/ci.yml`) so it doesn't block PRs. Lint output is visible in logs but PRs merge regardless of lint state until this is cleaned up. -- **Fix:** Triage each error. The rules-of-hooks ones need genuine code restructuring (move hooks before any early returns). The unescaped-entities are mechanical (`'` → `'`). After cleanup, remove `continue-on-error: true`. -- **Convoy:** `fix-lint-baseline` — run after `fix-auth-bypass` and `drop-public-setup`. Multitask-safe: split into briefs by file group. +- **Resolved by:** `fix-lint-baseline` convoy, PRs #61 (`309cfa2`), #62 (`c32bbd1`), #63 (`81bed51`) — three file-group sweeps (components, pages, lib/config). Post-`bump-next-js` baseline had peaked at **128 problems** (81 errors, 47 warnings); last pre-fix count was **125** after `single-auth-provider`. +- **As-shipped:** `npm run lint` exits **0** with no problems; `.github/workflows/ci.yml` `lint` job runs `npm run lint --if-present` with **no** `|| true` cushion and **no** `continue-on-error` — lint failures block merge. +- **Discovered (historical):** 2026-05-22 during the bootstrap PR. The repo had `"lint": "next lint"` in `package.json` but no `.eslintrc.json` — meaning lint was never run. Bootstrap added the config; lint surfaced ~100+ errors after `bump-next-js` (ESLint 9 flat config + stricter react-hooks rules). +- **Convoy:** `fix-lint-baseline` — multitask fan-out by file group (components → pages → lib/config). - **Owns:** `role-architect` (group strategy) → `role-implementer` (per-group fan-out). ### 12. Branding mismatch — "TCG Vault" vs. "Deck Hearth" @@ -405,7 +397,7 @@ Each phase is one Conductor-created convoy. Don't run more than two in parallel 1. **`fix-auth-bypass`** (P0 #1, #2, #4, #5, #6 partial). One PR. Highest risk; needs human review. 2. **`drop-public-setup`** (P0 #3, #4). One PR. Trivial; do as a hotfix. 3. **`fix-layout-default-user`** (P0 #7). One PR. Trivial. -3.5. **`fix-lint-baseline`** (P1 #11.5). 2-4 PRs via multitask. Closes the lint gate (drops `continue-on-error`). +3.5. **`fix-lint-baseline`** (P1 #11.5). 2-4 PRs via multitask. **RESOLVED 2026-06-02** — PRs #61–#63; lint **0 problems**; CI lint blocking. 4. **`add-rate-limiting`** (P0 #6 full). One PR. Adds @upstash/ratelimit + applies to listed routes. **RESOLVED 2026-05-24** — PR #20 squash `708ef45`; closes P0 #6 (last open P0), flipping the ship-blocker set to **8/8 RESOLVED**. 5 named limiters (auth/search/upload/generate/import), 6 routes newly gated + the 3 import routes auth-gated atomically with a `pages/admin/card-import.js` Bearer-header fix. Smoke 3/3 green in 3.8s post-merge — confirms the new 60/min search limiter doesn't 429 the smoke spec. See § Queued convoys and P0 #6 above for the full as-shipped block. 5. **`pick-a-name`** (P1 #12). Human decision first, then one or two PRs. **RESOLVED 2026-05-24** — PR #21 squash `9abbab6`; closes P1 #12 (brand-consistency, the inconsistency `AGENTS.md` line 5 had flagged since project setup). Two file-disjoint briefs landed serially (B1 commit `ac8c998` display + comment sweep across 7 files; B2 commit `1c18d21` infrastructure + email migration across 10 modified + 1 new migration script). Five canonical-string D-decisions ratified verbatim at gate-1 (Deck Hearth / `deck-hearth` / `deckhearth` / `admin@deckhearth.com` / full `deckhearth` Redis prefix) plus Risk 4 PRESERVE on `test/lib/permission-middleware.test.js` line 87's negative regression-lock literal. Smoke 3/3 green in 1m4s post-merge — fourth convoy in a row (PR #15 → #19 → #20 → #21) where the same 3-test smoke spec defends the auth surface through a sweeping change. **Operator action required:** run `node scripts/migrations/2026-05-24-rename-admin-email.js` against prod Neon BEFORE the next admin login attempt with the new email (idempotent, UNIQUE-collision-safe). See § Queued convoys for the downstream `rename-repo-and-vercel-project` + `point-domain-at-deckhearth` + `regenerate-brand-assets` follow-ups, and `.convoys/pick-a-name.md` § As-shipped for the full record. 6. **`adopt-vitest`** (P1 #10 step 1). One PR. Enables testing every future change. @@ -424,7 +416,7 @@ Total: ~14 convoys to get from current state to public-launch-ready. Estimate 4- Follow-ups surfaced mid-convoy or mid-PR that didn't fit the original launch sequence but need to land before public traffic. Listed in priority order; not all will be P0/P1 — most are CI / DX / hygiene polish. - **`rotate-default-admin`** (priority: P2 hygiene). Operator-rotation script for envs that ran `setup-neon-db.js` before `drop-public-setup` and still carry the weak `admin123` bcrypt hash. Surfaced in P0 #3 § Operator caveat. Optional: do nothing if no audit finds a deployed env with the weak hash. -- **`delete-dead-lorcana-import`** (priority: P3 polish). Delete `pages/api/cards/import-lorcana.js` (and possibly `scripts/import-lorcana.js`) if Lorcana stays out of the admin UI's `` only offers `'mtg'` and `'pokemon'`. The route is gated defensively (auth + admin-role + rate-limit) as part of PR #20 so the future Lorcana admin UI path inherits protection automatically, but if Lorcana is never wired in, this is the cleanup convoy. Strictly easier than gating-then-deleting because the gating shape is uniform across all three import routes today (mtg + pokemon + lorcana); a future cleanup only needs to delete the lorcana file + remove the `'lorcana'` enum option from `.cursor/rules/api-routes.mdc`'s import-routes table. Do nothing if Lorcana support gets wired into the admin UI in a feature convoy; cancel the entry then. +- **`delete-dead-lorcana-import`** — **RESOLVED 2026-06-02** by PR #59 (`8262fec`). Deleted `pages/api/cards/import-lorcana.js` and `scripts/import-lorcana.js`; no dedicated convoy file (cleanup tracked here only). Entry kept for audit trail. - **`tighten-visual-diff-path-filter`** — **RESOLVED 2026-05-26** by `tighten-visual-diff-path-filter` convoy, squash commit `ba95462` (PR #26). Single-edit `paths:` filter change in `.github/workflows/visual-diff.yml`: inserted `'!pages/api/**'` immediately after `'pages/**'` (order-sensitive per GitHub Actions' minimatch path-filter semantics — exclusions only fire after a prior include matches). Verified the YAML deserialization order at gate time (`['pages/**', '!pages/api/**', 'components/**', 'styles/**', 'tailwind.config.js', 'postcss.config.js']`). `preview-smoke.yml` left untouched (no `paths:` filter; intentionally fires on every PR). Diff: 2 files, +279 / -0 (1 YAML entry + inline comment block + the planning convoy file). **Post-merge verification still pending** — the only true verification is that the next API-only PR after this merges does NOT trigger `Screenshot diff`. PR #30 (`single-sql-client`, squash `c403ea4`) was the **first API-only PR post-merge** and its CI Checks tab showed `Screenshot diff: not triggered` — empirical confirmation that the `!pages/api/**` exclusion fires correctly. The next-API-only-PR success line was originally specified in the convoy file's § Verification plan as the deferred-to-post-merge gate; this is that confirmation. Entry kept (not removed) to preserve the audit trail. See `.convoys/tighten-visual-diff-path-filter.md` § As-shipped. - **`purge-weak-creds-from-helpers`** — **RESOLVED 2026-05-26** by `purge-weak-creds-from-helpers` convoy, squash commit `5f2b234` (PR #27). The umbrella is now closed; both remaining halves shipped together. **Multi-convoy history:** (1) `drop-public-setup` Brief 1+2 (`ff80753` + `b63b509`) removed the first `admin123` literal from `scripts/setup-neon-db.js` and set the env-var + fail-loud + no-echo precedent. (2) `pick-a-name` Brief 2 (`9abbab6`) swept the `@tcgvault.com` literals in the three helper paths to `@deckhearth.com` together with the migration script. (3) `fix-reset-db-script` (`3ab9bf8`, PR #25) removed the second `admin123` from `scripts/reset-db.js` and the second `Admin Password:` echo. (4) **This convoy (PR #27)** closes the umbrella by sweeping the last two files: `scripts/create-test-users.js` (alice/bob fixtures, previously hardcoding `bcrypt.hash('alice123', 12)` + `bcrypt.hash('bob123', 12)` and echoing both literals to stdout) and `TESTING_GUIDE.md` (Test Accounts table previously documenting the weak literals). The post-convoy contract: single `TEST_USERS_PASSWORD` env var (intentional simplification per Risk R2 — these are collaboration-flow demo fixtures, not independent identities), fail-loud at the top of `createTestUsers()` BEFORE any DB connection, no password echo anywhere (`✅ Created Alice (alice@deckhearth.com / alice123)` → `✅ Created Alice (alice@deckhearth.com)`), `ON CONFLICT (email) DO NOTHING` preserved. Diff: 3 files, +249 / -22. Lint preserved at 125 (post-PR-#31 baseline); vitest 21/21. ESM-already (this was the first of the three weak-creds-shape convoys to skip the CJS→ESM half because `scripts/create-test-users.js` was already top-level ESM). **Operator caveat:** existing alice/bob rows in already-seeded envs are NOT rotated by re-running the script — `ON CONFLICT` preserves the old hashes; operators must rotate manually via the app or drop those rows and re-seed. Same caveat as the `drop-public-setup` admin-row guidance. **Surfaced out-of-scope follow-up:** `purge-quick-login-from-loginpage` — see new queue entry below. Entry kept (not removed) to preserve the audit trail. See `.convoys/purge-weak-creds-from-helpers.md` § As-shipped. - **`rename-repo-and-vercel-project`** (priority: P2 polish). Rename the GitHub repo + the Vercel project from `tcg-vault` to `deck-hearth` to match the canonical product brand ratified in `pick-a-name` (squash `9abbab6`, 2026-05-24). Auto-redirects on both GitHub and Vercel make this low-urgency; the surface is a one-line update to local git remotes (`git remote set-url origin git@github.com:/deck-hearth.git`) + a Vercel project-settings rename + the 8 architect-verified literal-repo references documented in `.convoys/pick-a-name.md` § Full surface inventory § Repo / Vercel project name (out-of-scope) — `README.md` lines 30 + 105, `AGENTS.md` line 1, `.github/workflows/ci.yml` lines 12 + 123, `.github/workflows/visual-diff.yml` line 5, `.agent-context-manifest.yml` source tags. Also re-evaluate the `.agent-context-manifest.yml` `source: "tcg-vault-local"` tag at that point (Risk 5 of `pick-a-name` — renaming the source tag could break the `sync-agent-context` skill's drift tracking; do this convoy with the sync-skill author's input). Surfaced 2026-05-24 as the explicit downstream of `pick-a-name`. @@ -438,7 +430,7 @@ Follow-ups surfaced mid-convoy or mid-PR that didn't fit the original launch seq - **`harden-multipart-parser`** (priority: P2 quality). Surfaced 2026-05-24 in `add-rate-limiting` § Risk list. `pages/api/user/avatar.js`'s `parseMultipartFormData` consumes the 5MB multipart body via `req.on('data')` before any response is sent, so an attacker can still exhaust the 5MB body even on a 429 path from the new `checkUploadRateLimit` gate. Real defense requires moving the parse into a separate edge function or using `read-up-to` semantics. Not a release-blocker — the gate-ordering in PR #20 places the limiter BEFORE the method branches that call `parseMultipartFormData`, so when this hardening lands, the gate ordering is already correct. Surface as P1 only if a real abuse incident occurs. - **`god-function-split` / `refactor-cards-search-sql`** (priority: P2 refactor). Surfaced 2026-05-24 in `add-rate-limiting` § Files explicitly out of scope. `pages/api/cards/search.js` has a 240-line god-function shape with 7+ conditional `SELECT * FROM cards WHERE …` branches; the PR #20 rate-limit gate sits at the top of the handler and leaves the SQL byte-identical. Splitting is its own scope (probably one convoy per branch group with `slice_dependencies:` for safe multitask fan-out). Not security-critical; deferred to the P2 lane. - **`withAdmin(handler)` wrapper extraction** (priority: P3 polish / DX). Surfaced 2026-05-24 in `add-rate-limiting` Decision 1 + § What did NOT change. `.cursor/rules/auth-and-permissions.mdc` notes *"check `user.role === 'admin'` directly; consider extracting `withAdmin()` if a third call site appears"* — the three `cards/import-*.js` routes are the third+fourth+fifth call sites in the codebase, but PR #20 kept the inline shape for uniformity across the three import routes and for the convoy's atomic-close-P0-#6 goal. A future convoy can extract `withAdmin(handler)` to `lib/permission-middleware.js` (or wherever the architect decides) and sweep all 5 admin-role check sites onto it. Pure refactor; no security delta either way. -- **`seed-visual-baselines-on-linux`** (priority: P2 CI infra; **operator action required**). Generate Linux baselines for `tests/visual/__screenshots__/` in the `mcr.microsoft.com/playwright:v1.60.0-noble` Docker image and commit them in a small follow-up PR. Mac-generated baselines would silently overwrite Linux CI baselines because `playwright.config.js`'s custom `snapshotPathTemplate` has no `{platform}` token (Risk R3 + Boot-the-brief Finding 7 in `.convoys/adopt-playwright-smoke.md`). Until this PR lands, every `Screenshot diff` run on a PR touching `pages/**` / `components/**` / `styles/**` / Tailwind/PostCSS config fails at the test step and posts a "Visual Diff — view run" comment with empty artifacts — that's the documented Decision-4 end state of `adopt-playwright-smoke`, not a regression. One small PR with just the PNG baseline(s). Surfaced 2026-05-24 as the follow-up to `adopt-playwright-smoke` (PR #18). **Ordering: MUST run AFTER `pick-a-name` (now satisfied — squash `9abbab6` merged 2026-05-24); the first Linux baseline will capture Deck Hearth strings, not the pre-rename TCG Vault strings** (per `.convoys/pick-a-name.md` § Test plan + the architect's `update-seed-visual-baselines-on-linux-ordering` follow-up note). +- **`seed-visual-baselines-on-linux`** — **RESOLVED 2026-06-02** by PR #58 (`83a358b`). Linux `tests/visual/__screenshots__/home.png` committed; `Screenshot diff` can now compare on UI-touching PRs. Entry kept for audit trail. - **`adopt-playwright-smoke`** (priority: P1 quality, also listed as launch sequence step 10 / P1 #10 step 2) — **RESOLVED 2026-05-24**. - **Resolved by:** squash commit `7b6f751` (PR #18, architect-commit `3ac527e`, implementer-commit `c72d006`). Brief 1 shipped as planned with two small lint-baseline-preserving deviations from the brief's verbatim shape (documented in the convoy file's § As-shipped). - **As-shipped surface:** `@playwright/test@^1.60.0` added to `devDependencies`; new `playwright.config.js` at repo root (ESM, two projects partitioned by `testMatch` — `smoke` + `visual`, CI-fail-loud / dev-warn predicate on `VERCEL_AUTOMATION_BYPASS_SECRET` per Decision 2, `snapshotPathTemplate: 'tests/visual/__screenshots__/{arg}{ext}'` aligned with `visual-diff.yml`'s artifact upload path); new `tests/visual/homepage.spec.ts` (1 test, no baseline committed per Decision 4); three new `package.json` scripts (`test:smoke`, `test:visual`, `test:visual:update`); three new `.gitignore` entries (`/playwright-report/`, `/test-results/`, `/.playwright/`). No `eslint.config.mjs` change (Decision 5 + Finding 2 verified clean empirically). No source touched under `pages/**` / `components/**` / `lib/**`. @@ -451,9 +443,9 @@ Follow-ups surfaced mid-convoy or mid-PR that didn't fit the original launch seq - `Screenshot diff` workflow: **not triggered on PR #18 itself** because its `paths:` filter excludes test-infra-only changes; first real trigger fires on the next PR touching `pages/**` / `components/**` / `styles/**` / `tailwind.config.js` / `postcss.config.js`. At that point the documented Decision-4 end state runs live (test fails on missing baseline → `continue-on-error: true` swallows → comment-on-PR step posts run link with empty artifacts). - Bypass secret leak check: **0 matches** in the raw workflow log. GitHub Actions auto-masks registered secrets; our Decision-2 branches name the env var but never interpolate the value into any string. - **Cross-validation finding** (not a planned AC; surfaced organically from CI green): smoke test 2 (`'sign-in page renders'`) asserts `await expect(page.getByRole('button', { name: /sign in/i })).toBeVisible()` against `/login`, which only passes because `components/Layout.js` renders the `Sign in` CTA on the logged-out branch that PR #15 (`fix-layout-default-user`, `ca302a8`) introduced. P0 #7's resolved state is now defended by a live CI signal — if a future PR reverts to a hardcoded default user or breaks the CTA wording, smoke fails the PR (in addition to the 5 vitest assertions in `test/components/Layout.test.js`). - - **Operator action required going forward:** `seed-visual-baselines-on-linux` (above) is the follow-up. Until it lands, `Screenshot diff` runs post a "Visual Diff — view run" comment with empty artifacts on every UI-touching PR — that is the Decision-4 end state, not a regression. No operator action is required to keep `Playwright smoke` green. + - **Operator action required going forward:** none for smoke. `seed-visual-baselines-on-linux` **RESOLVED** PR #58 — `Screenshot diff` now has a Linux baseline for homepage. - **Flagged-but-deferred** (deliberately out of scope per the convoy file, restated here for the audit trail): - 1. `seed-visual-baselines-on-linux` — see above. + 1. ~~`seed-visual-baselines-on-linux`~~ — **RESOLVED** PR #58. 2. `adopt-test-smoke-local` (possible follow-up) — a `test:smoke:local` wrapper that auto-boots `next dev`. Explicitly rejected by Decision 6; queue only if dev friction proves out. 3. Deeper E2E coverage beyond the 3 existing smoke checks — per-feature work in feature convoys, not a test-infra concern. - **Owns:** `role-architect` (3 of 6 decisions self-ratified — D2 CI predicate, D3 two-project shape, D5 no-eslint-change; 3 of 6 operator-ratified — D1 keep `.ts`, D4 defer baselines, D6 simple scripts) → `role-implementer` (Brief 1, plus the two deviations above). @@ -476,8 +468,8 @@ Follow-ups surfaced mid-convoy or mid-PR that didn't fit the original launch seq 3. `Screenshot diff` baseline authoring — orthogonal scope; the visual-diff workflow has nothing to compare against on its first real run. - **Owns:** `role-architect` (3 Decisions ratified — A query-param, B 120s timeout, D fork-PR skip) → `role-implementer` (Brief 1) + two scope-expansion commits. -- **`purge-quick-login-from-loginpage`** (priority: P2 hygiene / security). Surfaced 2026-05-26 by `purge-weak-creds-from-helpers` (PR #27) as an out-of-scope sibling bug. `pages/login.js` lines ~172 + ~184 still hardcode `alice123` / `bob123` in client-side "Quick Login" button handlers (`handleQuickLogin('alice@deckhearth.com', 'alice123')` / `handleQuickLogin('bob@deckhearth.com', 'bob123')`). These ship to production HTML and reveal the legacy passwords directly to anyone viewing the login page source. The PR #27 convoy spec was "scripts + docs only; do NOT touch `pages/**`", so this was deliberately left for a follow-up. Small surface (one file, two button handlers). Two reasonable shapes: (a) delete the Quick Login section entirely, or (b) gate it behind `process.env.NODE_ENV === 'development'` with a credential source that doesn't ship to prod HTML (likely a `.env.local`-only `NEXT_PUBLIC_DEV_*` convention or a dev-only proxy endpoint). The latter is architect-worth. Should fold into a UI hygiene pass or a `pre-launch-checklist` convoy that strips dev affordances from prod builds. -- **`purge-neondatabase-serverless-fully`** (priority: P3 polish; **unblocked 2026-05-26** by `migration-tool` PR #32). Surfaced 2026-05-26 by `single-sql-client` (PR #30). The `@neondatabase/serverless` dep was retained in `package.json` because 11 `scripts/*` helpers still imported `neon()` directly (`setup-neon-db.js`, `migrations/2026-05-24-rename-admin-email.js`, `reset-db.js`, plus 8 historical `add-*` / `fix-*` / `seed-*`). Post-`migration-tool` (PR #32), the migration helpers all use `node-pg-migrate`'s `pg` client — not `@neondatabase/serverless` — so the only remaining direct `neon()` consumers are `setup-neon-db.js` (admin seed), `reset-db.js`, and the historical graveyard. The graveyard is no-go-zone; `setup-neon-db.js` and `reset-db.js` could be migrated to `@vercel/postgres` in a single small convoy to fully purge the dep. **Estimate:** 2 file edits + 1 `npm uninstall @neondatabase/serverless` + verify scripts still run against a Neon branch. Low priority — dual deps aren't actively harmful, just untidy. Caveat preserved from `.convoys/single-sql-client.md` § Follow-ups: `@vercel/postgres` is tuned for Vercel's edge / serverless runtime; the right answer may be "keep the dep but route all scripts through a single thin helper" rather than "delete the dep entirely". This is its own scope. +- **`purge-quick-login-from-loginpage`** — **RESOLVED 2026-05-29** by PR #56 (`e0218e4`). Quick Login removed from `pages/login.js`. See `.convoys/purge-quick-login-from-loginpage.md` § As-shipped. +- **`purge-neondatabase-serverless-fully`** — **RESOLVED 2026-06-02** by PR #57 (`5115683`). `@neondatabase/serverless` removed from `package.json`; operational scripts use `@vercel/postgres`. Historical `scripts/add-*` / `fix-*` / `seed-*` graveyard unchanged per no-go-zones. Entry kept for audit trail. - **`wire-migrate-into-ci`** (priority: P2 CI infra). Surfaced 2026-05-26 by `migration-tool` (PR #32) — D6 deferral. Add a CI job that runs `npm run migrate up` against a test DB (either a dedicated Neon branch + `MIGRATE_TEST_DATABASE_URL` secret with branch-reset logic, or a Postgres service container with a ~30s container-start tax). Catches syntactically-invalid migrations + most logical errors at PR time. Currently, the first signal that a new migration is broken is the developer's local `npm run migrate up` against their dev branch (or post-deploy on Vercel). Documented in `.convoys/migration-tool.md` § R3. - **`reconcile-historical-add-scripts`** (priority: P1 quality — needed for fresh-env onboarding). Surfaced 2026-05-26 by `migration-tool` (PR #32). Fold the effects of the 27 historical `scripts/add-*.js` / `fix-*.js` / `seed-*.js` jobs into the migration history so a brand-new Neon branch can be onboarded by `npm install` → `npm run setup-db` alone (without manually replaying the historical scripts). Multi-PR; ideally one migration per logical change, generated by reading the scripts' SQL and re-shaping into idempotent `pgm.sql(...)` blocks (with `IF NOT EXISTS` / `IF EXISTS` guards so re-application is safe). Documented in `.convoys/migration-tool.md` § R1. - **`retire-graveyard-scripts-after-audit`** (priority: P3 polish; **blocked on `reconcile-historical-add-scripts`**). Surfaced 2026-05-26 by `migration-tool` (PR #32). Once the migration history captures all historical effects, the legacy `scripts/add-*.js` / `fix-*.js` / `seed-*.js` files can be deleted (or moved to `scripts/historical/`). They remain no-go-zones until that cleanup convoy lands. Documented in `.convoys/migration-tool.md` § Follow-ups. @@ -491,30 +483,12 @@ Six convoys authored from the scanner audit portfolio plan. Dependency order: `redesign-scanner-flow`); `rename-collections-vocabulary` and `scanner-correctness-polish` parallel after #1. -- **`secure-scanner-gemini-key`** (priority: **P0 security** — ships first). - Delete `pages/api/config/gemini.js`; remove CameraScanner client key auto-load; - add sixth `scan` rate-limit class; new `forbidden-client-side-llm-keys` CI gate. - **Operator action:** rotate `GEMINI_AI_API_KEY` in Google AI Studio + Vercel - before merge. Convoy: `.convoys/secure-scanner-gemini-key.md`. -- **`server-side-scan-pipeline`** (priority: P1 feature; - `depends_on: secure-scanner-gemini-key`). Server-owned `/api/scan/identify`; - `card_submissions` + `scan_attempts` tables; remove user-writable `cards` - INSERT; admin review queue; client disambiguation UI. Four briefs; gate-1 - `/multitask` briefs 1+2. Convoy: `.convoys/server-side-scan-pipeline.md`. -- **`add-real-ocr-layer`** (priority: P1 feature; - `depends_on: server-side-scan-pipeline`). Tesseract Worker + `pg_trgm` - identify-by-text route; ≥70% Layer-1 hit rate via `scan_attempts.layer`. - Piggybacks `schema-map-fresh` CI path fix for `migrations/`. Convoy: - `.convoys/add-real-ocr-layer.md`. -- **`redesign-scanner-flow`** — **RESOLVED 2026-05-27** — PRs #42 (Brief 1), #43 (Brief 2), #44 (Brief 3). Post-PR audit `audit-redesign-scanner-flow-44` posted to PR #44; outcome comment-only. See `.convoys/redesign-scanner-flow/audit-redesign-scanner-flow-44.md`. Follow-ups queued: `scanner-redesign-a11y-fixes` (P1), `scanner-user-cards-quantity-guard` (P2), `test-scanner-redesign-surfaces` (P2). -- **`rename-collections-vocabulary`** (priority: P2 IA/copy; parallel to #1+#2). - "My Collection" / "Lists" / "Binders" copy sweep; `forbidden-stale-strings` - CI gate; `AGENTS.md` vocabulary table. Skips architect (`skip: arch`); IA + - UX run. Convoy: `.convoys/rename-collections-vocabulary.md`. -- **`scanner-correctness-polish`** (priority: P2 infra; parallel to #1+#2). - Idempotent Mark-Owned, fix bulk `setTimeout` race, `lib/use-auth` import, - `logCollectionActivity` on collection card POST. Single brief. Convoy: - `.convoys/scanner-correctness-polish.md`. +- **`secure-scanner-gemini-key`** — **RESOLVED 2026-05-27** — PR #34 (`8c58990`). Client key leak closed; `forbidden-client-side-llm-keys` CI gate. Convoy: `.convoys/secure-scanner-gemini-key.md`. +- **`server-side-scan-pipeline`** — **RESOLVED 2026-05-27** — PR #35 (`e81dd49`). Server-owned identify, `card_submissions`, disambiguation. Convoy: `.convoys/server-side-scan-pipeline.md`. +- **`add-real-ocr-layer`** — **RESOLVED 2026-05-27** — PR #38 (`d798e28`) + polish PR #39. Layer-1 Tesseract + `pg_trgm`. Convoy: `.convoys/add-real-ocr-layer.md`. +- **`redesign-scanner-flow`** — **RESOLVED 2026-05-27** — PRs #42 (Brief 1), #43 (Brief 2), #44 (Brief 3). Post-PR audit `audit-redesign-scanner-flow-44` posted to PR #44; outcome comment-only. See `.convoys/redesign-scanner-flow/audit-redesign-scanner-flow-44.md`. Follow-ups: `scanner-redesign-a11y-fixes` **RESOLVED** PR #45; `scanner-user-cards-quantity-guard` **RESOLVED** PR #46; `test-scanner-redesign-surfaces` **RESOLVED** PR #47. +- **`rename-collections-vocabulary`** — **RESOLVED 2026-05-29** — PR #54 + follow-up PR #55. Convoy: `.convoys/rename-collections-vocabulary.md`. +- **`scanner-correctness-polish`** — **RESOLVED 2026-05-27** — PR #41 (`55af7e3`). Convoy: `.convoys/scanner-correctness-polish.md`. - **`schema-cleanup-from-scanner-audit`** (priority: P2 schema; **deferred** — NOT scanner-specific). Separate convoy when ready; surfaced by the scanner audit but applies globally: @@ -529,19 +503,7 @@ Six convoys authored from the scanner audit portfolio plan. Dependency order: ### Catalog freshness (deferred — post-scanner) -- **`catalog-sync-vercel-cron`** (priority: P2 infra / data hygiene; - **deferred until scanner pipeline convoys finish**). Operator decision - 2026-05-27: use **Vercel Cron** (not GitHub Actions) for weekly delta sync of - new MTG + Pokémon sets. Motivation: Perfect Order Seel scan showed the catalog - has no row for unreleased/unimported sets; `card_submissions` is the safety net - but does not replace keeping `cards` current. v1 scope: extract shared import - logic from `import-mtg` / `import-pokemon`, discover missing sets via Scryfall + - Pokémon TCG API `/sets`, protected `/api/cron/sync-catalog` with `CRON_SECRET`, - `vercel.json` weekly schedule, paced imports respecting upstream rate limits. - Lorcana auto-discovery deferred (hardcoded `setCodeMap` today). **Unblocked** - post-`redesign-scanner-flow` merge (2026-05-27); still defer if operator wants - a11y fixes or catalog import (Perfect Order) first. Convoy: - `.convoys/catalog-sync-vercel-cron.md`. +- **`catalog-sync-vercel-cron`** — **RESOLVED 2026-05-29** — PRs #48–#52 (weekly Vercel Cron, shared import libs, admin trigger, submission auto-link, Pokémon data source switch). Convoy: `.convoys/catalog-sync-vercel-cron.md`. ## Self-analytics diff --git a/.convoys/single-sql-client.md b/.convoys/single-sql-client.md index 920712b..c438d55 100644 --- a/.convoys/single-sql-client.md +++ b/.convoys/single-sql-client.md @@ -13,7 +13,7 @@ skip: - role-a11y-auditor - role-ux-reviewer - role-ia-architect -status: open +status: shipped created: 2026-05-26 parent: ship-readiness addresses: P1 #8 (launch sequence step 8) — "Two SQL clients in parallel" diff --git a/.convoys/test-scanner-redesign-surfaces.md b/.convoys/test-scanner-redesign-surfaces.md index 09c7b9f..5d00cca 100644 --- a/.convoys/test-scanner-redesign-surfaces.md +++ b/.convoys/test-scanner-redesign-surfaces.md @@ -6,12 +6,14 @@ success_metric: | (destination picker, scanned card row, upload-image auth/rate-limit/MIME). depends_on: - redesign-scanner-flow -status: open +status: shipped created: 2026-05-27 --- # Convoy: test-scanner-redesign-surfaces +**As-shipped:** PR #47 (2026-05-27). Vitest coverage for redesign API validation and scanner components. + P2 follow-up from `audit-redesign-scanner-flow-44` reviewer report. ## Scope -- 2.45.2