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).
50 KiB
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-vaultfor now. GitHub rename is a separate concern (auto-redirects work, but Vercel-link /ghconfigs / local remotes / bookmarks all need touching). Track as a queued follow-up convoyrename-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.jsusestcgvault:auth/search/upload/generate/importas Redis key prefixes. These must rename todeckhearth:*(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.comrename toadmin@deckhearth.com(or similar — architect proposes the canonical email, operator ratifies). Includes the in-DB UPDATE for any existingadmin@tcgvault.comrow (write a one-offscripts/migrations/YYYY-MM-DD- rename-admin-email.jsper the no-go-zones rule for new migrations). package.jsonname field: rename todeck-hearthin 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.jsonin 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 adeckhearth.*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-assetsif they need new design.
Architect deliverables
- Full surface inventory — every occurrence of
TCG Vault,tcgvault,tcg-vault, plus every occurrence ofDeck Hearth/deck-hearth/deckhearth(the latter set tells you what's already correct and what shape collisions need handling, e.g., if Layout.js hasDeck Hearthbut AnimatedFireLogo.js hasDECKHEARTH— pick canonical casing). - 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.comor variant); the canonical Redis prefix (deckhearthordh). Mark each as D1-D5 (operator gate-1 for all five — they're naming choices, not architectural ones). - 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). - Boot-the-brief verification for each: confirm specs compile, lint passes, vitest passes, smoke can still run against a preview.
- 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-smokeDecision 4continue-on-errorswallow is still in effect, so visual-diff drift won't fail CI — but the queuedseed-visual-baselines-on-linuxconvoy 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.mdline 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 inpick-a-nameconvoy; 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 owndeckhearth.*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.comemails (besides admin), propose a migration. Architect to surface count via arg/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.jslines 623 + 716 →Deck Hearthcomponents/AnimatedFireLogo.jsline 13 →Deck Hearth Logopages/index.jslines 64, 96, 266, 299, 315 →Deck Hearthpages/login.jslines 76, 158 →Deck Hearthpages/signup.jsline 215 →Deck Hearthcomponents/ShareModal.jsline 116 →Deck Hearthstyles/globals.cssline 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 renderDeck 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.comand 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.jsagainst a staging DB. Expected: prints the count of@tcgvault.comrows 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
-
Operator forgets to run the migration script post-deploy. The admin email and Alice/Bob test-user emails would remain
@tcgvault.comin 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: thepick-a-namePR description MUST include a "Post-merge operator action" callout naming the migration script and thenode scripts/migrations/...command verbatim. (Same defense pattern asdrop-public-setup's "operator caveat" for the weak-hash rotation.) Doc-writer pass at convoy close adds this toAGENTS.mdGotcha #4's "Operator caveat" paragraph as a sibling caveat. -
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.
-
Lockfile regen surfaces unrelated dependency churn. Running
npm installafter apackage.jsonnamechange SHOULD only update thenamefield at the top ofpackage-lock.jsonplus thepackages.""entry inside it. But npm might opportunistically refresh staleintegrityhashes or pull in metadata updates. Mitigation: B2 acceptance criteria require the implementer to inspect thepackage-lock.jsondiff and confirm it touches onlyname-related entries; if any unrelateddependencies/versionchurn appears, the brief instructs the implementer to STOP and flag for a separatebump-deps-cleanupconvoy rather than landing accidental dependency updates inside the rebrand PR. -
The historical synthetic-admin negative regression assertion in
test/lib/permission-middleware.test.jsline 87. The literal emailadmin@tcgvault.comis 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-bypassshape"; or (b) update toadmin@deckhearth.comso 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. -
The
.agent-context-manifest.ymlsource: "tcg-vault-local"tag. Architect-verified at brief time that 6 lines reference this string as an internal source identifier for thesync-agent-contextskill's drift detection. Renaming todeck-hearth-localcould break the sync skill's behavior (it tracks "what shipped from this source"). Mitigation: leave as-is in this convoy; flag for re-evaluation inrename-repo-and-vercel-projectwhen the repo's literal name actually changes. -
Email is the unique constraint on
users— collision is impossible but admin-row recreation is. If the operator runssetup-neon-db.jsAFTER the migration runs, the seed'sINSERT … ON CONFLICT (email) DO NOTHINGwould NOT recreate the admin row (because the row exists with the NEW email post-migration). That's the correct behavior. If the operator runssetup-neon-db.jsBEFORE 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.comrow to@deckhearth.com, which would collide with the existing@deckhearth.comrow's UNIQUE constraint and fail loudly). The architect recommends the operator run the migration FIRST, then confirm viaSELECT email FROM users WHERE role = 'admin'returns exactly one row before runningsetup-neon-db.js. B2 acceptance criteria document this ordering. -
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
/multitasksince theirfiles: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-vault→deck-hearth) + Vercel project rename + local git remote update + the 8 architect-verifiedtcg-vaultreferences that cite the literal repo (README cd/tree, AGENTS.md line 1, the three workflow YAML comments,.agent-context-manifest.ymlsource tags). Auto-redirects make this low-risk-low-urgency.point-domain-at-deckhearth— DNS / domain. Blocked on domain acquisition (operator pre-convoy: doesn't owndeckhearth.*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.icoexists 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.comemails. Architect-verified at brief time: pre-launch, the ONLY@tcgvault.comaccounts are admin + alice + bob (3 fixtures created bycreate-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.comemails, the same migration covers them too (it sweeps every row matchingemail LIKE '%@tcgvault.com').
Two NEW follow-ups surfaced by architect inventory:
convert-reset-db-to-esm(P2 hygiene) —scripts/reset-db.jsline 142 uses CommonJSrequire('bcryptjs')inside an ESM file. Same bug pattern that hitsetup-neon-db.jspre-drop-public-setupBrief 2. Brief 2 ofpick-a-namedoes NOT fix this in scope (it touches the email literal and console-log copy only); a separate convoy converts the require → import. May fold intopurge-weak-creds-from-helpers.update-seed-visual-baselines-on-linux-ordering— the queuedseed-visual-baselines-on-linuxconvoy MUST run AFTERpick-a-nameso 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 adepends_on: pick-a-nameordering note to theseed-visual-baselines-on-linuxqueued 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 convoy — add-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.jswelcome copy,pages/signup.js,components/ShareModal.js,styles/globals.css). - D2 — Kebab string:
deck-hearth. Matches the existingdeck-hearth-logo-containerCSS class + Node-ecosystem convention forpackage.jsonnamefields. Used inpackage.jsonrename + the threeUser-AgentHTTP product tokens inpages/api/cards/import-*.jsscripts/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 abbreviateddh(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 queuedpoint-domain-at-deckhearthconvoy). Sweep applied to all three@tcgvault.comaccounts uniformly viaREPLACE(email, '@tcgvault.com', '@deckhearth.com'): admin + alice- bob (the test users created by
scripts/create-test-users.js).
- bob (the test users created by
- D5 — Redis prefix: full
deckhearth. Pattern:deckhearth:auth,deckhearth:search,deckhearth:upload,deckhearth:generate,deckhearth:import. The five-prefixes-must-be- distinct invariant fromadd-rate-limitingBrief 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 7files: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 inREADME.md(~10 lines added) + the 7-line Risk 4 PRESERVE comment block intest/lib/permission-middleware.test.js. Source-code edits inlib/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.jsondiff was confirmed before B2 commit to touch ONLY the twonamelines (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, noversionbumps.npm cisucceeded 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-renamedeckhearth:*keys were brand-fresh in Upstash, but the smoke spec's/api/healthhit 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;|| truewrapper in.github/workflows/ci.ymlwas a no-op for this convoy.forbidden-endpoints(fromfix-auth-bypassBrief 3) — PASS in 4s. No newpages/api/test-*.jsshapes reintroduced.forbidden-cors-headers(fromcors-tighten) — PASS in 4s. Pure brand-rename diff; noAccess-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 theadd-rate-limitingD6 +cors-tightenD4 precedent.Screenshot diff— PASS in 1m5s (continue-on-error: trueswallow peradopt-playwright-smokeDecision 4 still active; the rebrand introduced no visual regression because no baseline is committed yet — the queuedseed-visual-baselines-on-linuxconvoy 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-hearthpackage.jsonname.
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 refactoredlib/rate-limit.jsend-to-end; smoke test 2 still passed (the new 60/min search ceiling doesn't 429 the smoke spec because home +/logindon'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.mdGotcha #4 (extended in this doc-writer pass with the email-rename + migration-script + ordering paragraph).README.mdoperator caveat blockquote (added by B2 alongside the existingadmin123-rotation caveat fromdrop-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 todeck-hearth; auto-redirects make it low-urgency. One-line update to local git remotes + Vercel link- the 8 architect-verified
tcg-vaultliteral-repo references (README cd / tree, AGENTS.md line 1, three workflow YAML comments,.agent-context-manifest.ymlsource tags).
- the 8 architect-verified
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 referencetcgvault/tcg-vault/TCG Vaultas 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 ofAGENTS.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.comliterals 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 references —
README.mdlines 30 + 105,AGENTS.mdline 1,.github/workflows/ci.ymllines 12 + 123,.github/workflows/visual-diff.ymlline 5,.agent-context-manifest.ymlsource tags. All literally describe the GitHub repo + Vercel project, both of which still ARE namedtcg-vaultuntil the queuedrename-repo-and-vercel-projectconvoy ships. Per the convoy seed § "Keep the repotcg-vaultfor now". docs/agent-context/README.mdline 78 ("we'll measure tcg-vault's number after a few convoys") and.cursor/skills/add-api-route/SKILL.mdline 7 ("…for the tcg-vault stack") — both reference the literal repo name, not the product brand. Stay byte-identical; they sweep together withrename-repo-and-vercel-project.scripts/reset-db.jsline 142 (CJS-in-ESM bug) and lines 143 + 156 (admin123weak password literals) — out of scope perconvert-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 (peradopt-playwright-smokeDecision 4). The queuedseed-visual-baselines-on-linuxconvoy generates the first baseline AFTERpick-a-namelands so the captured strings are Deck Hearth (now satisfied —9abbab6is onmain)..agent-context-manifest.ymlsource: "tcg-vault-local"tags (6 entries) — internalsync-agent-contextskill source identifier for drift tracking. Renaming could break the skill's behavior; flagged for re-evaluation inrename-repo-and-vercel-projectwhen the actual repo name changes (Risk 5 of the convoy file).pages/api/cards/import-mtg.js'sUser-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.jsCLI variant setDeck-Hearth/1.0.
- the standalone
- 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.