deckhearth/.convoys/pick-a-name.md
varutasu 7e06ab559a
docs: post-convoy cleanup for pick-a-name — first post-P0 P1 convoy
Post-merge doc cleanup for PR #21. Resolves the AGENTS.md line-5 'Pick one before launch' branding question. Updates AGENTS.md top branding note + Gotcha #4 + Gotcha #12, adds full as-shipped to .convoys/pick-a-name.md, adds 4 queued-convoy entries to ship-readiness.md, adds pick-a-name attribution to .cursor/rules/api-routes.mdc. Pure docs (+357/-15 across 4 files).
2026-05-25 04:10:12 -05:00

50 KiB
Raw Permalink Blame History

pick-a-name (P1 — pre-launch brand consistency)

Status: shipped Priority: P1 (pre-launch; not a security blocker, but a brand-consistency blocker that touches every user-facing surface) Convoy owner: parent (orchestration); role-architect then role-implementer(s) then role-doc-writer Opened: 2026-05-24 Shipped: 2026-05-24 Operator decision (gate-0, already ratified): Deck Hearth wins.

Problem

AGENTS.md line 5 has called this out since project setup: the repo, README, seed data, package name, infrastructure, and admin email all say "TCG Vault" (and the kebab tcg-vault / camel tcgvault variants), while the actual rendered header brand in components/Layout.js says "Deck Hearth". Two distinct names, two distinct visual brands (the AnimatedFireLogo.js component is also affected). Pre-launch this MUST be one name end-to-end.

Operator's brand decision (gate-0)

Deck Hearth wins. Rationale (operator-stated): more distinctive, more ownable, and the brand the human-facing surface (Layout header) already renders. The trade is real: this is the higher-cost path because the already-shipped infrastructure ID is the losing name. The architect's job is to scope and minimize that cost — NOT to re-litigate the brand decision.

Initial surface inventory (architect to expand)

Pre-architect rough hit-count from rg, scoped to non-vendor files:

