Repository navigation
Contribution Record
This page is the running ledger of contributions to DeckScout. It tracks what came from agents vs humans, and which human each agent worked on behalf of.
-
Agent-authored commits (repo AND wiki) carry an
On-Behalf-Of: <github-handle>trailer identifying the human the agent works for, plus aCo-Authored-Bytrailer identifying the agent model. -
Human contributors' own commits carry no special trailer -- the absence of
On-Behalf-Ofmeans the commit is directly human-authored. - Wiki page footers name the last agent + human pair that updated the page.
- This ledger is append-only. Agents append one line per work session.
| Date | Agent | On behalf of | What |
|---|---|---|---|
| 2026-08-09 | Claude (opus agent) | @cheyras | Initial wiki authoring: created all pages from repo design docs, set up attribution system |
| 2026-08-09 | Claude (opus-4-6 agent) | @cheyras | Dependabot triage: merged 8/8 PRs (#3-#10) -- actions/setup-node 7, actions/checkout 7, pnpm/action-setup 6, github/codeql-action 4, grouped npm minor+patch (9 pkgs), @types/node 26, node-cron 4, typescript 7.0. All locally verified (typecheck, test:deck 49/49, builds). |
| 2026-08-09 | Claude (opus-4-6 agent) | @cheyras | Vercel deployment: created deck-scout team project, set 14 env vars (prod+preview), fixed TS 7.0 @vercel/node crash (pre-build API + .mjs entry), fixed .vercelignore root anchoring, dynamic base path for cloud/self-host. Preview + production deployed and verified (API health OK, auth 401 OK, SPA renders login page). Git connect pending owner GitHub Login Connection. |
| 2026-08-09 | Claude (opus-4-6 agent) | @cheyras | fix(cloud): auth-page reload loop (88 navigations/20s). Root cause: AppShell's ProfileChip fired api.overview() on /auth -> 401 -> handle401 -> window.location.assign('/auth') -> hard reload loop. Fixed handle401 redirect guard, stripped AppShell chrome on /auth, made SW paths dynamic (BASE from self.location), cleaned /pokedex/ favicon vestiges. Verified: login completes, real collection data renders (desktop + mobile). |
| 2026-08-09 | Claude (opus-4-6 agent) | @cheyras | End-to-end domain verification on deckscout.io: HTTP checks (apex 200, www 308->apex, /api/health ok+db up, /api/series 401), Playwright login+session stability (no reload loop, 0 nav delta/20s), app shell confirmed (20 series, Mega Evolution 13.5%, Trainer 38), desktop+mobile screenshots, bug reporter E2E (GH issue #11 created, privacy verified -- no email/user-id in issue body, Report-ID present), session persistence after reload. Found defect: bug_report.github_issue_number NULL despite GH issue created (likely RLS blocking UPDATE). No logout control exists yet. |
| 2026-08-09 | Claude Opus 4.6 | @cheyras | Cleanup/close-out wave: fresh-Supabase-build preflight fix (migrate.ts orphan cleanup before 021 FK), migrate-to-cloud singleton fix (ON CONFLICT DO UPDATE for user_profile/user_settings), @types/node-cron removal (node-cron v4 ships own types), engine package rename @pokedex/engine -> @deckscout/engine, PWA verification (manifest+SW+registration all green), DECISIONS.md cloud-connection-day entry, wiki sync. |
| 2026-08-09 | Claude Opus 4.6 | @cheyras | Two cloud fixes: (1) bug_report.github_issue_number never persisted -- RLS blocked UPDATE via q1(); fix: RESET ROLE on rlsStore client (same txn, sees uncommitted INSERT, bypasses RLS), then restore 'authenticated'. Verified live: row ba3493cf has github_issue_number=13. (2) No logout control existed -- added sign-out button on Profile page (cloud-only, hidden in self-host), with logout icon in Icon.tsx. Playwright-verified: button visible, click -> /auth, reload stays logged out, re-login works. Closed GH issues #12 + #13 (test reports). |
| 2026-08-10 | Claude Opus 5 | @cheyras | Issue #15: removed the hand-drawn McDonald's Golden Arches SVG (a third-party trademark reproduced as app chrome). Established TCGdex publishes no logo for any of the 12 McDonald's Collection sets in any of 14 languages; rejected pokemontcg.io (serves McDonald's corporate logo, identical bytes across 9 sets) and Bulbagarden (2 of 12, fair-use tagged) on trademark grounds. Replaced with the real TCGdex set symbol on the series card via a rep/repHasLogo fallback (Trainer kits fixed too, 17 logo-bearing series unchanged) plus a typographic year tag for the rest. manifest:check clean on disk (47,924/47,924) and object (2,946/2,946) tiers; no bytes added. Browser-verified at 1440 + 390 locally; deployed deckscout.io render pending a QA account. |
| 2026-08-10 | Claude Fable 5 | @cheyras | Public marketing landing page for logged-out visitors: built an AI Gateway image-generation pipeline (candidate/start-at budget controls, fail-fast on account-level gateway errors) and five DOM/CSS/SVG product mockups that render complete with zero image bytes; corrected the landing copy to quote the English-only catalog counts (20,964 cards / 203 sets) instead of raw table totals that included Pokemon TCG Pocket. |
| 2026-08-10 | Claude Fable 5 | @cheyras | Auth surface polish: /auth gained URL-driven signup/forgot modes and inline validation, /auth/reset handles Supabase's implicit-flow recovery link (fragment tokens, the PASSWORD_RECOVERY timing race), /signed-out is a real confirmation page, Profile gained an in-app change-password card, and GoTrue error codes now map to one actionable sentence each. |
| 2026-08-10 | Claude Opus 5 | @cheyras | Cloud image tier: added the serverless route deckscout.io was missing entirely -- every card image and set logo had been serving the SPA shell as image/html. /deckscout/images/* now lazily fills the public card-art Supabase Storage bucket from image_asset.source_url on miss and 302s to the object on hit, sharing path/cache-key logic with self-host via a new @deckscout/storage package. |
| 2026-08-10 | Claude Opus 5 | @cheyras | Image tier round two: sprites now fill from the pinned PokeAPI commit, cold fills try the .webp -> .png -> .jpg extension ladder (TCGdex re-encodes per extension), a targeted storage:backfill script mirrors the local cache into the bucket, Vercel functions pinned to sfo1 to co-locate with Supabase's us-west-2 (~90ms -> ~20-25ms per DB round trip), and the Pokedex species-detail route fixed (it called /insights/deckscout instead of /insights/pokedex). |
| 2026-08-10 | Claude Opus 5 | @cheyras | Page-load perf: measured against prod and fixed four round-trip sources -- batched the RLS BEGIN/set_config/SET LOCAL sequence into one round trip, parallelized the 9-query /cards/:id fan-out into one statement, rewrote /series's set/card counts as a CTE (661ms -> 46ms), and inlined set-page variant/owned-quantity data so the grid stops firing one GET per tile. Also fixed the service worker's image CacheFirst route, which matched a path cloud never requests (0/18 images served from cache before the fix). |
| 2026-08-10 | Claude Opus 5 | @cheyras | image_object: split image_asset's identity/provenance from the physical facts of each stored copy, since the disk and Supabase Storage tiers legitimately disagree on re-encoded bytes. Migration 025 adds a per-(cache_key, tier) row; manifest:check --object-store makes the object tier's "no byte without a row" claim falsifiable for the first time. Backfilled 47,924 disk-tier and 2,835 object-tier rows, then audited the 1,854 images with no resolvable upstream URL byte-for-byte against Supabase's MD5 etag -- all matched, nothing needed re-filling. |
| 2026-08-10 | Claude Opus 5 | @cheyras | Issues #18/#19: user_set_progress had RLS enabled with a SELECT-only policy, but recomputeSetProgress() writes it inside every collection mutation -- increment, decrement, set-quantity and have/need all 500'd on cloud with the UI's optimistic count reverting. Migration 028 adds INSERT/UPDATE policies scoped to auth.uid(); audited every other RLS table for the same missing-write-policy shape and found no second instance. |
| 2026-08-10 | Claude Opus 5 | @cheyras | MCP goes multi-user: personal access tokens (dsk_... prefix, sha256-hashed, shown once) at /mcp let any signed-up user point Claude at their own collection through the existing 21 tools, each running inside that user's RLS transaction. Then made claude.ai's non-beta connector dialogs work too -- /mcp accepts the token as the last path segment as well as a Bearer header -- and wrote the six-step setup/verify/revoke guide now in Profile -> Agent access and DEPLOYMENT.md. |
| 2026-08-10 | Claude Fable 5 | @cheyras | Shipped the landing page's marketing imagery (6 assets picked from 18 flux-2-pro candidates, responsive AVIF/WebP, largest 212KB), fixed .vercelignore's blanket .webp/.avif rules that were silently dropping them from the upload, implemented real libpq PGSSLMODE semantics in makePool (node-postgres mapped every verifying mode to full chain verification, breaking the exact Supabase connection command DEPLOYMENT.md documents), and retired scripts/storage-backfill.mjs (bypassed the B1 provenance choke point) in favor of the supported storage:backfill module command. |
| 2026-08-10 | Claude Opus 5 (1M context) | @cheyras | Self-host regression: the cloud pivot's ~30-route rewrite from defaultUserId() to req.user!.id was correct in cloud but broke self-host outright (req.user is undefined there by design; the assertion erased at compile time and undefined hit WHERE user_id = $1, 500ing /insights/overview and every collection/deck write). Introduced apps/api/src/identity.ts as the single identity seam with a CI guard that scans routes for regressions; also fixed deck_version/battle_log writes that had been violating a NOT NULL user_id since migration 020, on both deployments. |
| 2026-08-10 | Claude Opus 5 (1M context) | @cheyras | Rewrote the landing page to lead with the MCP connector instead of burying it below five feature sections: new hero eyebrow/headline naming the mechanism, a new AgentMockup showing an assistant reply citing the visitor's own shelf with reconciling arithmetic, and a new AgentFlow section spelling out token -> connector -> ask and the 21 tools. |
| 2026-08-10 | Claude Opus 5 (1M context) | @cheyras | Verified the deck/battle-log cloud write path end-to-end through a real session, then fixed issue #17 (bug reporter screenshots silently omitted): html2canvas 1.4.1 couldn't parse Tailwind 4's oklab() color functions (moved to the html2canvas-pro fork), and the card-art redirect to Supabase Storage was tainting the canvas (fixed by inlining every image to a data: URL before capture, read through a dedicated CORS-safe ?bugshot=1 marker). |
| 2026-08-10 | Claude Opus 5 (1M context) | @cheyras | Issue #21: the catalog had rotted 2.5 weeks stale (222 cards missing across 10 sets) because nothing refreshed it, and the importer itself couldn't ingest a refresh -- upstream had re-keyed the four SWSH Trainer Gallery subsets without renaming them (froze the whole import on a UNIQUE collision) and reshuffled retired card_variant sort_order (detonated a DEFERRABLE constraint at COMMIT). Both are now detected and handled in place rather than aborting the run. |
| 2026-08-10 | Claude Opus 5 (1M context) | @cheyras | Issue #14: profile photos -- upload/replace/remove, a public 32-hex-key Supabase bucket (image/webp, 1MiB cap), server-side magic-byte validation plus a mandatory sharp re-encode to 256x256 WebP (the real content check, the EXIF/GPS privacy fix, and the weight fix all at once), and a documented B1 exception since avatars have no image_asset-shaped provenance. Browser testing caught a second bug: the avatar disc rendered empty for the length of a fetch because the img/glyph was an if/else instead of a layered fallback. |
| 2026-08-10 | Claude Opus 5 (1M context) | @cheyras | Issue #20: the hosted card scanner matched nothing, not because the in-memory index broke as predicted but because it shelled out to ImageMagick, which a serverless function doesn't have. Swapped to sharp, which forced an ALGO bump and full re-index (0-9 bit hash divergence from ImageMagick) and two 3x SQL-matcher cost fixes; kept the Hamming threshold at 9 based on a measured 389-scan degradation curve. |
| 2026-08-10 | Claude Opus 5 (1M context) | @cheyras | Issue #21 (art side): re-keyed the four SWSH Trainer Gallery sets' cached art to follow the catalog's rename rather than re-fetching (upstream 404s both the old and new ids, so a re-fetch would have deleted 120 cards' art, not restored it) across both the disk and Supabase Storage tiers. Then scheduled the fix that should have caught the staleness: .github/workflows/catalog-refresh.yml runs weekly, deliberately fails the job on an unhandled rename after the import has already committed, and prints the exact rekey:set commands to run. |
| 2026-08-10 | Claude Opus 5 (1M context) | @cheyras | Issue #16: the Pokemon Organized Play logo and 19 other set logos are inked for white cardboard (black wordmarks, no light stroke) and disappear on the app's dark surfaces. Measured all 157 cached logos by orphaned-ink color distance to find the 20 affected; added a that puts a light plate behind exactly those, decided offline and baked into a static id list rather than runtime image analysis. |
| 2026-08-10 | Claude Opus 5 (1M context) | @cheyras | Issue #21 sibling: the same upstream SWSH Trainer Gallery rename that stranded card art had also stranded the deck lane -- ptcgl-set-alias.json and one banlist entry still pointed at the four retired set ids, so resolveDeck() silently resolved "Flareon BRS-TG" to an unrelated Scarlet & Violet promo instead of erroring. Fixed with a pure regression test sweeping both data files for the retired ids, wired into CI. |
| 2026-08-10 | Claude Opus 5 (1M context) | @cheyras | Made the full catalog (23,546 cards, every set, price, and the Pokedex) browsable without an account -- previously invisible behind the signup wall for no reason. The real work was closing the leakage every catalog route had quietly grown (per-user completion rollups, ownership flags, capture state): added a second identity accessor that resolves to null rather than undefined for anonymous requests, and found while doing so that the connection pool connects as postgres, which owns every table and therefore bypasses row-level security entirely -- anonymous requests now run under SET LOCAL role = 'anon' so RLS actually fires. Also fixed a stray unauthenticated GET /api/avatar firing from the mobile drawer before session state settled. |
| 2026-08-10/11 | Claude Opus 5 (1M context) | @cheyras | Issue #29: built a real OAuth 2.1 + PKCE + dynamic-client-registration authorization server for /mcp (oauthServer.ts, routes/oauth.ts, Authorize.tsx, migrations 031-033) so any MCP-spec client connects with one click instead of copy-pasting a token -- the token endpoint just calls the existing createToken(), so it's a bridge onto the pre-existing credential, not a parallel one. An independent Opus review before shipping caught a real bug (session routes reading the RLS-scoped client, which migration 033's zero-policy lockdown would have blocked in production despite passing every local test) and a real open redirect (/\evil.com bypassing a //-only check); both fixed. Verified end-to-end against production with the QA account. A follow-up deploy then hit a stale Vercel build cache that 500'd every signed-in user (unrelated code, --force fixed it); root-caused and reverified within the hour. Synced DEPLOYMENT.md, SPEC.md, SECURITY.md, ARCHITECTURE.md, README.md, and this wiki (MCP-Setup, Decision-Log) to match; strengthened AGENTS.md's doc-sync checklist into an explicit table since the wiki had gone stale before this pass caught it. |
| 2026-08-11 | Claude Sonnet 5 | @cheyras | Issue #25: Profile and the header chip hardcoded the literal "Trainer" instead of the signed-in user's name. Stopped mid-fix to report that the obvious approach (reading session.user.user_metadata.username) was actually broken -- verified against the live DB that only the QA account (admin-created) has that metadata set; the 3 real signups have it empty, since Auth.tsx's signUp() never writes it. Per direction, added GET /me in apps/api (matching the codebase's cloud/self-host abstraction-layer convention over a first-of-its-kind direct supabase.from() call) reading app_user.username, the value the signup trigger's COALESCE fallback always populates. Verified in a real signed-in browser session (QA account, both 390px and 1440px) after working around two local-only obstacles: no Vite dev proxy for /api (cloud path), and vercel dev's local function runtime failing on an opaque DB-permission error unrelated to the fix. Also surfaced, but left unfixed as out of scope: a pre-existing Promise.all race in /insights/overview (two queries sharing one RLS-scoped PoolClient) that wedges the connection pool under a persistent single-process local run. |
| 2026-08-11 | Claude Sonnet 5 | @cheyras | Issue #26: "/insights range chips don't change anything" turned out to be true and honest -- the one real account with any history has only 10 days of it (07-30 .. 08-08), so 30d/3m/6m/1y all resolve to the identical rows. Rejected both easy-but-wrong fixes (hiding the feature behind a flag, or inventing back-dated points) in favor of a small pure rangeCoverageCaption() helper plus one new line under the chart: "Showing all N days of recorded history (started )" whenever the real span is shorter than the selected window. Added apps/web's first-ever unit test (node --import tsx --test, mirroring apps/api's convention) covering the exact reported scenario. Verified live against the QA account: 0-point and 1-point cold starts unchanged (regression-clean), then seeded 5 rows for the QA user only to reproduce the actual bug -- 30 Days/3 Months/1 Year rendered pixel-identical charts, each now carrying the caption -- deleted afterward, row count confirmed back to 0. Independently re-hit and corroborated the Promise.all//insights/overview connection-pool race from the #25 entry above (a real page load fires ~6 concurrent authenticated requests against a 2-connection pool); also confirmed there is currently no live automated mechanism producing collection_value_point rows for any cloud account at all (no Vercel Cron, no GitHub Action, the only cron needs a persistent process nothing here runs) -- both logged as separate, out-of-scope findings, not fixed here. |
| 2026-08-11 | Claude Sonnet 5 | @cheyras | Issue #27: removed the never-shipped Stream Tools feature (OBS overlay pop-up) end to end -- the nav's disabled "Soon" entry, the stream icon, routes/Overlay.tsx, its route registration, its /overlay chromeless-path entry, and the now-orphaned collectionEvents API wrapper (confirmed sole caller before removing), plus one stale comment beyond the traced list. Deliberately left GET /collection/events and its tests alone (general attribution log, not stream-specific -- confirmed via its own doc comment and ?source=rotom-mcp test coverage) and left the two research/*.md mentions alone (competitor research notes on pkmn.gg's own Stream Tools, not a DeckScout spec). Verified live against the QA account at 1440px + 390px on /series: no Stream Tools row at all; /overlay now falls through to the router's default "Not Found." Re-hit (and left unfixed, same as #25/#26) the known Promise.all//insights/overview pool-wedging bug while standing up the local verification harness. |
Last updated by Claude Sonnet 5 on behalf of @cheyras -- 2026-08-11