CODECfeatureinfrastructure
Enshrouded drop sources — farming locations in Field Manifest, feedback fixes
Features
- Drop sources enrichment live —
scripts/enrich-enshrouded-drops.ts pulls the wiki Drops
Cargo table (286 rows), matches to ref_items by slug, writes drops: [{ source, qty, type }]
into properties; 91 items enriched, idempotent on re-run
- Field Manifest source pills — raw materials in the gather list now show which enemies or nodes
drop them (up to 4 shroud-styled pills with +N overflow); players can farm without leaving the page
- Item detail "FOUND ON" section — raw material detail pages surface drop locations with a
"MORE ON WIKI ↗" link; falls back to the Google search button when no drop data exists
- Ember/shroud glyph color fix in the codec how-to-use guide — ◈ and ◇ now render in their
correct design-system colors; required named React components (
Ember, Shroud, EmberSpan,
ShroudSpan) as a workaround for next-mdx-remote/rsc style stripping
Infrastructure
- Bearer token auth path added to
/api/wiki/feedback — codec server-to-server calls authenticate
via CODEC_API_KEY; game_slug column added to public.wiki_feedback (additive, nullable —
existing wiki inserts unaffected)
Bug Fixes
- Enrichment upsert rejected with NOT NULL violation on
category — ON CONFLICT DO UPDATE still
validates all NOT NULL constraints before routing to UPDATE even when the row exists; fixed by
switching to individual .update().eq("id") calls
Lessons
next-mdx-remote/rsc silently strips style={{ }} props from inline MDX JSX — DOM inspection
confirms style attr: null at runtime; define named components in the page file with styles in
TypeScript and reference by name in the MDX
- Postgres
ON CONFLICT DO UPDATE validates NOT NULL constraints on the INSERT row before checking
for a conflict — partial upserts that omit required columns fail even when the row already exists
- MediaWiki Cargo's
Drops table has 40 slugs that don't match ref_items — name discrepancies
between the Ingredients table (ETL source) and Drops; 91/131 is still solid coverage for v1
TODO
- Quest search — open feedback submission: "be able to search for quests — to see where gives the
quest and what the quest chain is"; no quest data in DB yet, needs a wiki data source
- Field Manifest full test — carry-forward from launch: mark recipes wanted across two accounts,
verify BOM totals, assign materials, confirm realtime sync
CODECSHIPPEDfeaturelaunch
Enshrouded Emberline pillar — shipped (v2.0)
Features
- Enshrouded pillar shipped (v2.0) — full recipe and crafting tracker for modded co-op sessions, sourced from the community wiki and live in production
- 1,964 items and 1,785 recipes loaded via MediaWiki Cargo API (
action=cargoquery); 5,227 ingredient edges stored in a normalized junction table for recursive traversal
- Campaign system live — create a world by name, share a 6-character invite code; all unlock and wanted state is scoped per campaign and shared across members in real time
- BOM engine (
explode()) recursively expands any recipe to raw materials, cycle-guarded via Set, depth-capped at 12 — crafted intermediates collapse automatically into the final aggregate
- Field Manifest (▤ MANIFEST) turns all wanted recipes into a raw-materials gather list with per-player assignment and live sync via Supabase Realtime
- Artisan unlock info curated for 8 NPCs (rescue location, quest name); unknown unlock paths get a Google search button and a saveable community note — honest where the data runs out
- Wiki feedback form shipped to the how-to guide — bug/suggestion toggle, screenshot upload to Supabase Storage, live RECENT submissions list
Bug Fixes
join_campaign() returned "Invalid invite code" for all callers — root cause: codec schema tables don't auto-grant INSERT to service_role; fixed by converting to a SECURITY DEFINER function owned by postgres
RETURNS TABLE(campaign_id uuid, ...) caused "column reference is ambiguous" inside the function body — output variable shadowed the same-named column in INSERT ... ON CONFLICT; fixed by switching to RETURNS void (required DROP FUNCTION + recreate, return type can't change in place)
- ETL recipe FK violations — ingredient edges referenced recipe IDs for items dropped by PostgREST's silent 1000-row cap on the item read-back; fixed with
.range() pagination
- Wiki tables across 18 files rendered as raw pipe text —
remark-gfm was imported but not in package.json, breaking the build and silently breaking table rendering everywhere; missing dep committed immediately
Lessons
- Supabase
service_role bypasses RLS but still needs explicit grants on non-public schemas — service_role is not omnipotent the moment you use a custom schema
RETURNS TABLE(col_name type) creates output variables that shadow same-named columns inside the function body — use RETURNS void when the caller needs no return value
- PostgREST silently truncates
.select() results at 1000 rows with no error — any ETL read-back or full-table scan needs .range() pagination from the start
- Filing the first bug through the new feedback form immediately surfaced a real issue — remark-gfm not wired to
MDXRemote made every table in the wiki render as pipe text; the tool catching its own gap on first use is a good sign
- MediaWiki Cargo API (
action=cargoquery) is a clean structured data source for any wiki on the platform — structured JSON, no scraping, covers the full recipe corpus
TODO
- Rotate the Supabase service-role key — a live key was pasted in chat during this session; rotate in Supabase dashboard and update
.env.local + Vercel
- Field Manifest full test — mark recipes wanted across two accounts, verify BOM aggregation, assign materials, tick gathered, confirm realtime sync propagates
CODECfeatureinfrastructure
Layer 2 Steps 1+2 — Riot account linking and match sync live
Features
- Riot account linking live — /settings page accepts Riot ID (Name#TAG) + region, looks up PUUID via Account API, stores in
user_profiles
- Match sync operational —
syncRecentMatches pulls last 20 TFT games, parses participant data (units, traits, augments, placement, duration), upserts into games + game_units
- SyncButton component shows live feedback — new games synced count, already-stored count, error display
- Gear icon in comp library header navigates to settings — visible on all screens, minimal footprint
Infrastructure
user_profiles schema extended with riot_puuid, riot_game_name, riot_tag_line columns
lib/riot/client.ts — Riot API utility: PUUID lookup, match history, match detail, region → routing value mapping, queue ID → mode string
RIOT_API_KEY added to Vercel env via file redirect (pipe fails on this CLI version)
Lessons
- Riot dev key expires every 24h by design — personal project application required for persistent access; submitted to developer portal, approval pending
- Vercel CLI
env add requires file redirect (< /tmp/file), not pipe — pipe produces "Invalid number of arguments" on current CLI version
- Riot Account API uses routing values (
americas, europe, asia, sea) for PUUID lookup — distinct from platform values (NA1, EUW1); using the wrong one returns 404
- TFT team planner codes changed format between Set 13 and Set 17 — old community spec (starts with
01, 1 byte per champion) doesn't match current codes; text paste via Claude API is more reliable than binary format decoding
TODO
- Personal Riot API key approval pending — replace dev key in
.env.local and Vercel when received
- Layer 2 Step 3: fuzzy comp association — unit overlap + trait scoring →
similarity_score + matched_comp_id on games
CODECfeaturelaunchinfrastructure
Layer 1 shipped — comp library, coaching view, and live deployment
Features
- Comp Library (Step 4) live — filter bar (playstyle, rating, emblem trait, archived toggle), CompCard with channel numbers, tags, difficulty bars, star rating; 3-step AddComp wizard
- Comp Detail + Coaching View (Step 5) live — unit roster grouped by FRONT / MID / BACK rows, CORE vs FLEX badge, per-unit items and coaching notes
- INTEL BRIEF accordion — one panel per note type (general → adaptation), all panels open by default when content exists;
adaptation panel uses amber accent to flag game-critical notes
- Inline note editing — pencil icon per panel, textarea replaces content on click, optimistic update reverts on server error
- Client-side filtering —
useMemo over full comp set; show_archived: true on initial fetch so archive toggle works without refetch
- App deployed at
codec.understorylabs.co — Google OAuth working, all routes protected, user data isolated via Supabase RLS
Bug Fixes
- Next.js 16 proxy convention: initial deploy used
middleware.ts with export function middleware() — produced deprecation warning in Vercel build; fixed by creating proxy.ts with export async function proxy() and deleting middleware.ts
- React 18 click-through on modal open: backdrop click handler and open trigger processed in same synchronous flush — modal opened and immediately closed; fixed with
setTimeout(() => setIsModalOpen(true), 0)
- OAuth redirect to
localhost:5173 on live deployment: caused by Supabase shared project having only localhost in Redirect URLs; fixed by adding https://codec.understorylabs.co/auth/callback to Redirect URLs (Site URL unchanged)
Infrastructure
- GitHub repo created at
nrisacher-lang/codec — private; git remote set-url required after initial remote conflict
- Vercel project configured — three env vars set, auto-deploy on push to main
codec.understorylabs.co CNAME routes through Cloudflare (DNS-only) to Vercel
Lessons
- Next.js 16 prints a clear deprecation warning when
middleware.ts is present — the message says "use proxy instead," which is exact; reading Vercel build output catches this immediately
- Supabase shared projects (multiple apps on one instance): Redirect URLs is additive — add the new app's callback URL without touching Site URL; changing Site URL re-routes all apps sharing that project
- Tailwind v4 CSS variable colors require inline
style props for opacity variants — bg-my-color/80 silently outputs full opaque color when the token is a CSS variable; color-mix() is the fix
- Optimistic UI with server actions: update local state and clear edit mode before awaiting the action, revert in catch — this keeps the UI snappy while preserving correctness on failure