Surface Files (sample) "TCG Vault" or tcg(-)?vault hits
Marketing copy README.md, pages/index.js, pages/login.js, pages/signup.js 4 + 5 + 4 + 1
Brand components components/AnimatedFireLogo.js, components/ShareModal.js 4 + 1
Seed scripts (admin email) scripts/setup-neon-db.js, scripts/create-test-users.js, scripts/seed-collections-*.js 2 + 7 + 6
Test guides TESTING_GUIDE.md 7
Infrastructure-bound (rename has external blast) package.json, lib/rate-limit.js (Redis prefix), .github/workflows/*.yml 1 + 5 + 3
Already-correct (Deck Hearth) components/Layout.js, styles/globals.css, components/AnimatedFireLogo.js partial n/a (these stay)

Hard scoping rules

  • Strictly NO re-litigating the brand decision. Deck Hearth wins; the architect ratifies how to rename, not whether to.
  • Keep the repo tcg-vault for now. GitHub rename is a separate concern (auto-redirects work, but Vercel-link / gh configs / local remotes / bookmarks all need touching). Track as a queued follow-up convoy rename-repo-and-vercel-project. This convoy renames the product brand in-app; the repo/Vercel rename is a downstream convoy.
  • Redis prefix migration is in scope. lib/rate-limit.js uses tcgvault:auth/search/upload/generate/import as Redis key prefixes. These must rename to deckhearth:* (or similar) atomically with the convoy. Accept the one-time rate-limit-state reset (counters are per-15-min / per-1-hour anyway; no user-perceptible impact).
  • Admin email migration is in scope. admin@tcgvault.com rename to admin@deckhearth.com (or similar — architect proposes the canonical email, operator ratifies). Includes the in-DB UPDATE for any existing admin@tcgvault.com row (write a one-off scripts/migrations/YYYY-MM-DD- rename-admin-email.js per the no-go-zones rule for new migrations).
  • package.json name field: rename to deck-hearth in this convoy (npm package name; we don't publish, so blast is contained to lockfile + any local script reference).
  • Lockfile regeneration: the architect ratifies whether to regenerate package-lock.json in this convoy or accept the rename mismatch short-term. (Recommended: regenerate; cheap; matches package.json shape.)
  • DNS / domain: out of scope — separate convoy point-domain-at-deckhearth (you don't own a deckhearth.* domain yet per operator's pre-convoy statement; if/when acquired, that triggers DNS).
  • Favicon / OG images / social cards: in scope IF source files exist; defer to follow-up convoy regenerate-brand-assets if they need new design.

Architect deliverables

  1. Full surface inventory — every occurrence of TCG Vault, tcgvault, tcg-vault, plus every occurrence of Deck Hearth / deck-hearth / deckhearth (the latter set tells you what's already correct and what shape collisions need handling, e.g., if Layout.js has Deck Hearth but AnimatedFireLogo.js has DECKHEARTH — pick canonical casing).
  2. Canonical-string ratification: propose the canonical product name ("Deck Hearth" with internal-cap; or "Deckhearth" one word; or other); the canonical kebab (deck-hearth); the canonical camel/lower (deckhearth); the canonical admin email (admin@deckhearth.com or variant); the canonical Redis prefix (deckhearth or dh). Mark each as D1-D5 (operator gate-1 for all five — they're naming choices, not architectural ones).
  3. Slice plan: probably 2-3 briefs. Suggested split (architect can change): Brief 1 = in-repo string sweep + AnimatedFireLogo brand update (mechanical); Brief 2 = Redis prefix migration in lib/rate-limit.js + admin-email DB migration script + package.json rename + lockfile regen (operationally riskier — needs careful testing). Brief 3 (if needed) = visual baseline reset (because the smoke + visual-diff specs will all fail with the new brand).
  4. Boot-the-brief verification for each: confirm specs compile, lint passes, vitest passes, smoke can still run against a preview.
  5. Out-of-scope queued follow-ups: explicit list (repo rename, Vercel project rename, DNS / domain acquisition, brand asset regeneration, social card refresh).

Known constraints

  • Cross-cutting smoke/visual diff blast. PR-#18's Playwright smoke spec ("sign-in page renders") + the visual-diff baseline are both predicated on the current brand strings. Brief 2 (or Brief 3 if split) MUST update the smoke spec expectations and either regenerate visual baselines or accept temporary baseline drift (the adopt-playwright-smoke Decision 4 continue-on-error swallow is still in effect, so visual-diff drift won't fail CI — but the queued seed-visual-baselines-on-linux convoy becomes harder to ship until after this convoy lands).
  • Smoke spec lives at tests/smoke/app.smoke.spec.ts. Likely needs one-or-two-character edits to text matchers.
  • AGENTS.md line 5's branding note is the canonical historical record of the inconsistency; the doc-writer pass MUST update it from "Pick one before launch" to "Deck Hearth (decided 2026-05-24 in pick-a-name convoy; commit X)."
  • Operator-action-required pre-merge: smoke-test the rebranded preview manually before merge (every public page should render "Deck Hearth"; login + register should still work; admin import UI should still function with the renamed admin user).

Out of scope (queued follow-ups)

  • rename-repo-and-vercel-project — GitHub repo rename + Vercel project rename + local git remote update. P2 polish. Auto-redirects make this low-risk-low-urgency.
  • point-domain-at-deckhearth — DNS / domain. Blocked on domain acquisition (operator pre-convoy: doesn't own deckhearth.* yet).
  • regenerate-brand-assets — favicon, OG images, social cards. P2 polish; requires design pass.
  • migrate-existing-user-emails — if any existing user accounts have @tcgvault.com emails (besides admin), propose a migration. Architect to surface count via a rg/DB query during inventory.

Owns

role-architect (decisions D1-D5, slice plan) → role-implementer(s) (per brief) → role-doc-writer (AGENTS.md line 5 + this file's As-shipped + ship-readiness Status summary footer).

Architecture (role-architect, 2026-05-24)

Status: architect-complete; awaiting operator gate-1 ratification on D1-D5 before implementer dispatch.

No blocking findings. No surfaced naming collision with existing npm packages (we don't publish; the name field is a local identifier). deckhearth.com domain is unowned per operator's pre-convoy statement — queued in point-domain-at-deckhearth, downstream of this convoy. Redis counter reset is an acknowledged one-time cost (per-15-min and per-1-hour windows; no user-perceptible impact). All other costs are mechanical sweeps.

Scope summary: 16 source files modified across two parallel briefs + 1 new migration script. ~75-110 lines of net diff (excluding package-lock.json regen, which is its own opaque artifact). Two briefs touch DISJOINT file sets and can run in parallel via /multitask.

Operator-ratified decisions (gate-0, captured)

  • Brand winner: Deck Hearth. Operator's rationale: more distinctive, more ownable, and the brand the human-facing surface (Layout header) already renders. The architect's job is to scope and minimize the cost of getting there — NOT to re-litigate the brand decision.

Architect-proposed decisions (operator gate-1 required, ALL FIVE)

These five naming choices are NOT architect-self-ratifiable. They establish the canonical strings the entire codebase will be swept to. All five require operator gate-1 ratification before Brief 1 OR Brief 2 dispatch.

D1 — Canonical display string (operator gate-1)

Recommendation: "Deck Hearth" (two words, internal cap, space-separated).

Trade-off considered: "Deckhearth" (one word) is brand-simpler and gives a cleaner email TLD (deckhearth.com reads as one syllable). But every already-shipped user-facing surface in the repo renders the two-word form:

  • components/Layout.js lines 623 + 716 → Deck Hearth
  • components/AnimatedFireLogo.js line 13 → Deck Hearth Logo
  • pages/index.js lines 64, 96, 266, 299, 315 → Deck Hearth
  • pages/login.js lines 76, 158 → Deck Hearth
  • pages/signup.js line 215 → Deck Hearth
  • components/ShareModal.js line 116 → Deck Hearth
  • styles/globals.css line 15 → Deck Hearth Fire-Inspired

Choosing "Deckhearth" would require RE-sweeping all seven currently-correct surfaces — net negative-cost choice. Recommendation: ratify "Deck Hearth" to make the in-progress strings the canonical strings.

D2 — Canonical kebab string (operator gate-1)

Recommendation: deck-hearth (matches existing CSS class deck-hearth-logo-container in components/AnimatedFireLogo.js line 8 + the conventional hyphenated lowercase used by package.json name fields and file naming).

Trade-off: deckhearth (no hyphen) would match D3 below but conflicts with both Node ecosystem convention (@scope/kebab-name) and the existing CSS class. Recommendation: ratify deck-hearth.

D3 — Canonical camel/lower string for IDs (operator gate-1)

Recommendation: deckhearth (single token, no separators).

Used for: Redis key prefixes, env-var-style identifiers, internal symbol names. Distinct from D2 because IDs lack convention-bound separators (the Upstash prefix: option is a single string token; convention is colon- separated namespace, e.g. deckhearth:auth:1.2.3.4). Recommendation: ratify deckhearth.

D4 — Canonical admin email (operator gate-1)

Recommendation: admin@deckhearth.com (placeholder TLD pending the queued point-domain-at-deckhearth convoy).

Trade-off: the operator does not yet own deckhearth.com (or any deckhearth.* TLD) per the convoy seed § "point-domain-at-deckhearth … blocked on domain acquisition". The seed admin row's email is a STRING literal in the database — it doesn't need to resolve to a real mailbox to function (auth uses the email as a unique identifier, not as a mail target). Picking .com now matches the most-likely future domain purchase; if the operator instead buys .app or .gg, a future one-line migration can sweep the TLD without touching this convoy's structure.

Apply to all three current @tcgvault.com accounts: admin, alice, bob (the test users created by scripts/create-test-users.js). The migration script in Brief 2 sweeps all three uniformly via a REPLACE(email, '@tcgvault.com', '@deckhearth.com') UPDATE.

Recommendation: ratify admin@deckhearth.com + sweep all @tcgvault.com emails to @deckhearth.com in the same migration. If the operator wants a different TLD, swap .com for the chosen TLD in the migration script and the seed scripts before Brief 2 implementer dispatch — that's a one-line change confined to B2's file set.

D5 — Canonical Redis prefix (operator gate-1)

Recommendation: full deckhearth (NOT abbreviated dh).

Trade-off considered: dh saves ~8 bytes per key (vs. deckhearth). At peak load with say 100k active rate-limit identifiers, total savings would be ~800KB of Redis memory — negligible against an Upstash plan that bills by request count, not per-byte storage. The self-documenting deckhearth:auth:… prefix is materially easier to debug when reading Redis console output. (And the cors-tighten Decision-style precedent applies: short-and-cryptic IDs tend to attract "what does this mean?" comments in code, longer-and-clear IDs explain themselves.)

Recommendation: ratify full deckhearth.

Full surface inventory

Architect-verified rg sweep results, grouped by classification. Files under .convoys/** (append-only convoy history per .cursor/rules/no-go- zones.mdc) are listed at the bottom for completeness but are NOT in any brief's files: list — they stay byte-identical.

User-facing display strings (must rename)

File Line Current Brief
README.md 1 # TCG Vault 2 (file already in B2 for email)
TESTING_GUIDE.md 1 # 🎯 TCG Vault Collaboration Testing Guide 2 (file already in B2 for emails)
scripts/README.md 1 # TCG Vault Bulk Import Scripts 1

Brand-bound infrastructure IDs (must rename; has external blast)

File Line Current Blast Brief
lib/rate-limit.js 10-14 tcgvault:auth/search/upload/generate/import Orphans existing Redis counters at deploy time; new counters start at zero 2
package.json 2 "name": "tcg-vault" Lockfile regen required 2
package-lock.json 2, 8 "name": "tcg-vault" (×2) Generated; regen via npm install after package.json change 2
scripts/setup-neon-db.js 148 VALUES (${'admin@tcgvault.com'}, …) Seeds NEW deploys' admin email; needs migration for existing deploys 2
scripts/setup-neon-db.js 157 console.log(' Admin user ready (email: admin@tcgvault.com)') Stdout copy 2
pages/api/cards/import-lorcana.js 14 'User-Agent': 'TCG-Vault/1.0' External HTTP identity sent to Lorcana API 1
pages/api/cards/import-pokemon.js 14 'User-Agent': 'TCG-Vault/1.0' External HTTP identity sent to Pokémon TCG API 1
scripts/import-lorcana.js 20 'User-Agent': 'TCG-Vault/1.0' External HTTP identity (CLI variant) 1

Email-bound infrastructure (must rename; in-DB migration required)

File Lines Current Brief
scripts/setup-neon-db.js 148, 157 admin@tcgvault.com 2 (also infra above)
scripts/reset-db.js 147, 155 admin@tcgvault.com 2
scripts/create-test-users.js 18, 21, 27, 30, 34-36 alice@tcgvault.com, bob@tcgvault.com, admin@tcgvault.com 2
pages/login.js 172, 184 alice@tcgvault.com, bob@tcgvault.com (quick-login fixtures) 2
README.md 132 Admin email: the seed creates admin@tcgvault.com 2
TESTING_GUIDE.md 7-9, 24, 46, 53 admin/alice/bob @tcgvault.com emails in account table + workflow 2
test/lib/permission-middleware.test.js 87 email: 'admin@tcgvault.com' (NEGATIVE regression assertion) 2 (see B2 decision on whether to preserve or update — leaning preserve)
scripts/migrations/2026-05-24-rename-admin-email.js NEW (new file) 2

Internal references in docs / rules / agent context (rename for consistency)

File Line Current Brief
AGENTS.md 5 Branding note: the repo, README, and seed data say "TCG Vault" … 1
.cursor/rules/ui-and-theming.mdc 61 The repo says "TCG Vault" everywhere except … 1
.cursor/rules/auth-and-permissions.mdc 2 description: Auth model + permission model for tcg-vault (…) 1

Repo / Vercel project name (explicitly OUT OF SCOPE per convoy seed)

These references match the literal repo name tcg-vault, which stays for now per the convoy seed § "Keep the repo tcg-vault for now." When the follow-up convoy rename-repo-and-vercel-project ships, these update together with the repo rename. Do NOT touch in this convoy.

File Line Reference
README.md 30 cd tcg-vault (git clone instructions)
README.md 105 tcg-vault/ (directory tree diagram)
AGENTS.md 1 # AGENTS.md — AI collaboration (tcg-vault)
.github/workflows/ci.yml 12 # NOTE: tcg-vault is JavaScript (not TypeScript). …
.github/workflows/ci.yml 123 # The tcg-vault frontend and API are served from the same Vercel …
.github/workflows/visual-diff.yml 5 # Paths are tcg-vault-specific (pages router, JS).
.agent-context-manifest.yml 84, 89, 99, 104, 109, 114 source: "tcg-vault-local" (internal source tag, re-evaluated by sync-agent-context skill at repo rename time)

Already-correct (Deck Hearth — stays byte-identical)

These render the canonical D1 string and have no tcgvault baggage. Do NOT touch:

  • components/Layout.js (lines 623, 716 — sidebar headers)
  • components/AnimatedFireLogo.js (lines 8, 13, 25, 29 — CSS class + alt text + style scope)
  • components/ShareModal.js (line 116 — social share title)
  • pages/index.js (lines 64, 96, 266, 299, 315 — landing page)
  • pages/login.js (lines 76, 158 — welcome copy; emails at 172/184 are in B2)
  • pages/signup.js (line 215 — signup heading)
  • styles/globals.css (line 15 — theme comment)

Convoy artifacts + manifest (no-go per .cursor/rules/no-go-zones.mdc)

.convoys/**/*.md are append-only history — they describe the state at write-time. Per the no-go zone for append-only artifacts and scripts/seed- *.js historical jobs, these are NOT renamed in this convoy:

  • .convoys/fix-auth-bypass/brief-2-remove-admin-bypass.md (4 hits — describes the BUG that returned the OLD synthetic-admin email)
  • .convoys/fix-auth-bypass/brief-5-vitest-and-auth-tests.md (2 hits — same)
  • .convoys/drop-public-setup.md, .convoys/drop-public-setup/brief-*.md (multiple hits — historical decision record)
  • .convoys/add-rate-limiting.md, .convoys/add-rate-limiting/brief-*.md (8 hits — Redis prefix references reflect the pre-rename state at convoy-close; this convoy is what changes them; doc-writer pass at THIS convoy's close updates the gotcha / rule, NOT the historical convoy file)
  • .convoys/cors-tighten/brief-1-sweep-wildcard-cors.md (2 hits — historical)
  • .convoys/fix-layout-default-user.md, .convoys/fix-layout-default-user/brief-*.md (3 hits — historical)
  • .convoys/ship-readiness.md (4 hits — historical milestone log; doc-writer pass appends a NEW "12. RESOLVED" entry rather than rewriting line 244)
  • .convoys/bump-next-js.md, .convoys/fix-vercel-deployment-protection-in-ci.md (each 1 hit — historical)
  • scripts/seed-collections-with-cards.js, scripts/seed-collections-alice-bob.js (3 hits each — historical seed jobs per no-go-zone rule "scripts/seed-*.js — historical migration / seed jobs already executed"; the email migration in B2 covers the in-DB rename, so these scripts' WHERE-clause literals would simply match zero rows post-migration if re-run, which is a harmless no-op)
  • components/Layout.js.backup (1 hit — legacy snapshot per no-go-zone rule)

API surface

No new or modified API routes. No request / response shape changes. The Redis prefix rename in lib/rate-limit.js is internal to the lib module — the five exported check*RateLimit(req[, userId]) functions retain their byte-identical Brief-4-and-add-rate-limiting-Brief-1 contracts: same name, same args, same { allowed, remaining, reset } return shape. The seven gated handlers do NOT need editing.

Schema diff

No DDL changes. The only schema-adjacent operation is the in-DB UPDATE of users.email for at most 3 rows (admin + alice + bob), performed by the new dated migration script scripts/migrations/2026-05-24-rename-admin- email.js. No new tables, no new columns, no new indexes, no new constraints. docs/SCHEMA_MAP.md requires no update (column shapes and table inventory are unchanged).

The new scripts/migrations/ folder is created with this convoy's first migration file. Per .cursor/rules/no-go-zones.mdc § "if the change touches DDL strings or INSERT semantics, file a migration; if it only touches Node-module behavior or pre-flight validation, edit in place and document why in the convoy", the seed scripts' literal email INSERT values in scripts/setup-neon-db.js + scripts/reset-db.js + scripts/create-test- users.js ARE INSERT-semantics changes; the migration script is the required artifact to accompany those edits. The architect ratifies this: the in-place edits keep new-deploy seeding correct; the migration handles the existing-deploy update.

Test plan

No new vitest unit tests in this convoy (decoupled from the rebrand). Per add-rate-limiting Decision 6 precedent — new handler / unit tests are queued under fill-vitest-handler-coverage. The only test edit is the update to test/lib/permission-middleware.test.js line 87's NEGATIVE regression assertion (see B2 acceptance criteria).

Vitest 21/21 must still pass after Brief 1 and Brief 2 land. Brief 1 touches zero files referenced by any test. Brief 2 touches three test- adjacent files (pages/login.js quick-login fixtures, test/lib/permission- middleware.test.js line 87, and the test-user creation scripts) — none of these break the unit-test suite. The negative regression assertion's .toBeNull() check is the canonical assertion; the .not.toEqual({...}) soft check is incidentally about the historical literal email value and will continue to pass against any email.

Playwright smoke 3/3 must still pass after both briefs. Verified at architect time: tests/smoke/app.smoke.spec.ts text matchers are 'sign in' (case-insensitive regex), /api/health, and HTTP status < 500. NONE of these text matchers reference TCG Vault or Deck Hearth brand strings; the smoke spec is brand-agnostic and needs zero edits. (This is the "audit" the prompt requested — confirmed via direct read of the spec file.)

Playwright visual baseline: verified at architect time that tests/visual/__screenshots__/ does NOT yet exist (no committed baselines). Per the adopt-playwright-smoke Decision 4 continue-on-error swallow and the queued seed-visual-baselines-on-linux convoy, the visual-diff workflow's first-run failure is the documented end state. No baseline drift to manage in this convoy. When seed-visual-baselines-on-linux eventually lands, it MUST run AFTER pick-a-name so the seeded baseline captures the Deck Hearth brand strings; the architect updates the seed-visual-baselines-on-linux queued entry below to record this ordering constraint.

Manual verification (operator pre-merge):

  • Boot npm run dev. Visit /, /login, /signup, /cards. Every brand string should render Deck Hearth (verify in browser DevTools "Find").
  • Click the "Alice" / "Bob" quick-login buttons on /login. Expected behavior post-migration: each button fills the email field with <name>@deckhearth.com and the password <name>123. With the migration run, login succeeds and redirects to dashboard. WITHOUT the migration run (e.g., op forgot), login 401s — that's the expected failure mode and the operator's signal to run the migration script.
  • Run node scripts/migrations/2026-05-24-rename-admin-email.js against a staging DB. Expected: prints the count of @tcgvault.com rows found (0-3), UPDATEs them, prints the success count. Re-run: prints "Nothing to migrate. Exiting." (idempotency check).
  • Visit a Vercel preview. Verify all public surfaces render Deck Hearth. Quick-login test (admin / alice / bob with the new emails) only works if the preview's DB is the staging Neon with the migration run.

Risk list

  1. Operator forgets to run the migration script post-deploy. The admin email and Alice/Bob test-user emails would remain @tcgvault.com in the DB while the seed scripts and login.js fixtures reference @deckhearth.com. Symptom: admin login attempts with the new email 401 because the DB still has the old email. Mitigation: the pick-a-name PR description MUST include a "Post-merge operator action" callout naming the migration script and the node scripts/migrations/... command verbatim. (Same defense pattern as drop-public-setup's "operator caveat" for the weak-hash rotation.) Doc-writer pass at convoy close adds this to AGENTS.md Gotcha #4's "Operator caveat" paragraph as a sibling caveat.

  2. Redis counter reset surprises a heavy active user. Rare in pre-launch. The five sliding windows (15-min auth, 1-min search, 1-hour upload/generate/import) all expire within an hour anyway — the worst-case "loss" is that an attacker who had burned 4/5 of the auth budget at the moment of deploy gets reset to 0/5. This is defensively neutral: the legitimate user's prior counter also resets (they too get a fresh 5/15-min budget). Mitigation: none needed; the convoy seed explicitly accepts this trade.

  3. Lockfile regen surfaces unrelated dependency churn. Running npm install after a package.json name change SHOULD only update the name field at the top of package-lock.json plus the packages."" entry inside it. But npm might opportunistically refresh stale integrity hashes or pull in metadata updates. Mitigation: B2 acceptance criteria require the implementer to inspect the package-lock.json diff and confirm it touches only name-related entries; if any unrelated dependencies / version churn appears, the brief instructs the implementer to STOP and flag for a separate bump-deps-cleanup convoy rather than landing accidental dependency updates inside the rebrand PR.

  4. The historical synthetic-admin negative regression assertion in test/lib/permission-middleware.test.js line 87. The literal email admin@tcgvault.com is the OLD bug's exact shape. Two options: (a) preserve the historical literal so the test continues to encode "the helper does NOT return THIS SPECIFIC pre-fix-auth-bypass shape"; or (b) update to admin@deckhearth.com so the test's literal matches the current admin email. Recommendation: preserve (a) and add a comment explaining the historical preservation. The .toBeNull() assertion two lines below is the strong contract; the .not.toEqual({...}) is the soft historical-shape check. B2's acceptance criteria documents the preserve-(a) recommendation but leaves the final call to the implementer.

  5. The .agent-context-manifest.yml source: "tcg-vault-local" tag. Architect-verified at brief time that 6 lines reference this string as an internal source identifier for the sync-agent-context skill's drift detection. Renaming to deck-hearth-local could break the sync skill's behavior (it tracks "what shipped from this source"). Mitigation: leave as-is in this convoy; flag for re-evaluation in rename-repo-and-vercel-project when the repo's literal name actually changes.

  6. Email is the unique constraint on users — collision is impossible but admin-row recreation is. If the operator runs setup-neon-db.js AFTER the migration runs, the seed's INSERT … ON CONFLICT (email) DO NOTHING would NOT recreate the admin row (because the row exists with the NEW email post-migration). That's the correct behavior. If the operator runs setup-neon-db.js BEFORE the migration runs and the existing admin row has the OLD email, the seed inserts a NEW row with the NEW email, creating TWO admin rows (one with each TLD). Mitigation: the migration script's UPDATE is idempotent and handles both cases (it sweeps any remaining @tcgvault.com row to @deckhearth.com, which would collide with the existing @deckhearth.com row's UNIQUE constraint and fail loudly). The architect recommends the operator run the migration FIRST, then confirm via SELECT email FROM users WHERE role = 'admin' returns exactly one row before running setup-neon-db.js. B2 acceptance criteria document this ordering.

  7. B1 and B2 are parallelizable BUT both depend on operator gate-1 ratification of D1-D5 BEFORE dispatch. This is a single gate at the convoy level, not a per-brief gate. Once the operator says "approved, run implementers" with the D1-D5 choices ratified, the conductor can dispatch B1 + B2 in parallel via /multitask since their files: sets are disjoint.

Decomposition

Brief # Title Files Depends on Estimated PR size
1 In-repo display + comment sweep (User-Agent strings, branding notes, rule descriptions) 7 files none (gate-1 ratification of D1+D2+D3 required) ~10-15 lines net
2 Infrastructure + email migration (Redis prefix rename, package.json + lockfile regen, admin/alice/bob email rename in seed scripts + login fixtures + migration script + regression test) 10 files + 1 new migration script none (gate-1 ratification of D1-D5 required); operator pre-merge action: run migration ~65-95 lines net + opaque package-lock.json regen

Slice dependencies (multitask-ready)

slice_dependencies:
  - brief: 1
    depends_on: []
    files:
      - AGENTS.md
      - .cursor/rules/ui-and-theming.mdc
      - .cursor/rules/auth-and-permissions.mdc
      - scripts/README.md
      - pages/api/cards/import-lorcana.js
      - pages/api/cards/import-pokemon.js
      - scripts/import-lorcana.js
  - brief: 2
    depends_on: []
    files:
      - lib/rate-limit.js
      - package.json
      - package-lock.json
      - README.md
      - TESTING_GUIDE.md
      - scripts/setup-neon-db.js
      - scripts/reset-db.js
      - scripts/create-test-users.js
      - pages/login.js
      - test/lib/permission-middleware.test.js
      - scripts/migrations/2026-05-24-rename-admin-email.js

Parallelism: B1's 7 files and B2's 11 files are DISJOINT. The conductor can dispatch both in parallel via /multitask once the operator ratifies D1-D5 at gate-1. Neither brief blocks the other.

Out-of-scope queued follow-ups (reaffirmed + 2 new)

Reaffirms the convoy seed's four:

  • rename-repo-and-vercel-project (P2 polish) — GitHub repo rename (tcg-vaultdeck-hearth) + Vercel project rename + local git remote update + the 8 architect-verified tcg-vault references that cite the literal repo (README cd/tree, AGENTS.md line 1, the three workflow YAML comments, .agent-context-manifest.yml source tags). Auto-redirects make this low-risk-low-urgency.
  • point-domain-at-deckhearth — DNS / domain. Blocked on domain acquisition (operator pre-convoy: doesn't own deckhearth.* yet). If the operator buys a TLD other than .com, this convoy also sweeps the email TLD via a one-line REPLACE migration.
  • regenerate-brand-assets — favicon, OG images, social cards. public/favicon.ico exists today (unverified what brand it is — out of scope to inspect binary in this convoy). P2 polish; requires design pass.
  • migrate-existing-user-emails — for real users with @tcgvault.com emails. Architect-verified at brief time: pre-launch, the ONLY @tcgvault.com accounts are admin + alice + bob (3 fixtures created by create-test-users.js). Brief 2's migration handles all three. If the operator opened the app to friends-and-family pre-this- convoy and any signed up with @tcgvault.com emails, the same migration covers them too (it sweeps every row matching email LIKE '%@tcgvault.com').

Two NEW follow-ups surfaced by architect inventory:

  • convert-reset-db-to-esm (P2 hygiene) — scripts/reset-db.js line 142 uses CommonJS require('bcryptjs') inside an ESM file. Same bug pattern that hit setup-neon-db.js pre-drop-public-setup Brief 2. Brief 2 of pick-a-name does NOT fix this in scope (it touches the email literal and console-log copy only); a separate convoy converts the require → import. May fold into purge-weak-creds-from-helpers.
  • update-seed-visual-baselines-on-linux-ordering — the queued seed-visual-baselines-on-linux convoy MUST run AFTER pick-a-name so the first committed Linux baseline captures the Deck Hearth brand strings (not TCG Vault). Architect recommends the operator (or the conductor of that future convoy) add a depends_on: pick-a-name ordering note to the seed-visual-baselines-on-linux queued entry in .convoys/ship-readiness.md.

As-shipped

Shipped 2026-05-24 as squash commit 9abbab6 (PR #21, architect-commit 50ce9ab, Brief 1 commit ac8c998, Brief 2 commit 1c18d21). This is the first post-P0 convoyadd-rate-limiting (squash 708ef45, PR #20, 2026-05-24) closed the LAST P0 ship-blocker, flipping .convoys/ship-readiness.md's § Status summary to "8 of 8 RESOLVED"; this convoy opens the post-P0 P1 quality-bar phase by closing the brand-consistency P1 (#12 in the same file) that AGENTS.md line 5 has flagged since project setup. Two briefs in the convoy: B1 (display + comment sweep) and B2 (infrastructure + email migration) shipped without scope expansions and with all five D-decisions ratified verbatim at gate-1 plus Risk 4 PRESERVE applied to the negative regression-lock test.

Decisions ratified at gate-1

All five canonical-string choices required operator gate-1 ratification (naming choices are not architect-self-ratifiable per the convoy file's § Architect-proposed decisions header). Operator ratified all five verbatim:

  • D1 — Display string: "Deck Hearth" (two words, internal cap, space-separated). Avoids re-sweeping the seven user-facing surfaces that already render the two-word form (components/Layout.js, components/AnimatedFireLogo.js, pages/index.js, pages/login.js welcome copy, pages/signup.js, components/ShareModal.js, styles/globals.css).
  • D2 — Kebab string: deck-hearth. Matches the existing deck-hearth-logo-container CSS class + Node-ecosystem convention for package.json name fields. Used in package.json rename + the three User-Agent HTTP product tokens in pages/api/cards/import-*.js
    • scripts/import-lorcana.js (Deck-Hearth/1.0, hyphenated lowercase per HTTP UA convention).
  • D3 — Camel/lower for IDs: deckhearth (single token, no separators). Used in the five Redis key prefixes (deckhearth:auth/search/upload/generate/import) — full word, NOT the abbreviated dh (the ~800KB Redis-memory savings at peak load is negligible against an Upstash plan that bills by request count, and the self-documenting prefix is materially easier to debug in Redis console output).
  • D4 — Admin email: admin@deckhearth.com (placeholder TLD pending the queued point-domain-at-deckhearth convoy). Sweep applied to all three @tcgvault.com accounts uniformly via REPLACE(email, '@tcgvault.com', '@deckhearth.com'): admin + alice
    • bob (the test users created by scripts/create-test-users.js).
  • D5 — Redis prefix: full deckhearth. Pattern: deckhearth:auth, deckhearth:search, deckhearth:upload, deckhearth:generate, deckhearth:import. The five-prefixes-must-be- distinct invariant from add-rate-limiting Brief 1 is preserved.

Risk 4 PRESERVE applied: the architect's recommendation (.convoys/pick-a-name.md § Risk list § 4) was to preserve the historical admin@tcgvault.com literal in test/lib/permission-middleware.test.js line 87's negative regression assertion as the EXACT pre-fix-auth-bypass synthetic-admin shape this test locks against. The implementer kept the literal and added a 7-line comment block above the .not.toEqual({...}) call documenting why future doc-writers / refactorers must NOT update the literal to the new @deckhearth.com email — doing so would weaken the regression-lock to a shape that never actually existed. This matches the project's established pattern of treating historical-shape locks as audit-trail artifacts rather than self-consistency markers (Gotchas #2, #3, #4, #5, #8 in AGENTS.md all note "Entry kept (not renumbered) to preserve cross-references" for the same reason). The post-merge state has exactly 1 @tcgvault.com literal outside of .convoys/** and *.md files — the preserved historical regression-lock.

Brief structure (file-disjoint; serial-in-practice)

The architect designed B1 (7 files: AGENTS.md line 5, two .cursor/rules/*.mdc description / branding paragraphs, scripts/README.md title, three User-Agent strings) and B2 (10 modified + 1 new migration script: lib/rate-limit.js 5 prefix renames, package.json name rename, package-lock.json regen, README.md + TESTING_GUIDE.md title + email + operator caveat, three seed/reset/test-user scripts' admin/alice/bob email rename, pages/login.js quick-login fixtures, test/lib/permission-middleware.test.js Risk 4 PRESERVE comment block, new scripts/migrations/2026-05-24-rename-admin-email.js) so that the two files: sets are DISJOINT and the conductor could dispatch them in parallel via /multitask. In practice the two briefs landed serially (B1 commit ac8c998 first, B2 commit 1c18d21 second on the same branch) for the lockfile-regen + migration-script-author caution recommended by Brief 2 § Risk 3 (lockfile STOP-on-churn verification is easier to audit if no other diff is in flight). The parallel-via-multitask path stayed open as the architect's intended fallback if either brief had hit a blocker.

As-shipped diff

Per git show --stat 9abbab6:

  • B1 (ac8c998): 7+/7- across 7 files (the 7 files: listed in .convoys/pick-a-name/brief-1-display-and-comment-sweep.md). Pure string-literal edits; no new code, no new imports, zero ESLint surface added.
  • B2 (1c18d21): 133+/30- across 10 modified files + 1 new migration script. The +/- imbalance is dominated by the new ~85-line migration script (scripts/migrations/2026-05-24-rename- admin-email.js) + the new operator-caveat blockquote in README.md (~10 lines added) + the 7-line Risk 4 PRESERVE comment block in test/lib/permission-middleware.test.js. Source-code edits in lib/rate-limit.js (5 prefix lines) + pages/login.js (2 quick- login fixtures) + scripts/setup-neon-db.js / reset-db.js / create-test-users.js (admin/alice/bob email INSERTs + console logs) are pure string-literal swaps.
  • Lockfile regen: the package-lock.json diff was confirmed before B2 commit to touch ONLY the two name lines (top-level + packages[""] entry); the brief's STOP-on-churn rule (Risk 3) was exercised by the implementer and confirmed clean — no opportunistic dependency-metadata refresh, no integrity-hash churn, no version bumps. npm ci succeeded against the regenerated lockfile.

As-shipped CI metrics

Post-merge run 26387909625 + subsequent runs on main:

  • Playwright smoke — PASS in 1m4s, 3/3 tests against the rebranded Vercel preview. Same three checks (home redirects or renders without 5xx ✓ / sign-in page renders ✓ / public health endpoint responds ✓) — all green. The new brand strings render correctly on / + /login; the auth surface still works (smoke test 2 still asserts the <Link href="/login">Sign in</Link> CTA wording, which is brand-agnostic per architect-time verification of the smoke spec's text matchers); no false 429s from the renamed Redis prefixes (the post-rename deckhearth:* keys were brand-fresh in Upstash, but the smoke spec's /api/health hit is anonymous and unrate-limited and the home + sign-in routes don't touch any of the 6 newly-gated rate-limit endpoints).
  • Aggregate gate — PASS in 10s. All upstream gate dependencies green.
  • Lint — PASS in 45s, 128 problems (lint baseline preserved, no regression from B1 or B2). Zero new lint problems; || true wrapper in .github/workflows/ci.yml was a no-op for this convoy.
  • forbidden-endpoints (from fix-auth-bypass Brief 3) — PASS in 4s. No new pages/api/test-*.js shapes reintroduced.
  • forbidden-cors-headers (from cors-tighten) — PASS in 4s. Pure brand-rename diff; no Access-Control-Allow-* headers reintroduced.
  • Unit tests (vitest) — PASS, 21/21 in 35s. The Risk 4 PRESERVE comment-block addition kept the negative regression-lock test green byte-identically — the assertion semantics are unchanged; only the comment block above the .not.toEqual({...}) call is new. Decision 6 (no new vitest specs in this convoy) honored per the add-rate-limiting D6 + cors-tighten D4 precedent.
  • Screenshot diff — PASS in 1m5s (continue-on-error: true swallow per adopt-playwright-smoke Decision 4 still active; the rebrand introduced no visual regression because no baseline is committed yet — the queued seed-visual-baselines-on-linux convoy generates the FIRST baseline and that baseline will capture the Deck Hearth brand strings, per the architect's ordering note in .convoys/pick-a-name.md § Test plan + the updated entry in .convoys/ship-readiness.md § Queued convoys).
  • Schema map up to date — skipping (only runs on Prisma changes; this convoy made an INSERT-semantics change but no DDL change).
  • Vercel deployment — completed cleanly. First production-tier Vercel deploy serving the deck-hearth package.json name.

Cross-validation finding: smoke spec defends the brand-rename surface (organically)

This convoy is the fourth in a row where the same 3-test Playwright smoke spec authored by adopt-playwright-smoke (PR #18, 2026-05-24) defended the auth surface through a sweeping change without anyone having to write a dedicated test. The lineage now reads:

  • PR #15 (fix-layout-default-user, ca302a8) — introduced the <Link href="/login">Sign in</Link> CTA that smoke test 2 asserts on.
  • PR #19 (cors-tighten, da50d78) — removed wildcard CORS from 24 handlers; smoke test 2 still passed against the post-CORS preview.
  • PR #20 (add-rate-limiting, 708ef45) — wired 6 new route gates (search/upload/generate/import) and refactored lib/rate-limit.js end-to-end; smoke test 2 still passed (the new 60/min search ceiling doesn't 429 the smoke spec because home + /login don't touch any gated route).
  • PR #21 (pick-a-name, 9abbab6) — this convoy — renamed the product brand end-to-end across 17 files + 1 new migration script, including all 5 Redis key prefixes; smoke test 2 still passes.

The smoke spec's text matchers ('sign in' regex, /api/health, HTTP status < 500) are brand-agnostic by architect-time design — but organically, smoke is doing real work: it has now defended the auth surface against four sweeping changes spanning the full P0 #5

  • P0 #6 + P0 #7 closures + the P1 #12 brand-consistency closure. That's a "trust the smoke spec" data point worth preserving. Future sweeping changes (single-auth-provider, single-sql-client, the god-component-split convoys) should expect the smoke spec to keep catching auth-surface regressions without dedicated coverage — unless and until a feature change deliberately rewords the <Link>Sign in</Link> CTA, in which case the smoke spec needs a matcher update in the same PR.

Implementer-vs-architect calibration note

The architect's B2 forecast paragraph (.convoys/pick-a-name/brief-2- infrastructure-and-email-migration.md line 662, the post-B2 repo-wide grep cross-check) reads "Expected: at most 1 match — test/lib/permission-middleware.test.js line 87's preserved historical regression literal". Architect-verified — the implementer's actual post-B2 state showed exactly 1 non-.convoys/-non-*.md match. But the architect's authoritative § Full surface inventory § Convoy artifacts + manifest list (line 344 of the convoy file) had already correctly enumerated scripts/seed-collections-with-cards.js and scripts/seed-collections-alice-bob.js (3 hits each) as DO-NOT-TOUCH historical seed jobs per .cursor/rules/no-go-zones.mdc § Append-only / historical, with the explicit reasoning: "the email migration in B2 covers the in-DB rename, so these scripts' WHERE-clause literals would simply match zero rows post-migration if re-run, which is a harmless no-op." So the forecast paragraph and the inventory are both correct — the forecast counted "in-scope source files" while the inventory documented why the seed-collections matches stay. Worth noting for the next architect: when forecasting post-brief grep-cleanliness, cite the inventory's exact carve-out rationale to avoid a reader thinking the two numbers contradict each other. Not a process gap — both ends were right; the cross-reference between them could be tighter.

Operator action required going forward

Run node scripts/migrations/2026-05-24-rename-admin-email.js against the prod Neon DB before any admin login attempt with the new admin@deckhearth.com email. The migration is ESM, idempotent, UNIQUE-collision-safe — re-running on an already-migrated DB prints "Nothing to migrate." and exits 0; running on a DB where the seed script already created a duplicate admin@deckhearth.com row fails loud on the users.email UNIQUE constraint (which is the correct fail-loud behavior — manually delete one of the duplicate admin rows before retrying). Order: migration FIRST, then any subsequent npm run setup-db — the migration sweeps the existing @tcgvault.com row to @deckhearth.com; a subsequent setup-db sees the row exists with the new email and no-ops via ON CONFLICT (email) DO NOTHING.

Documented in three places for redundancy:

  • AGENTS.md Gotcha #4 (extended in this doc-writer pass with the email-rename + migration-script + ordering paragraph).
  • README.md operator caveat blockquote (added by B2 alongside the existing admin123-rotation caveat from drop-public-setup).
  • The migration script's own header comment + stdout messages.

Two downstream convoys are queued in .convoys/ship-readiness.md § Queued convoys after this one:

  • rename-repo-and-vercel-project (P2 polish) — GitHub repo + Vercel project rename to deck-hearth; auto-redirects make it low-urgency. One-line update to local git remotes + Vercel link
    • the 8 architect-verified tcg-vault literal-repo references (README cd / tree, AGENTS.md line 1, three workflow YAML comments, .agent-context-manifest.yml source tags).
  • point-domain-at-deckhearth (blocked on domain acquisition) — DNS pointer + Vercel domain claim + admin email TLD swap if the operator buys a TLD other than .com.

What did NOT change

Audit trail of files explicitly NOT touched by this convoy, despite sitting near the brand-rename surface:

  • All .convoys/** historical artifacts. ~25 lines across the drop-public-setup / fix-auth-bypass / cors-tighten / add-rate-limiting / fix-layout-default-user / bump-next-js / fix-vercel-deployment- protection convoy files reference tcgvault / tcg-vault / TCG Vault as the historical state at write-time — per .cursor/rules/no-go-zones.mdc § Append-only / historical, these stay byte-identical. The doc-writer pass at THIS convoy's close rewrites only .convoys/pick-a-name.md (this file) + Gotcha #4 + Gotcha #12 of AGENTS.md + the launch-sequence step in .convoys/ship-readiness.md + the lineage attribution in .cursor/rules/api-routes.mdc. No retroactive historical rewrite.
  • scripts/seed-collections-with-cards.js, scripts/seed-collections-alice-bob.js, scripts/add-*.js, scripts/fix-*.js — historical seed / migration jobs per the no-go-zones rule. The 6 hits (3 each in the two seed-collections scripts) of @tcgvault.com literals in their WHERE-clauses are now stale — the migration sweeps the in-DB rows, so any re-run of these scripts would match zero rows and no-op harmlessly.
  • Repo-name referencesREADME.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. All literally describe the GitHub repo + Vercel project, both of which still ARE named tcg-vault until the queued rename-repo-and-vercel-project convoy ships. Per the convoy seed § "Keep the repo tcg-vault for now".
  • docs/agent-context/README.md line 78 ("we'll measure tcg-vault's number after a few convoys") and .cursor/skills/add-api-route/SKILL.md line 7 ("…for the tcg-vault stack") — both reference the literal repo name, not the product brand. Stay byte-identical; they sweep together with rename-repo-and-vercel-project.
  • scripts/reset-db.js line 142 (CJS-in-ESM bug) and lines 143 + 156 (admin123 weak password literals) — out of scope per convert-reset-db-to-esm (now queued, surfaced 2026-05-24 during this convoy's architect inventory) + purge-weak-creds- from-helpers. B2 only renamed the email literals on lines 147 + 155; the CJS bug + weak-password literals stay byte-identical for the dedicated follow-up convoys to fix together.
  • tests/smoke/**, tests/visual/**, playwright.config.js — smoke spec is brand-agnostic (architect-verified at brief time); visual baseline doesn't exist yet (per adopt-playwright-smoke Decision 4). The queued seed-visual-baselines-on-linux convoy generates the first baseline AFTER pick-a-name lands so the captured strings are Deck Hearth (now satisfied — 9abbab6 is on main).
  • .agent-context-manifest.yml source: "tcg-vault-local" tags (6 entries) — internal sync-agent-context skill source identifier for drift tracking. Renaming could break the skill's behavior; flagged for re-evaluation in rename-repo-and-vercel-project when the actual repo name changes (Risk 5 of the convoy file).
  • pages/api/cards/import-mtg.js's User-Agent — the file does NOT set a custom UA (Scryfall is fine with Node's default per Brief 1 Finding 2). Only the Lorcana + Pokémon TCG import handlers
    • the standalone scripts/import-lorcana.js CLI variant set Deck-Hearth/1.0.
  • No new vitest or playwright specs — Decision 6 honored. The Risk 4 PRESERVE comment-block addition is the only test-file edit; assertion semantics are unchanged.