← HOME

PROJECT

Codec

ACTIVE

FEATURES

SHIPPED

  • Enshrouded — recipe/crafting tracker
    JUL 2026

CHANGELOG

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 categoryON 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