597 lines
33 KiB
Markdown
597 lines
33 KiB
Markdown
|
|
# pick-a-name (P1 — pre-launch brand consistency)
|
|||
|
|
|
|||
|
|
**Status:** in-progress
|
|||
|
|
**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
|
|||
|
|
**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)
|
|||
|
|
|
|||
|
|
```yaml
|
|||
|
|
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-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
|
|||
|
|
|
|||
|
|
(To be appended post-merge by `role-doc-writer`.)
|