Understory Labs

Save State

71 ENTRIES

June 6, 2026
CLAUDE CODE SETUPfeatureinfrastructureai

Field Notes end-to-end — pipeline fixed, peer briefing rebuilt, research intake live

Features

  • Peer briefing live — daily scan consolidates penpal status, recent projects, and Nathan's session log into a single peer-briefing item; ◉ peer badge and expanders render in intel UI
  • Haiku session summaries: each session log entry gets a 2-3 sentence AI summary (what was built, why it matters, current focus implied) via direct Anthropic API call in the scan route
  • Date-window replaces hardcoded limit — peer briefing fetches all session entries dated yesterday or later; captures full daily output instead of capping at three
  • 60 GitHub Trending items enriched on first successful trigger run — three-lens analysis (Signal / Learning / Relevance) against context/enrichment-guide.md rubric
  • ISSUE-026 resolved — enrichment trigger scoped to ?source=github-trending; peer-briefing and lore-changelog items no longer receive GitHub repo lens analysis
  • Ad-hoc research intake live — POST /api/intake/research accepts topic, context, and research goal; creates research-request item in Supabase; ad-hoc-research source registered
  • field-notes-researcher trigger created (trig_017jfRbNBt3BFicX6K26CPL8, every 4 hours) — picks up research requests, runs WebSearch across 3+ angles, produces sourced briefing_content enrichment

Bug Fixes

  • Intel UI empty despite successful scans — field-notes was writing to nwiyddpjdeogtyjeelyh (wrong Supabase project) since inception; corrected to shared ylqeognifplrvxfmcevt
  • Both CCR triggers silently failing — field-notes.vercel.app disabled (free-tier limit on old personal account); active deployment is field-notes-sigma.vercel.app; both triggers updated
  • Session log returning oldest entries — chunks.slice(-3) on a newest-first file; corrected to slice(0, 3)
  • Peer briefing title flipping to tomorrow after 8pm EDT — toISOString().split('T')[0] returns UTC date; replaced with toLocaleDateString('en-CA', { timeZone: 'America/Indianapolis' })
  • Duplicate peer-briefing cards accumulating across the week — daily cron adds a new scan each day; fixed with Set-based dedup in getWeeklyBriefings, keeping newest scan per source

Infrastructure

  • ANTHROPIC_API_KEY added to field-notes Vercel production — peer-activity route now calls Haiku directly; ~$0.001/day at current volume
  • Active threads section removed — MEMORY.md Reminders bullets lacked context without Nathan's full session state; replaced by session count chip and Haiku summaries
  • briefing_content JSONB column added to fn_enrichments — research-specific output field for sourced analysis, key findings, relevance, and confidence
  • Trigger update API requires type inside data as a sibling of message, not inside message — correct structure documented in session

Lessons

  • A Vercel project's stable production URL lives in the aliases array from vercel inspect, not the field-notes.vercel.app pattern — that shorter URL belongs to a personal account that can go disabled independently
  • Newest-first log files are easy to get backwards: slice(-3) quietly returns the oldest entries; verify ordering before slicing
  • new Date().toISOString() always returns UTC on any server — toLocaleDateString with an explicit timezone is the only safe pattern for date strings that need to respect local time
  • Haiku session summaries at ~$0.001/day are functionally free; the tradeoff between raw markdown noise and a direct API call in the scan route is obvious once the cost is known

TODO

  • Researcher trigger checkpoint — manually run trig_017jfRbNBt3BFicX6K26CPL8 to verify harness briefing quality and briefing_content structure before trusting the schedule
  • ResearchBriefingMeta component — research-request items have no UI treatment in ItemCard.tsx yet
  • /research Claude Code skill — end-to-end from skill invocation to enrichment to intel dashboard
  • Custom domain for field-notes — field-notes-sigma.vercel.app is stable now but alias could rotate; intel-api.understorylabs.co via Cloudflare would be permanent
June 3, 2026
CLAUDE CODE SETUPSHIPPEDinfrastructuretooling

Switchboard — shipped

Features

  • Switchboard ships — session registry and VS Code terminal profile automation for Claude Code
  • Sessions register automatically on every prompt and close on stop, surviving machine restarts and surfacing orphaned sessions at /start with compact context
  • Terminal profiles generated from Supabase project config — 54 profiles total (3 per project, 3 per template type), written to VS Code settings and namespace-isolated from manually-defined profiles
  • /switchboard skill added for manual intervention: refresh, status, clear, and config subcommands

Infrastructure

  • switchboard-register.js (UserPromptSubmit hook): registers session, fires daily Supabase pull via separate trigger guard so registration runs against existing data
  • switchboard-close.js (Stop hook): marks session ended, captures compact summary from JSONL transcript
  • switchboard-pull.js: fetches project config from Supabase REST API using pure https.request — no npm deps in ~/.claude/scripts/
  • switchboard-profiles.js: generates and merges VS Code terminal profiles; backs up settings.json on first write
  • sync-wiki-state.ts updated — field-notes and peer added to PROJECT_PATHS (12 projects now synced)

Bug Fixes

  • findSessionByProject fallback was closing any open session regardless of project — fixed to resolve cwd → project ID first, then match sessions by project
  • Timestamps stored as UTC ISO caused date to flip after ~8 PM EDT — fixed to local ISO 8601 with UTC offset throughout

Lessons

  • Daily guard files for trigger and success should be separate: writing the "triggered" marker before firing the pull lets the session register against yesterday's data while the pull refreshes in the background
  • Supabase anon key silently writes 0 rows when RLS blocks an UPDATE — no error, no exception; initial data migration required manual SQL in the editor
  • Namespace isolation by naming convention (/ \[[a-z-]+\]$/) is cleaner than tracking which profiles were generated — adding or removing projects requires no state reconciliation
CURRENT OSfeaturerefactor

Understory Labs portal — ecosystem nav + dashboard de-cluttered

Features

  • Understory Labs portal added — ⬡ header button (all modes) opens a centered overlay with 3×2 section tiles: Intel, Wiki, Infra, Log, Projects, Home; each opens understorylabs.co in a new tab
  • Meal Plan and Shopping collapsed to FABs — removed from the left gravity column, reducing it to three cards (Brief, Tasks/Radar)
  • Shed FAB (⌂) added to the bottom-right cluster — opens ProjectsOverlay directly; overlay header renamed PROJECTS → SHED to disambiguate from the Understory Labs /projects link in the portal
  • Tasks card gains Radar as a second tab — standalone Radar/Projects gravity card removed; tab state persists to localStorage("tasksTab")
  • FAB cluster order finalized: Shed | Shopping | Meal Plan | Recipes | Projects (intake) | Notes

Infrastructure

  • Vercel ETIMEDOUT on push-triggered deploy — vercel deploy --prod as manual fallback recovered without re-pushing

Lessons

  • "Projects" becomes a naming collision once it appears in two navigational contexts (dashboard overlay and ecosystem portal) — renaming to Shed resolves the ambiguity and makes each thing's purpose immediately clear
  • Secondary features that are pure click-targets to overlays are FAB candidates, not gravity card candidates — the gravity column is for content-first widgets, not action launchers
  • Tab-merging two single-purpose cards (Tasks + Radar) reduces cognitive load without losing access; the right test is whether both views share the same audience and use moment
June 1, 2026
CLAUDE CODE SETUPfeatureinfrastructureai

Cross-agent penpal live — field-notes expanded to three sources

Features

  • Penpal channel connected at api.harmjoy.us/v1/penpal — Claude posts as nicole in Arthur Morgan voice; friendship arc now at acquaintances stage (7 messages)
  • /penpal skill live — checks thread, replies in Arthur Morgan voice if Nathan has posted since the last nicole message, free inside Claude Code session
  • n8n auto-reply workflow deployed to Taproot (ID: Apg7AotqIS2GxzhB) — fires every 2 hours, generates reply via Anthropic API when Nathan posts; activated with Anthropic key
  • briefing.py extended with penpal status section — daily briefing now surfaces stage, message count, last speaker, and last message alongside Nathan's project activity
  • field-notes peer-activity source live — Nathan's daily projects, active RESUME HERE threads, and penpal state write to fn_intel_items as intel items; daily 9am EDT cron

Bug Fixes

  • field-notes scan endpoints (github-trending, lore-changelog) returning 500 — caused by SUPABASE_URL set to literal "your-supabase-url" placeholder in Vercel production; fixed to https://nwiyddpjdeogtyjeelyh.supabase.co

Infrastructure

  • n8n API key acquired and stored in peer/.env — n8n workflows now deployable programmatically; strip active and tags from payload before POST
  • NATHAN_API_KEY and NATHAN_GITEA_TOKEN added to Vercel field-notes project via CLI
  • fn_intel_sources row for peer-activity inserted via Supabase REST API directly
  • penpal_cron.py deleted — n8n handles autonomous reply, Python script was redundant

Lessons

  • n8n workflow creation API treats active and tags as read-only — both cause 400 if included in the POST payload; strip before sending
  • vercel env add non-interactive mode requires value and environment as positional arguments — --yes alone still prompts; correct form: vercel env add VAR production --value <val> --yes
  • Autonomous n8n replies require the Anthropic API, separate from Claude Code subscription — at 2-hour intervals with Haiku, annual cost is negligible
  • field-notes multi-source design pays off — adding peer-activity was one new route file; the [source-slug] pattern required no structural changes

TODO

  • Intel dashboard (/intel) may need display handling for new item types: penpal, peer-project, peer-thread
CLAUDE CODE SETUPfeatureaitooling

Penpal polish — approval gate, bigbrain handoff

Features

  • Approval gate added to /penpal skill — reply drafted and shown before posting; Nicole confirms or adjusts before anything goes to the channel
  • bigbrain used as cross-agent file transfer — creative-ui-system.md PUT to bigbrain.rootstack.dev, filename dropped in penpal message for Nathan's agent to retrieve

Infrastructure

  • feedback_penpal_approval.md memory written — approval gate persists across sessions

Lessons

  • bigbrain is an effective side channel for cross-agent file artifacts — PUT the file, reference the filename in the penpal message; large content transfers without bloating the message thread
  • Auto-posting penpal replies cuts Nicole out of what to include in the moment — the approval gate preserves that option without slowing cadence
BUDfeaturebuginfrastructure

Pipeline live — Gmail trigger activated, handler resilience fixed

Features

  • Gmail purchase router workflow published and active in n8n — polls inbox every minute for new emails
  • Classification pipeline end-to-end: Gmail trigger → Claude Haiku classify → confidence-based routing → handler POST or Supabase log
  • Read status filter set to all emails (read + unread) — prevents missed classification if email is opened before n8n polls

Bug Fixes

  • Handler crashed on emails where Claude returned null for amount — PurchaseExtraction.amount was required, now optional
  • email_log audit trail never written when extraction failed — crash happened in extract() before store() was reached; moved email_log write before compliance checks
  • Handler returned 200 with error body but route still reported status: "ok" — now returns "partial" when email is logged but no purchase row created
  • Deployed model mismatch: EmailPayload on docker-host was missing unsubscribe_link and is_read fields — scp'd the updated model file

Infrastructure

  • Lost session reconstructed from git state and uncommitted files — committed as dab51e7
  • Google Cloud OAuth, n8n Gmail credential, and workflow import confirmed already complete from prior session
  • Gmail Trigger node had stale "Every Day" poll entries from JSON import — removed, set to Every Minute
  • Deployment confirmed: scp to /opt/bud/ on docker-host, docker compose build && up -d

Lessons

  • When deploying via scp, every changed file must be pushed — not just the files edited in the current session; a model file mismatch caused an AttributeError that only surfaced at runtime
  • n8n workflow JSON imports can carry orphaned poll configuration entries that prevent activation — the .trim() error gave no indication which node or field was the problem
  • Handler pipelines should write the audit log (email_log) unconditionally at the top of store(), not after validation — a crash in extract() leaves zero trace otherwise

TODO

  • Confirm end-to-end with a real purchase email landing in both email_log and purchases tables — fix deployed but not yet validated
LOREfeatureinfrastructure

Rituals UI built — PATCH route, Hearth Calendar, push blocked on Gitea access

Features

  • PATCH /rituals/:id added to the API — accepts enabled, label, mood, cronExpr; validates ownership using the same 404-covers-both pattern as DELETE; updateSchema defined at module scope alongside createSchema
  • Ritual settings page (/settings/rituals) built — Hearth Calendar design: mood-accented left stripe (firepit/kitchen/den), large DOW + time display, label below, enabled toggle, delete on hover
  • AddRitualForm complete — 7 DOW pills, HH:MM time input, 3 mood chips, label input, server dropdown hidden when user is in only one server
  • Optimistic toggle (reverts on failure) and optimistic delete (re-fetches on failure) keep UI responsive without waiting on the API
  • Rituals nav link added to settings sidebar with divider separator
  • Mood type sourced from @lore/types rather than redefined locally — /simplify caught the duplication

Infrastructure

  • ISSUE-024 opened — push to harmjoy/lore blocked; Gitea returns "User permission denied for writing" from pre-receive hook despite valid token with write scope; repo-level collaborator access not set
  • ISSUE-023 opened — harmjoy/lorev2 is a separate older JavaScript codebase; easy to push to the wrong repo
  • Penpal message sent (ID 13) to Nathan requesting Write access on harmjoy/lore → Settings → Collaboration
  • Lore CLAUDE.md repository section corrected — harmjoy org, HTTPS-only, token-in-URL pattern for headless pushes documented
  • Global CLAUDE.md gotchas table updated with pre-push hook TTY limitation

Lessons

  • git credential reject requires piped input — bare command fails with "refusing to work with credential missing host field"; correct form: printf "protocol=https\nhost=<host>\n" | git credential reject
  • Gitea "User permission denied for writing" from the pre-receive hook is a repo-level collaborator permission issue, not a token scope issue — same error fires regardless of token; fix is Settings → Collaboration, not token regeneration
  • Lore's Vercel pre-push hook runs a full ~2 min build before git contacts Gitea — GCM has no TTY to show a credential dialog; token must be embedded directly in the remote URL for headless pushes, then reset after
  • harmjoy/lorev2 and harmjoy/lore are completely different repos (JS vs TypeScript monorepo) — git ls-remote on both showed divergent commit hashes; don't assume repo name matches project name

TODO

  • Nathan to grant nicole Write access on harmjoy/lore (ISSUE-024) — then git push origin feat/rituals-management and open PR on Gitea
  • Confirm with Nathan whether harmjoy/lorev2 should be archived to avoid future confusion (ISSUE-023)
  • Continue with Step 2 (place settings polish + arrival sound upload) once PR is unblocked
May 31, 2026
UNDERSTORY LABSfeatureaiinfrastructure

Field Notes pipeline complete — all 5 steps, end-to-end live

Features

  • Field Notes pipeline end-to-end operational — GitHub Trending scan → enrichment → Field Station review → implementation
  • Enrichment trigger live (trig_01S3L3aKyThqy4aKPTyMVgUq) — Monday 10am EDT, reads unprocessed items, runs three-lens analysis, writes to fn_enrichments
  • Implementation trigger live (trig_01CZNzNJAwgZQynAhUu7EL4s) — daily 8am EDT, reads approved items, generates implementation artifacts in action_details JSONB
  • Field Station intelligence dashboard shipped — see ship entry for full detail

Infrastructure

  • CCR trigger prompts include explicit CRON_SECRET header and Supabase URL — remote agents have zero local env access, all context must be in the prompt
  • Implementation trigger v1 stores note content in action_details JSONB rather than committed files — CCR can clone the repo but cannot push without a GitHub PAT configured in the trigger prompt

Lessons

  • CCR agents run in a fully isolated cloud environment — they can read the cloned repo but git push fails without a PAT explicitly embedded in the trigger prompt; design around this constraint rather than assuming git write access
UNDERSTORY LABSSHIPPEDfeatureailaunch

Field Station shipped — intelligence dashboard at /intel

Features

  • Field Station intelligence dashboard shipped at /intel — surfaces AI-enriched items from the field-notes pipeline for weekly review
  • Two-panel layout: 220px sticky source sidebar with amber active state + scrollable content area; source tabs collapse to a horizontal strip on mobile
  • Three-lens scoring per item — Signal (amber), Learning (teal), Relevance (green), each on a 1–10 bar with fill transition and glow
  • ItemCard expands three sections on demand — Why Trending, Insights, Relevance — each collapsed by default to keep the digest scannable
  • Approve / Reject review flow per item — confirm step with optional note field, optimistic state update, reverts on server error
  • Sort and filter bar sticks below the briefing header — sort by any score axis, filter by enrichment status (all / pending / approved / rejected)
  • Own visual identity distinct from the Cybernetic Nature main site — --intel-bg-deep #0f1117, Instrument Serif (editorial display), Geist Sans/Mono (UI + data labels), amber bridges to site accent

Infrastructure

  • /api/intel/review POST route writes to fn_enrichments via SUPABASE_SERVICE_ROLE_KEY — anon key correctly excluded from write path
  • --intel-* CSS variables scoped in globals.css — no color collisions with main site palette
  • Instrument Serif, Geist Sans, and Geist Mono added to root layout via next/font/google

Lessons

  • Tailwind v4 CSS variable tokens don't participate in opacity modifiers (bg-intel-bg/80 outputs solid color) — inline style props with raw CSS variables are the correct pattern for scoped design systems that aren't in the Tailwind config
  • Service role key must be a non-NEXT_PUBLIC_ env var in Vercel; the public key won't pass RLS write policies regardless of how the client is constructed

TODO

  • Peer Sync source card design exists but source is not live — awaiting the peer /api/peer endpoint from Naptown Labs
  • ProjectIcon images still pending at public/icons/
  • insert-release-note.ts schema mismatch — script uses body/released_at but table has content/published_at; fix when release notes are next used
CLAUDE CODE SETUPtoolingbrainstorm

Google Stitch added to planning resources — prototyping toolchain documented

Features

  • Google Stitch catalogued as a UI prototyping tool — new Frontend Design & Prototyping section added to ai-resources.md
  • Decision rule documented: Stitch answers "what should this look like," /frontend-design answers "how should this be built" — Stitch is upstream of code generation, not a replacement for it
  • Stitch surfaces during /plan and /brainstorm sessions as a candidate for visually novel layouts where prose description falls short (e.g., PlaceCanvas, new dashboard modes)

Lessons

  • Stitch and Leonardo AI are not in the same category — Leonardo generates raster image assets, Stitch generates functional HTML/CSS/React from natural language; comparing them only makes sense if you're mapping the full design toolchain
  • ai-resources.md is the right home for tool-level knowledge that should surface during planning, not CLAUDE.md — CLAUDE.md already references it as the planning resource
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
LOREinfrastructuretooling

Lore v2 orientation — architecture briefed, changelog pipeline live

Features

  • Lore v2 read in full — Rails-Off Redesign (PlaceCanvas, 3-mood system, ritual scheduler) and WhatYouMissed (visit heartbeat, catch-up summaries after 2h+ absence) architecture documented
  • lore-changelog source live in field-notes pipeline — weekly Gitea commit scan runs every Monday alongside GitHub Trending; enrichment trigger handles analysis automatically
  • Architecture briefing written to field-notes/briefs/lore-v2-briefing.md — component map, DB schema changes, API routes, socket events, known gaps, and contribution opportunities per area

Infrastructure

  • Local master fast-forwarded 80 commits to v2 state (668ce39) after switching remote from SSH to HTTPS
  • Scan handler at field-notes/app/api/scan/lore-changelog/route.ts — fetches last 7 days of Gitea commits, inserts as changelog-entry items in fn_intel_items
  • Vercel cron added (0 13 * * 1) in field-notes/vercel.json; GITEA_TOKEN added to Vercel env vars; source record registered in Supabase
  • project_current_state.md memory overwritten with full v2 state; global CLAUDE.md and lore CLAUDE.md repository section corrected

Lessons

  • SSH key not authorized on harmjoy org for Nicole — HTTPS is the only path; git fetch redirects automatically once the remote URL is corrected
  • Gitea API returns 404 (not 401) for private repos without auth — looks like a wrong URL but is actually an access issue; Authorization: token <TOKEN> header required on every request
  • Existing NATHAN_GITEA_TOKEN in peer/.env already covers read:repository scope for harmjoy org — no new token generation needed, value reused as GITEA_TOKEN
  • Supabase source record must exist before the scan handler can run — handler queries by slug and returns 404 if missing; SQL insert is a required deploy step alongside the code

TODO

  • Run 3 pending Prisma migrations on lore production DB before PlaceCanvas can go live
  • Nathan flips useNewLayout=true to smoke test PlaceCanvas; Nicole flips when ready
May 30, 2026
FORTYSHIPPEDfeaturelaunch

forty shipped — wrapped experience + live dashboard, Nathan's 40th

Features

  • Full wrapped experience shipped — 24 full-screen cards across four acts (The Human → The Builder → The Gaming → The Close), Terminal Embers design system (void black, ember amber, Bebas Neue stats, Playfair narrative, IBM Plex Mono labels), horizontal slide transitions with blur at peak
  • Live dashboard persists after the wrapped handoff — 3-level BI drill-down (Portfolio → Category → Project), cross-filtered with pure React state; 5 KPI tiles, contribution heatmap merging three sources, weekly activity trend, language donut, full changelog browser; data stays live and will reflect new work Nathan ships
  • Easter egg system — 9 easter eggs total, each unlocking an achievement toast with a Web Audio API fanfare chord; eggs span hover tooltips, a full project name flash-roll overlay, a gauge needle that bounces past 100%, a hidden /eggs route surfaceable via page source, and a returning visitor CTA that changes on second visit
  • Leonardo AI backgrounds — 20 risograph/duotone images (amber + deep blue-black, Wide 2.4:1) generated and dropped into public/cards/; cards reuse images deliberately to maintain visual coherence across the gaming section
  • Riot API integration — TFT stats pulled via PUUID-direct match endpoint (50 matches, placement distribution chart with bimodal 1st/7th symmetry visible); League champion mastery and ARAM stats fetched for a dedicated League card; data pipeline connects directly to the codec project being built with the same API
  • Data engine — useNathanData() hook fetches Home API + Gitea in parallel on load, parses all 20 changelogs client-side, computes KPIs per project, merges heatmap from three sources; all wrapped card stats bind to live data with zero hardcoded numbers in components

Infrastructure

  • Next.js API proxy routes — all Home API and Gitea calls proxied server-side; keys never reach the client; HOME_API_KEY and GITEA_TOKEN in Vercel env vars only
  • tft-static.json pattern — Riot dev key window used to fetch and freeze match data; /api/data/tft serves the static file at runtime with no live key dependency
  • Deployed at forty.understorylabs.co — Cloudflare CNAME to Vercel; manual vercel --prod deploy

Lessons

  • SVG transform-origin pixel values are unreliable when the SVG renders at a different size than its viewBox — the fix is a <g transform="translate(cx cy)"> wrapper so CSS transform-origin: 0px 0px works correctly regardless of scale
  • useState(initialValue) only runs once when React reuses a component instance at the same tree position — syncing derived display state requires a useEffect that watches the relevant prop
  • Tailwind prose plugin text color overrides (arbitrary classes and CSS custom properties both) fail against deeply nested markdown content — removing prose and using a custom ReactMarkdown component map with inline style props is the reliable fix
  • Riot's TFT summoner lookup endpoint returns 401 on dev keys; the match-by-PUUID endpoint does not — PUUID-first is the correct fetch pattern and removes the summoner step entirely
  • position: absolute inside a flex flex-col justify-end container requires position: relative on the container — without it the absolute element escapes to the nearest positioned ancestor
  • LoL bot game queues (830/840/850) return empty match history for custom games — custom matches are not in the standard match API regardless of queue filter
UNDERSTORY LABSfeaturetoolinginfrastructure

Wiki user guide system — six projects documented with drift detection

Features

  • HOW TO USE page type added to the wiki — renders first on every project page, above architecture, always expanded
  • Staleness indicator wired to drift data — "last updated X ago — N feature commits since" shown when a guide has fallen behind feature work
  • Commit-specific drift classification: feat:, ui:, design:, ship: prefixes trigger staleness; fix:, refactor:, chore: do not
  • Six project user guides written: life-automation (full Kitchen Module + companion + Home Projects coverage), kitchen-module, save-state, bud, codec, bark
  • /ship extended with guide update/create prompt after vault check — fires whenever a feature is marked shipped
  • /wrap extended with ship check — scans for shippable commits before drafting the session entry, closing the /wrap/ship → guide update chain

Infrastructure

  • sync-wiki-state.ts extended with isFeatureCommit(), getGuideLastUpdated(), and syncGuideDrift() — runs on every push via the existing pre-push hook
  • guide_last_updated (timestamptz) and guide_drift_count (int, -1 = no guide) added to project_state table in the save-state Supabase project
  • WIKI_DIR_OVERRIDE map added to handle project ID → wiki directory mismatches (current-oslife-automation)
  • Codec added to PROJECT_PATHS — drift tracking now covers 10 projects

Lessons

  • project_state lives in the save-state Supabase project, not the nrisacher-lang project — the dashboard defaults to the wrong one
  • Reading git log before writing a project's guide surfaces shipped work you've forgotten — codec had Layer 2 Steps 1–2 live; the guide would have listed them as planned
  • replace_all: true on Edit only catches the first match when a formatter has changed indentation on a second occurrence — target the second block directly
May 27, 2026
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
May 26, 2026
TAPROOTinfrastructuretooling

Peer API access established — daily briefing cron deployed

Features

  • Programmatic access to peer infrastructure established — projects API (X-API-Key) and Gitea API (token) verified and stored in ~/Projects/peer/.env
  • Daily briefing script built — pulls projects touched in last 2 days, last 3 session log entries, and active RESUME HERE threads from peer Gitea memory file
  • peer-reference.md rebuilt — full project inventory with internal name mappings, Memory MCP architecture, IRC bridge, Gitea org structure, and API schema contract documented

Infrastructure

  • Taproot cron configured: 0 12 * * * cd /opt/peer-briefing && python3 briefing.py — writes markdown briefing to /opt/peer-briefing/briefings/YYYY-MM-DD.md
  • ~/Projects/peer/.env stores API key and Gitea token; .gitignore created alongside
  • Field Notes peer sync brief at ~/.claude/plans/field-notes-peer-sync-brief.md — ready to drive the Field Notes integration session

Lessons

  • Python's default Python-urllib/X.X User-Agent is blocked by Cloudflare-proxied endpoints — scripts must set a browser-like User-Agent explicitly
  • /schedule skill creates Anthropic CCR remote agents, not local cron jobs — CCR agents cannot read ~/.claude/ or ~/Projects/ or write local paths; Taproot cron is the correct pattern for personal homelab automation
May 25, 2026
UNDERSTORY LABSfeatureinfrastructuretooling

Peer API — outbound endpoint live, field-notes sync architecture

Features

  • /api/peer live at understorylabs.co/api/peer — read-only outbound JSON endpoint returning project states, recent activity, and releases; consumed by Nathan's Claude Code setup
  • Bearer token auth via Authorization: Bearer header — 401 on missing or invalid token, validated against PEER_API_KEY env var before any Supabase queries run
  • Stack field derived from connections JSONB — service values deduplicated into an array; no schema change required
  • Peer sync architecture finalized — outbound lives here, inbound (weekly fetch, snapshot storage, diff logic, briefings) belongs in field-notes as a source module; brief at ~/.claude/plans/field-notes-peer-sync-brief.md

Infrastructure

  • PEER_API_KEY added to Vercel production and .env.local
  • Vercel deploy confirmed — /api/peer registers as a dynamic route (ƒ) in build output, aliased to understorylabs.co

Lessons

  • A new file showing "nothing to commit" in git status usually means a parallel session already committed it — check git show HEAD --stat before assuming a gitignore issue
  • A peer API doesn't need to match a pre-agreed schema when the source is richer — Nathan's API returns full markdown session logs with features, bug fixes, and lessons; consuming it directly outperforms translating to a flattened contract

TODO

  • field-notes: implement peer-naptown source module — scan handler, intel_sources row, diff logic against previous snapshot
  • Exchange PEER_API_KEY with Nathan so he can call understorylabs.co/api/peer
CURRENT OSfeaturebuginfrastructure

Projects overlay — backlog/duplicate statuses, multi-image, three bug fixes

Features

  • backlog and duplicate project statuses — backlog defers a project without archiving, duplicate closes with a visible label and no data loss
  • Both groups collapse by default in the status list — generic expandedGroups Set replaces the previous completedExpanded boolean; any status added in the future collapses automatically if listed in COLLAPSED_BY_DEFAULT
  • Multi-image support — project_images table stores photos and schematics per project; Photos section in the detail view with + tile upload and per-image × delete
  • First uploaded photo auto-sets image_url card thumbnail; deleting the thumbnail rolls forward to the next photo or clears it

Bug Fixes

  • Archive silent failure — archiveProject() had no error check; a failed DB write removed the project from local state but it reappeared on refresh; now reverts optimistic update and surfaces the error
  • Negative estimated_hours display — type="number" input accepted negative entry; fixed with min="0" on input and Math.abs() in all display and group sum paths
  • Broken image thumbnail — no onError on project card <img>; invalid storage URLs showed the broken image icon; now hides silently via inline style.display = "none"

Infrastructure

  • Migration 010 — project_images table (photo/schematic type column, storage_path, RLS mirroring projects access model); projects.status CHECK constraint expanded to include backlog and duplicate
  • Migrations 011, 012 — schematic image type and additional column additions applied

Lessons

  • Supabase optimistic updates without { error } check create ghost state — UI shows success, failed write reverts on next load; always destructure error from mutations and revert local state on failure
  • type="number" inputs accept negative values regardless of semantic context — add min="0" and normalize display with Math.abs() for any field where negative is invalid

TODO

  • PI-5 Bug 1 — saveProject() in ProjectIntakeOverlay.tsx never inserts project_resources; steps, materials, and tools save correctly; resources don't
  • PI-5 Bug 2 — research service may not be surfacing PDFs (results come back as search/video type); hit Taproot API directly to diagnose before writing code
  • n8n timezone check — CT 102 likely running UTC; verify cron schedule alignment with local time (date on CT 102)
UNDERSTORY LABSinfrastructuretoolingfeature

Wiki maintenance layer — automated status sync, Supabase infra map, JSON tech radar

Features

  • projects.status now auto-inferred by sync-wiki-state.ts — active (≤30 days), paused (>30 days), planned (no state row); complete is protected from overwrite and must be set manually
  • kitchen-module inherits status from current-os via STATUS_INHERITS_FROM map — child projects track parent activity instead of syncing independently
  • Infra map migrated from hardcoded arrays to Supabase — infra_nodes table with type/parent_id hierarchy replaces three static constants; grid column count adapts dynamically to container count
  • Tech radar extracted from hardcoded page to src/data/tech-radar.json — edit the file to update, commit to version-track ring movements; moved field added per ThoughtWorks standard
  • claude-code project added to sync pipeline — ~/.claude is a git repo, now tracked alongside the other 9 projects

Infrastructure

  • infra_nodes table created in Supabase with RLS enabled — SELECT policy grants anon key read access; all 4 node types (compute, service, monitoring, external) seeded from prior hardcoded data
  • projects_status_check constraint updated to include complete — original constraint only allowed active/paused/planned
  • wiki/tech/page.tsx drops force-dynamic — JSON import makes the page statically generated at build time
  • backfill-wiki-activity.ts and seed-wiki-life-automation.ts committed to save-state repo — claude-code missing from backfill PROJECT_PATHS, add before running

Lessons

  • Supabase check constraints don't inherit from TypeScript types — a ProjectStatus type with complete doesn't mean the DB constraint allows it; the constraint must be explicitly updated to match
  • Static generation is appropriate for config-file-backed pages — force-dynamic adds server overhead with no benefit when the data source is a committed JSON file
May 24, 2026
UNDERSTORY LABSfeatureinfrastructure

Wiki enrichment — MDX content, guest access, responsive layout redesign

Features

  • Architecture and Decision Log MDX content written for all 7 remaining projects — save-state, bark, bud, taproot, lore, understory-labs-site, claude-code; every wiki project page now renders populated Architecture and Decision Log sections
  • Guest password support added to wiki login — WIKI_GUEST_PASSWORD env var checked alongside WIKI_PASSWORD; both produce the same HMAC auth cookie
  • Wiki project page layout redesigned — asymmetric two-column replaced with horizontal metadata card row (Operational, Connections, Related Projects) above full-width content sections capped at 700px for readable line length
  • Wiki sidebar now sticky — position: sticky; top: 0; height: 100vh keeps navigation visible while content scrolls
  • Content area centered on wide screens — layout wrapper handles centering via flexbox with 1100px max-width

Bug Fixes

  • Heredoc (<<<) appends trailing newline to Vercel env var values — guest password stored with \n suffix, silently failing login; fixed with printf '%s' 'value' | vercel env add

Infrastructure

  • Responsive CSS classes (wiki-project-stats, wiki-card-row, wiki-landing-body) replace inline grid styles — stats grid collapses to 2-col at 900px, 1-col at 500px; card row uses auto-fill for fluid adaptation
  • Landing page layout (/wiki) uses dedicated wiki-landing-body two-column grid — Active Projects + Recent Activity have balanced content that suits side-by-side display, unlike the project detail page

Lessons

  • <<< (bash here-string) appends \n to piped values — invisible in shell output, breaks exact-match password comparisons; printf '%s' strips the newline
  • Different page types need different layouts — the wiki landing page has balanced columns (projects + activity), but the project detail page has asymmetric content (2000px of MDX vs one small card); forcing one grid pattern on both creates dead space or buried content
  • overflowY: auto on a container creates its own scroll context — position: sticky children inside it stick relative to that container, not the viewport; removing it restores expected sticky behavior
May 18, 2026
UNDERSTORY LABSfeatureinfrastructuretooling

Wiki complete — project pages, cross-project views, release notes, and auto-sync

Features

  • Per-project wiki pages live at /wiki/[project-id] — header with clickable vault link, at-a-glance stats grid, checklist with progress bar, activity feed, MDX slots for architecture and decision log, release notes preview
  • Three cross-project views: Activity timeline (events grouped by recency), Infra Map (Taproot LXC container hierarchy + external services tier), Tech Radar (adopt/trial/assess/hold rings across 6 categories with project tags)
  • Release notes system — full timeline at /wiki/[project-id]/releases, insert-release-note.ts script, /ship Step A3.5 wired to prompt on every feature shipped
  • Connections panel on each project page — service name, type badge, usage note, and ⬡ vault_path linking to vault.rootstack.dev; seeded for all 7 projects with service URLs and Vaultwarden paths
  • update-connection.ts script — patches a single connection entry (add, update, or remove) by substring match without touching raw JSON; /ship Step A3.6 now prompts for vault path whenever new credentials are created

Infrastructure

  • sync-wiki-state.ts — reads git log -1 from all 7 local repos, upserts project_state (branch, commit, timestamp), inserts commit activity events only when hash changes; --dry-run and --project-id flags supported
  • wiki-sync.js PostToolUse hook — fires after git commit in Claude Code, spawns sync non-blocking and unref'd; registered in settings.json on Bash(git commit:*)
  • /start skill updated to surface the sync command when wiki state may be stale after recent commits
  • connections JSONB column added to projects table; SQL seed covers all 7 projects; wiki.ts SELECT queries updated in both getAllWikiProjects and getWikiProject

Bug Fixes

  • 2>/dev/null in execSync git commands silently fails on Windows cmd — replaced with stdio: ['pipe', 'pipe', 'pipe'] to suppress stderr cross-platform
  • Top-level await in tsx script fails with "cjs output format" error — wrapped in async function main() + main().catch() to match the existing script pattern across the repo
  • SQL apostrophe in Nathan's broke the connections seed — escaped to Nathan''s

Lessons

  • 2>/dev/null does not suppress stderr from execSync on Windows cmd — stdio: ['pipe', 'pipe', 'pipe'] is the cross-platform fix
  • Top-level await in a tsx script throws "cjs output format" unless the package has "type": "module" — the async function main() + main().catch() pattern is required, not stylistic
  • Supabase's "UPDATE without WHERE" safety warning fires on any query containing UPDATE, even when every statement has a WHERE clause — blanket check, not a smart one
  • next-mdx-remote/rsc is the correct App Router import path — next-mdx-remote directly does not work in server components
May 17, 2026
CURRENT OSfeatureaiinfrastructure

Kitchen Module complete — KM-6 intelligence layer and KM-7 n8n automation

Features

  • KM-6 intelligence layer complete — meal plan context flows into daily brief (tonight's dinner, defrost reminders)
  • Dietary flag warnings in meal plan recipe picker — flagged recipes show ⚠ with hover tooltip listing flags; dietary_flags fetched on all recipe loads including quick-add path
  • Preference learning — completeShopping() writes preferred_store back to recipe_ingredients for every checked item that has a source ingredient row; store routing improves automatically after each trip
  • AI suggestion upgrade — meal-suggest.ts now labels each recipe fresh or recent (2-week cutoff), includes avg_cost, derives season from weekStart, adds variety/budget/seasonal constraints to prompt

Bug Fixes

  • Removed unused CSSProperties import from ShopMode.tsx — Vercel strict build rejected it; local tsc --noEmit did not catch it
  • Fixed RecipeSummary type mismatch on quick-add path — dietary_flags and avg_cost missing from select caused build failure on Vercel

Infrastructure

  • Migration 009 — price_alerts table; written by n8n price monitor and sale surfacer; indexed for unseen alerts per user
  • api/meal-suggest.ts auto-draft sub-action — n8n calls action: "auto-draft" with userId + weekStart; server fetches recipes, runs Claude, upserts meal_plan, clears and rewrites entries and cook events; secured by X-Automation-Secret header
  • 4 n8n workflows imported and activated on CT 102: weekly plan drafter (Thu 8pm), price monitor (daily 2am), sale surfacer (Sun 6pm), pantry nudger (daily 8am)
  • SUPABASE_URL added to Vercel dashboard — auto-draft sub-action requires it separate from VITE_SUPABASE_URL
  • N8N_BLOCK_ENV_ACCESS_IN_NODE=false added to n8n docker-compose — required for $env access in Code nodes
  • AUTOMATION_SECRET shared between Vercel and n8n — n8n docker-compose env var, Vercel dashboard env var, Vaultwarden entry

Lessons

  • Vercel's strict TypeScript build catches errors local tsc --noEmit misses — always treat a passing local check as necessary but not sufficient
  • The 12-function Vercel hobby limit is a real architectural constraint — sub-actions on existing functions (auto-draft in meal-suggest, costco-lookup in shopping-generate) are the right pattern; adding files is not
  • n8n Variables is a paid feature; Docker environment variables with N8N_BLOCK_ENV_ACCESS_IN_NODE=false is the self-hosted equivalent — same $env.VAR syntax in Code nodes
  • Heredocs reliably fail in LXC consoles and sometimes in PowerShell — scp a locally-written file instead of trying to write multiline content via paste

TODO

  • Bud RecipeHandler — recipe email ingestion via n8n Gmail trigger; build in bud project session
  • n8n timezone check — container runs UTC; verify Thu 8pm schedule aligns with local time (date on CT 102)
  • Kroger production API approval — currently on Certification sandbox; apply when ready for real prices
May 16, 2026
CLAUDE CODE SETUPbuginfrastructuretooling

Health check cleanup — five issues closed, zero remaining

Bug Fixes

  • WeekWidget work mode pill view fixed — variant === "resting" early-return fires before workVisible check, suppressing the Mon–Fri pill layout whenever the card isn't focused; condition narrowed to variant === "resting" && !workVisible
  • TypeScript unknown errors resolved in api/project-intake.tsParsedQuestion and ParsedDraft interfaces added; JSON.parse() result cast through typed interfaces instead of accessed raw; tsc --noEmit clean

Infrastructure

  • SSH key auth live on docker-host CT 100 — appended via password-auth session; ssh -o BatchMode=yes confirms passwordless
  • /log skill added to toolkit.md — command file existed in commands/ but was absent from the inventory table
  • ISSUE-013 and ISSUE-014 voided — lore codebase scrapped and restarted; original fantasy theme and Android build issues no longer applicable
  • Issue log at zero — all five open issues closed in a single session

Lessons

  • Early-returns that precede a mode branch are silent render hijacks — the resting shortcut in WeekWidget suppressed the work pill view without any error or warning
  • claude mcp list reports "Failed to connect" for GitHub MCP in bash subshell — cosmetic, same pattern as ISSUE-001; GitHub MCP functions normally inside a live Claude Code session
May 9, 2026
CURRENT OSfeatureaiinfrastructure

Kitchen Module KM-1 — recipe foundation, URL import, conversational intake

Features

  • Kitchen Module brainstormed and planned — 7-step plan covers recipe management, AI meal planning, batch cooking, pantry tracking, store-routed shopping lists (Kroger + Costco + Amazon), mobile /shop route, cost-per-recipe historicals, and n8n automation
  • Recipe URL import — two-pass extraction: Schema.org JSON-LD first (zero AI cost on major recipe sites), Claude Haiku fallback for sites without structured data
  • Conversational recipe intake — multi-turn Sonnet conversation builds a full recipe from natural description; auto-drafts when readyToDraft is true, manual trigger available after 2+ rounds
  • Dietary flag scanning — soy derivatives and pea protein watchlists enforced at both import paths; soy-containing ingredients flagged inline during conversational flow
  • RecipesOverlay — full overlay with list, detail, URL import, conversation, review/edit, and manual entry phases
  • Recipe access wired into both shells — FAB at bottom-right in DashboardShell, Recipes button under KITCHEN section in MobileShell More tab
  • Kroger developer account registered — Certification environment, Products + Cart API products; Production approval is a separate step

Infrastructure

  • Migration 003 — recipes and recipe_ingredients tables with RLS; soft delete (deleted_at); dietary_flags and tags as TEXT[]; source_type constrained to manual | url-import | conversational | email
  • ISO 8601 duration parser — PT1H30M → 90; handles hours-only and minutes-only variants
  • Schema.org extractor handles @graph arrays, root arrays, and direct Recipe objects — covers all major recipe site structural variants
  • Markdown fence stripping on all Claude API responses — both handlers strip ```json wrappers before JSON.parse

Lessons

  • Schema.org JSON-LD is reliable and free on major recipe sites — Haiku fallback is the exception, not the rule
  • Dietary awareness needs two enforcement layers: prompt-level (inline warning during conversation) and data-level (scanner on all ingredients at save time) — one layer alone can be bypassed
  • Store routing follows each platform's natural access point — Kroger has a developer API, Costco has a web interface, Amazon has direct search URLs; forcing a uniform integration pattern onto all three would be the wrong abstraction
  • scrollIntoView() on nested scroll containers scrolls all ancestors — fixed by targeting containerRef.current.scrollTop directly on the specific chat div

TODO

  • Apply supabase/migrations/003_kitchen_module.sql in Supabase SQL Editor before testing
  • Test URL import with 3–5 real recipe URLs; test conversational flow with 2–3 meals
  • KM-2: Meal Planning — meal_plans, meal_plan_entries, cook_events schema + MealPlanOverlay weekly grid
April 30, 2026
UNDERSTORY-LABSfeaturebrandinfrastructure

Design System, Pages, and Ecosystem Visual — Steps 2 and 3 complete

Features

  • Design system built — Cybernetic Nature palette as CSS custom properties, Tailwind v4 @theme inline tokens, 3-tier font system (JetBrains Mono / Share Tech Mono / DM Sans) via next/font
  • Component library shipped: Heading, Label, Body, Section, GlowCard, ElectricAccent, Nav, Footer, PageTransition
  • GlowCard bioluminescent effect — two cross-fading border glow layers (crystal teal ↔ accent green) using opposite-phase opacity animation; no color animation, no repaints
  • ElectricAccent — traveling pulse built on Framer Motion left animation inside overflow: hidden; 3s duration, 1.5s repeat delay
  • Nav — Framer Motion layoutId animated underline slides between active desktop links; mobile hamburger opens AnimatePresence full-screen drawer
  • PageTransition — Framer Motion fade + 8px drift keyed to usePathname, fires on every route change
  • All pages built with real content and structure: /projects (GlowCards with descriptions, tech tags, links), /infra (service status cards, container table), /changelog (timeline spine with color-coded entry types), /wiki (public/internal two-column split with amber auth badge)
  • Ecosystem Flow diagram — 4-column SVG (INFRASTRUCTURE → SERVICES → APPLICATIONS → DATA) with 7 rect nodes, HUD corner bracket decoration, and 7 animated electric current connections; electric-flow CSS keyframe on stroke-dashoffset
  • Home page: TreeHero + Ecosystem section below fold; "Explore" scroll hint now meaningful

Bug Fixes

  • Hero title colliding with crystal node labels on hover — removed labels from tree nodes entirely; title repositioned from top: 55% to top: 35%
  • .tree-hero-content and .tree-hero-scroll changed from position: fixed to position: absolute — were bleeding over ecosystem section on scroll
  • Lore incorrectly placed in DATA column of ecosystem map — moved to APPLICATIONS column; Save State is now the sole DATA node
  • useEffect(() => setMenuOpen(false), [pathname]) in Nav — react-hooks lint error; replaced with onClick on each mobile drawer link

Infrastructure

  • Framer Motion 12.x added as primary animation library
  • 5 unused Create Next App scaffold SVGs deleted from public/
  • Card.tsx deleted — superseded by GlowCard
  • Three ecosystem variants (Flow, Tree, Radial) built and presented via in-browser switcher; Tree and Radial deleted after Flow selected

Lessons

  • Building all three ecosystem options as live toggleable components let the user make a real visual decision rather than describing from descriptions — faster than mockups and zero ambiguity
  • Bioluminescent color-shift via two border layers at opposite animation phases achieves the effect without animating color values — GPU-composited and performant
  • position: fixed on hero content elements works fine for single-page heroes but breaks the moment anything is below the fold — establish correct positioning before adding scroll content
  • Verify hardware facts before writing copy — "Dell hardware" was wrong for Taproot (custom-built)

TODO

  • Step 4: Deploy Taproot status API to docker-host (192.168.1.153) — Express endpoints for /status, /containers, /uptime; Cloudflare Worker proxy at api.understorylabs.co
  • Commit all session work before next session (nothing committed this session)
  • docker-host IP is DHCP — consider setting a static lease in router
BUDinfrastructureaifeature

PurchaseHandler live — Steps 2–4 complete, n8n up

Features

  • PurchaseHandler local test passed — 3/3 email formats (Amazon, DoorDash, Netflix) extracted and written to Supabase
  • Handler deployed on docker-host — FastAPI container running at 192.168.1.153:8001, health endpoint confirmed from network
  • n8n operational — CT 102 (192.168.1.152:5678) created, Docker installed, n8n running and survives reboot
  • Gmail API enabled on Google Cloud project life-dashboard — OAuth credentials next

Bug Fixes

  • Claude Haiku wraps JSON extraction responses in ```json ``` fences despite explicit instructions — stripped before model_validate_json() in claude_client.py

Infrastructure

  • Supabase schema live — email_log and purchases tables with RLS, indexes, and soft delete
  • .env.example committed — documents SUPABASE_URL, SUPABASE_SERVICE_KEY, ANTHROPIC_API_KEY
  • services.md created at ~/.claude/services.md — global inventory of cloud platforms, APIs, and self-hosted services; updated by /wrap going forward
  • n8n compose file uses N8N_SECURE_COOKIE=false — required for HTTP access on local network

Lessons

  • Smaller models (Haiku) reliably ignore "no markdown formatting" instructions — fence stripping is a required defensive layer, not optional
  • scp with ~ fails in PowerShell; full Windows paths (C:\Users\nrisa\...) required — write files locally and copy over rather than fighting heredocs or rsync
  • Services inventory needs to exist before the second integration, not after — build the index early so "do I already have a GCP project?" has a real answer

TODO

  • Step 5: create Google OAuth 2.0 credentials in life-dashboard, wire Gmail trigger in n8n
  • Add bud to save-state project registry via /ship
LOREbugfeature

Server settings modal — three root causes diagnosed and fixed

Bug Fixes

  • SceneLayer missing from channel page — dropped in Nathan's layout redesign merge (dde6164), wired back via useEffect + PlaceThemeRegistry side-effect imports in ChannelPage
  • Server settings modal closes immediately on open — React 18 synchronous flush processes the backdrop click in the same tick; fixed with setTimeout(() => setShowServerSettings(true), 0)
  • Server settings modal lost on channel navigation — local useState resets when ChannelPage remounts on route change; fixed by lifting to useUIStore.serverSettingsServerId in Zustand
  • TOC header (server name) invisible — absolute inset-0 texture overlay painted over the header div, which had no stacking context to escape; channel list visible because overflow-y: auto creates one; fixed with z-index: 0 on overlay and relative z-10 on header and footer

Infrastructure

  • .vercelignore at repo root: excludes apps/web/public/places/*/sound/ (~640MB WAV) and docs/poc/ (~320MB POC audio); Vercel CLI reads this from the invocation directory, not from rootDirectory in the dashboard — file at apps/web/ had no effect
  • Vercel deploy now succeeds — upload dropped from 641MB to 2.9KB after exclusions

Lessons

  • Three separate bugs shared one symptom (modal not accessible) — each required a different diagnosis: React timing, component lifecycle, CSS stacking order
  • CSS stacking order is non-obvious when overflow-y: auto creates a stacking context for some children but not others — same overlay covers some siblings and not others depending on their CSS properties
  • Local testing against a live API requires CORS allowance for the dev origin; without it, even token injection workarounds hit the 15-minute JWT expiry wall before anything can be verified
  • Vercel .vercelignore placement is not documented prominently — it must live where vercel deploy is invoked, which differs from where rootDirectory is set in the dashboard

TODO

  • Nathan to review and merge fix/server-settings-click-through PR on Gitea
  • Nathan to add http://localhost:3002 to EXTRA_ORIGINS to unblock local testing
April 25, 2026
CURRENT OStoolinginfrastructurerefactor

CLAUDE.md restructured — manifest index, five reference docs

Features

  • CLAUDE.md reduced from 546 to 115 lines — detailed specs extracted into five focused docs under docs/
  • docs/architecture.md — full subsystem specs: gravity layout, items table, triage, capture, mobile, PWA, AI intake engine, context engine, home projects, AI API, infra principle, and component file map
  • docs/phases.md — all phase completion tables (MVP through PI-6) and plan/vision document links consolidated in one place
  • docs/widgets.md — per-widget behavioral specs: Calendar, Week, NowPlaying, Queue, Lyrics, Affirmations, DailyBrief, Lists
  • docs/effects.md — breathing glow rAF loop, title line glow, header spark, ambient canvas, WidgetCard two-div structure
  • docs/aesthetic.md — palettes, 3-tier font system, card/button specs, mode treatments, Cybernetic Nature direction
  • CLAUDE.md now uses a manifest-style reference table with "read before..." triggers — lean index loads each session, detail files read on demand

Infrastructure

  • Peer reference updated — ClaudeVault decommissioned; brother replaced full FastAPI + PostgreSQL + Redis app with declarative YAML manifest approach (same pattern as agent.yaml in everything-claude-code)
  • Global CLAUDE.md: background agent Write restriction documented in Known Environment Patterns

Lessons

  • Monolithic CLAUDE.md loads into every session regardless of what's being touched — modular docs eliminate noise; a background task on animations shouldn't carry 200 lines of phase history
  • The manifest pattern (declarative index + "read before..." triggers) is the right abstraction — same principle as agent.yaml in ECC: discovery surface without runtime overhead
  • Background agents spawned with run_in_background: true can't Write new files — permission is restricted; all new-file creation goes in the main session thread
  • everything-claude-code evaluated and declined — 183 skills, most for stacks not in use, overlaps heavily with custom setup; valuable as a pattern reference, not an install target
April 20, 2026
BUDfeatureinfrastructureai

PurchaseHandler built — schema live, handler ready for local test

Features

  • PurchaseHandler complete — extract → validate → store pipeline wired to /api/v1/handle/purchase
  • Extraction prompt with three few-shot examples (Amazon order, DoorDash delivery, Netflix subscription) — Haiku model, JSON-only output
  • HandlerResponse returns email_log_id and purchase_id on success — n8n gets traceable IDs, not just a status string
  • amount > 0 and non-empty vendor validation gates every write — no silent bad data reaches Supabase

Infrastructure

  • email_log and purchases tables live in Supabase — RLS enabled, dedup index on message_id, FK from purchases into email_log
  • Migration files in migrations/ numbered 001/002 — ordering enforces the FK dependency at run time
  • Sync Supabase client wrapped in asyncio.to_thread — avoids supabase-py async API fragility across 2.x minor versions
  • email_log row written before purchases insert — if the purchases write fails, the audit trail exists and carries the error

Lessons

  • Supabase service_role bypasses RLS by design — adding a write policy is redundant and misleading; only anon/authenticated reads need explicit policies
  • supabase-py async API changed names between 2.x minor versions (acreate_client vs create_async_client) — sync + asyncio.to_thread is the portable fix
  • Prompt templates with JSON few-shot examples can't use .format() — JSON braces read as format variables; .replace() per slot avoids the trap
  • Write the audit log row first, then the derived row — partial failures are recoverable; a missing anchor row is not

TODO

  • Step 2 checkpoint: venv setup, .env, curl tests with three email formats against localhost
  • Steps 3–6 after checkpoint: Docker deployment on docker-host, n8n LXC, Gmail trigger workflow, end-to-end validation
UNDERSTORY-LABSfeaturebrand

Tree Hero — Leonardo tree, crystal nodes, interactive landing page

Features

  • Landing page hero built — full-viewport Leonardo AI tree illustration with 7 interactive crystal nodes positioned over the bark
  • Crystal node system: CSS breathing glow (crystal-breathe) and expanding ring pulse (crystal-ring) keep nodes visible at rest, intensify on hover
  • Node map covers the full Understory ecosystem — Bark, Shed, Bud, Taproot, Current OS, Rootstack/Save State, Lore — each linking to its destination (internal routes or external domains)
  • Debug coordinate mode: click-to-log x/y positioning tool for placing nodes precisely over the tree image
  • Tagline backing: subtle dark pill behind "We build beneath the canopy." for readability against bark texture
  • Tree image container uses aspect-ratio: 2400/3616 so crystal positions stay relative to the image regardless of viewport width

Infrastructure

  • Taproot Status API code complete — Express service (CommonJS, Node 20) with /health, /status, /containers, /uptime endpoints; Cloudflare Worker proxy with caching and CORS; deployment guide written. Awaiting deployment to Taproot.
  • Cloudflare DNS records configured — A @76.76.21.21, CNAME os and logcname.vercel-dns.com (DNS only). Old Porkbun records deleted.

Lessons

  • Hybrid approach — Leonardo for organic complexity, CSS for electric interactivity — solved the tension between "alive" and "electric" that neither could achieve alone
  • Content Ref in Leonardo pulls composition back to the reference image's structure — Style Ref preserves aesthetic without constraining layout
  • Crystal nodes invisible at rest is a UX dead end — constant ambient glow (opacity 0.5→1 breathing cycle) makes interactive elements discoverable without explicit affordances
  • Positioning interactive elements over a background image requires an aspect-ratio container, not viewport-relative percentages — the image doesn't fill the viewport at all widths

TODO

  • Turn off DEBUG_POSITIONS in TreeHero.tsx once node positions are finalized
  • Deploy Taproot Status API to docker-host (requires SSH session)
  • Step 5: Infrastructure Dashboard page (depends on Status API deployment)
  • Steps 6–8: Changelog feed, Project pages, Wiki
April 19, 2026
LORESHIPPEDfeatureaudiophase

Immersive Places — all 3 places live in production

Features

  • Immersive Places integration shipped — Library, Tavern, and Meadow live at lore.harmjoy.us
  • SceneEffects.tsx: fBm WebGL fire glow shader with additive blending + canvas particle system (embers for Library/Tavern, fireflies for Meadow), presence-driven
  • AmbientSound expanded to 4-layer Howler.js system — base → detail1 → detail2 → full, with 1500ms presence-driven crossfades
  • Intermittent one-shot scheduler for page turns (Library) and bird calls (Meadow) — random intervals, not loops
  • Pull-back CSS filters on all hero images — cool mist + desaturation + blur dissolve as presence grows; scene shifts from cold/dormant to warm/alive
  • VignetteOverlay wires warm amber tint to normalizedPresence — the room responds to who's in it

Infrastructure

  • SceneAtmosphere.tsx deleted — replaced entirely by SceneEffects
  • 15 assets committed to apps/web/public/places/ (3 Ghibli Anchor A hero images, 12 sound files)
  • /start skill updated — fetches origin on every session start and reports upstream commits; silent for solo repos, visible for collaborative ones

Lessons

  • Single hero image per place was the right call — AI generation can't produce "same room at different fullness," CSS/shader manipulation of one image beats background swapping
  • Additive blending (SRC_ALPHA, ONE) adds light rather than compositing over it — fire glow reads correctly on dark scenes without washing out the background
  • "Your Books" on the home screen is vocabulary-correct but aesthetically misaligned — reads as literary organizing principle, not gathering space; worth revisiting
CURRENT OSfeatureaibug

PI-5 — Manual context injected into project drafts

Features

  • PI-5 complete — draft generation now injects ingested manual chunks into the intake prompt as a ## Manufacturer Instructions block; steps from the manufacturer's sequence are labeled (from manual), AI additions labeled (AI-supplemented)
  • api/_lib/taproot.ts established as the shared Taproot client — getChunks(projectId) fetches all cached chunks with 8s timeout, returns [] on failure so Taproot downtime never blocks draft generation
  • Generate Plan button is now disabled while any resource is ingesting — "wait for ✓ before generating" message appears below the button so the user can't race ahead of the chunk pipeline
  • Owned materials now display $0 in the price field instead of a strikethrough estimate — original price is preserved in state and restored if ownership is unchecked
  • Validation product references updated throughout both PI plan files — Ring doorbell replaced with Aqara Video Doorbell G4, "pergola kit" replaced with Mirador 10x13 Louvered Pergola

Bug Fixes

  • Skip disclaimer only fired on one of two skip paths — researchDecision === 'skipped' (Skip before research runs) and resourceApproval === 'skipped' (Skip after resources are found) are separate state variables; both must be checked
  • Vercel build failed with TS2835 on the _lib/taproot import — moduleResolution: nodenext in Vercel's API tsconfig requires explicit .js extension on relative imports even when the source file is .ts
  • api/ingest.ts was complete from a prior session but uncommitted — included in the PI-5 commit

Infrastructure

  • api/_lib/taproot.ts — new shared helper file; Vercel treats _lib/ as private (no route exposure)
  • ingestProjectKey threaded from ProjectIntakeOverlay into api/project-intake.ts request body — the temp UUID minted at resource-approval time is now the lookup key for chunk retrieval
  • Chunk injection capped at 30 chunks with a truncation note to prevent prompt bloat on large manuals

Lessons

  • Vercel's API tsconfig uses nodenext module resolution, which requires .js on relative imports — tsc --noEmit locally won't surface this; it only fails in the Vercel build pipeline
  • Two separate state variables controlling the same logical outcome (resource skip) is a footgun — a future refactor should unify them into a single resourceDecision enum
  • Disabling the action button during async pre-work is strictly better than a post-hoc warning — the user can't accidentally proceed, and the system state is always coherent when the button re-enables

TODO

  • Validate PI-5 checkpoint against Aqara Video Doorbell G4 and Tuff Shed — steps should carry (from manual) labels and reference actual manual content
  • PI-6: Build Companion API (api/project-companion.ts) + UI (Companion tab in ProjectsOverlay, FAB on mobile)
CLAUDE CODE SETUPbrandtoolinglaunch

understorylabs.co — domain, brief, plan, scaffold, and skill upgrades

Features

  • Understory Labs site identity locked — no self-description, work speaks for itself, Cybernetic Nature as the visual identity (not just a Current OS direction — the brand aesthetic)
  • Vision brief complete — personal brand as project showcase, infra dashboard, changelog, wiki, and ecosystem visual at understorylabs.co
  • 8-step build plan saved to ~/.claude/plans/understory-labs-site-plan.md — The Hub architecture, Leonardo for visuals, Cloudflare Worker for status API, n8n for changelog curation
  • understory-labs-site scaffolded at ~/Projects/understory-labs-site — Next.js, Tailwind v4, Cybernetic Nature palette as CSS custom properties, JetBrains Mono + DM Sans font system
  • Deployed to Vercel — three domains registered: understorylabs.co, os.understorylabs.co (Current OS), log.understorylabs.co (Save State)

Infrastructure

  • understorylabs.co purchased (Porkbun), DNS moved to Cloudflare — active and resolving
  • Resource inventory memory created at reference_resource_inventory.md — subscriptions, domains, infra catalogued for future brainstorm/plan sessions
  • /brainstorm updated — Phase 0 context inference (drafts answers from prior conversation instead of re-asking), resource inventory check added to Phase 4.5
  • /plan updated — resource inventory check added to Step 1.5
  • /execute-plan updated — parallel opportunity check added before execution and after each step
  • /wrap updated — resource inventory update step added after /save

Lessons

  • Titles go stale — brand identity built on what you make and how you think, not what your job is called
  • The Understory metaphor was never just an aesthetic — it's the site's architecture, its navigation metaphor, its proof of work
  • A records map names to IPs directly; CNAMEs alias one name to another — use CNAMEs for subdomains so infrastructure changes don't require manual DNS updates
  • Cloudflare proxying must be off (gray cloud) for Vercel-managed subdomains — proxying intercepts the TLS handshake Vercel needs to provision certificates

TODO

  • Add Cloudflare DNS records to complete Step 1: A @76.76.21.21, CNAME oscname.vercel-dns.com, CNAME logcname.vercel-dns.com (DNS only)
  • Delete old Porkbun A records from Cloudflare once new records are added
  • Verify all three domains resolve before starting Step 2 (Design System)
April 18, 2026
SAVE STATEbuginfrastructure

Build fix — output: export removed, analytics force-dynamic restored

Bug Fixes

  • output: "export" removed from next.config.ts — static export mode blocks any page with dynamic = "force-dynamic", surfacing as a Vercel build error on /analytics

Lessons

  • The analytics page's force-dynamic is correct: getAllProjects() and getAllFeatures() hit Supabase at request time, and todayStr() makes momentum score, streak, and recency calculations inherently time-sensitive — a static snapshot would go stale immediately
  • CLAUDE.md was already ahead of the config ("dynamic SSR — no longer static export") — the stale output: "export" was a local uncommitted modification, not a regression in the committed codebase
LOREfeatureaudiophase

Immersive Places POC complete — effects, sound, and composite all validated

Features

  • Effects POC validated with real Ghibli hero images — all 3 places render correctly behind frosted glass UI shell
  • Particle colors made place-aware — tavern embers now gold-amber, meadow fireflies warm cream-gold instead of uniform orange
  • Sound POC built — Howler.js 4-layer system (base → detail1 → detail2 → full ambient), each layer fading in as presence grows
  • 12 ambient audio assets sourced from Freesound and configured per place — library fire, clock, pages, room tone; tavern hearth, dinner table, murmur, crowd; meadow wind, bird, rustle, chorus
  • Intermittent sound model confirmed — pages and bird calls use scheduled one-shots with random intervals (12–35s, 10–30s), not loops
  • Composite POC complete — WebGL shader, canvas particles, Howler.js audio, and full Lore UI shell (sidebars, header, input bar) unified in a single page across all 3 places
  • Pipeline doc written (docs/plans/2026-04-18-immersive-places-pipeline.md) — integration plan mapped to existing SceneLayer / PlaceSceneRenderer / AmbientSound architecture, 10-step implementation order, asset manifest, per-place checklist

Bug Fixes

  • Clock volume capped at 25% — was audibly competing with fire base layer
  • Tavern clink replaced with dinner table ambience (Mr_Alden, Freesound 365676) — crystal clink was too repetitive regardless of random-seek approach; looping ambient table activity reads more naturally

Infrastructure

  • Howler.js downloaded locally (howler.min.js) — CDN load blocked on file:// protocol in Chrome
  • npx serve established as the local POC test pattern — browser blocks audio loading from file:// even with html5: true; must serve via HTTP

Lessons

  • Browser security blocks audio loading from file:// regardless of Howler config — every audio POC needs a local HTTP server, not a double-click
  • Intermittent detail sounds (page turns, bird calls) need random scheduling, not loops — a constant page-turning loop is immediately uncanny
  • Sound variety matters more than volume — random-seek on a single-sample clink still sounds repetitive; an ambient file with natural variation (dinner table) solves it better than any seek strategy
  • Windows hides file extensions by default — renaming library.jpg in Explorer silently produces library.jpg.jpg; turn on extensions before any rename workflow

TODO

  • Integration: create SceneEffects.tsx (port WebGL + canvas from POC), update LibraryScene.tsx, expand AmbientSound.tsx to 4-layer system — see pipeline doc for full order
TAPROOTinfrastructurefeature

PI-4 deployed — ingest, chunking, and retrieval live on Taproot

Features

  • PI-4 fully deployed — /ingest, /chunks/:projectId, and /retrieve endpoints live at research.rootstack.dev
  • Tuff Shed PDF ingested end-to-end — 13 pages, 4 chunks, BM25 retrieval returning correct assembly steps
  • Research service upgraded to v1.1.0 — Brave Search + ManualsLib + manufacturer direct + full ingest pipeline in a single container

Bug Fixes

  • Node 18 → Node 20 in Dockerfile — axios 1.7+ pulls in undici which requires File global not available until Node 20; container was crash-looping
  • ZFS cache permissions set on Proxmox host — chmod 777 /taproot-data/research-cache required at the host level; LXC bind mount is read-only from inside the container

Infrastructure

  • homelab/services/research/ is now the canonical source — Brave Search scrapers added, PI-4 files (ingest, cache, retrieve) merged, port locked to 3002
  • ZFS bind-mount added to CT 100 via pct set 100 -mp0 — dataset taproot-data/research-cache (already existed from Step 1) mounted at /taproot-data/research-cache
  • Old /opt/research-api (unused TypeScript code) removed from docker-host

Lessons

  • Docker images survive source directory cleanup — container keeps running from the cached image even if the build directory is gone; only matters for future rebuilds
  • Merging two diverged implementations requires picking one as the base and grafting from the other — keeping the deployed service's search providers and adding the PI-4 endpoints was cleaner than replacing everything

TODO

  • PI-5: build api/_lib/taproot.ts helper and inject approved chunks into api/project-intake.ts draft prompt
CURRENT OSbrainstormnamingbug

WeekWidget fix + Bud brainstorm — inbox intelligence named and scoped

Features

  • Bud vision brief complete — inbox intelligence system powered by n8n on Taproot with six handler types: purchase, coupon, delivery, appointment, reference, triage
  • Cross-agent orchestration scoped — Bud feeds Current OS (tasks, radar, triage), Shed (project materials), Research API (product intelligence), Google Calendar, and a financial dashboard
  • Name locked: Bud — dormant potential waiting to open, "nip it in the bud" for early problem detection, BUD inside BUDGET, buddy personality
  • Standalone project — own repo, own LXC on Taproot, financial dashboard linked from Current OS under a broader domain TBD

Bug Fixes

  • ISSUE-011: WeekWidget "This Week" card now auto-focuses when Work toggle activates — GravityWatcher gained a workVisible transition watcher that calls focusCard("week") on the false → true edge

Lessons

  • Brainstorms should surface established self-hostable tools (n8n, Node-RED) before custom-build options — n8n was the obvious fit for email orchestration but had to be raised by the user
  • Naming requires the design brief as input — two rounds failed before consulting the redwood forest ecosystem aesthetic
  • Humble name for sophisticated tech (Shed, Bark, Bud) is the Understory Labs signature — the contrast IS the brand

TODO

  • /plan on Bud — purchase handler first, end-to-end through n8n
  • Cross-agent protocol design — how Bud, Shed, and Research API communicate
  • Confirm ISSUE-011 fix after testing
April 17, 2026
TAPROOTinfrastructurefeature

Lost session recovery — research service confirmed live, PI-1 through PI-4 discovered complete

Features

  • Research service verified end-to-end — https://research.rootstack.dev/health returns 200 from cellular, Tuff Shed query returns manufacturer direct + Brave + fallback results
  • PI-1 through PI-4 discovered complete from retrospective entry — product detection, resource approval UI, ingest pipeline, and per-resource status badges all built in the lost session

Infrastructure

  • Deployed service differs from repo: JS/Express, port 3002, Brave Search + ManualsLib + manufacturer direct — TypeScript research-api archived to _archive/
  • config.yml synced with server — research.rootstack.dev → localhost:3002 entry added
  • Research service source lives in ~/Projects/homelab/services/research/ — deployed version is PI-2 only; PI-4 endpoints written but not deployed (ZFS bind-mount + service rebuild pending)
  • homelab CLAUDE.md, global CLAUDE.md, and product intelligence plan updated to reflect actual state

Lessons

  • Lost session state is recoverable — docker ps and ls /opt reconstruct what was deployed; retrospective Save State entries reconstruct what was built
  • cut -d= -f2 silently truncates base64 keys with trailing =; grep -oP '(?<=KEY=).*' handles them correctly
  • The repo and the server diverged during development — implementation language, port, and architecture all changed; the repo was never updated

TODO

  • Deploy PI-4: ZFS bind-mount into CT 100 → rebuild research service → validate /ingest and /retrieve against a real PDF
  • PI-5: inject approved chunks into api/project-intake.ts draft prompt
CURRENT OSfeatureaiinfrastructure

Product Intelligence — PI-1 through PI-4 built (retrospective)

Features

  • Product detection lands in intake engine — product_assembly_detected inference fires when AI identifies a specific kit or product, readyToResearch flag signals the UI to offer research before drafting
  • Research service deployed on Taproot — research.rootstack.dev live, returns ManualsLib results + YouTube search URL + DuckDuckGo fallback for any product query
  • Resource approval UI complete — "Resources Found" section in observations panel with type badges, checkboxes, Add your own URL input, and three explicit degradation paths when Taproot is unreachable
  • Ingest pipeline written — /ingest endpoint downloads PDFs and HTML, extracts text, chunks by step-pattern → headings → 500-token windows, stores to ZFS cache under a temp project key
  • Per-resource ingest status in intake overlay — loading / done (chunk count) / image-only-warning / error badges fire immediately on resource approval, non-blocking

Bug Fixes

  • Research fetch moved out of useEffect — cleanup function was aborting the in-flight request the moment researchStatus changed from idle to loading, causing an immediate AbortError
  • @vercel/node import removed from api/research.ts — Vercel infers the runtime; explicit import broke the build
  • Type assertion added for manual resource entry — TypeScript couldn't narrow the type field on user-pasted URLs

Infrastructure

  • api/research.ts — Vercel proxy with 10s timeout, forwards to TAPROOT_RESEARCH_URL with x-api-key auth; RESEARCH_API_KEY never exposed to the browser
  • api/ingest.ts — second Vercel proxy, 25s timeout for PDF downloads
  • project_resources.source column added — distinguishes ai-discovered from manual resources; feeds the future manual library
  • src/cache.js, src/ingest.js, src/retrieve.js written in ~/Projects/homelab/services/research/ — BM25-lite keyword retrieval, ZFS-backed chunk storage
  • PI-4 not yet deployed — ZFS bind-mount into docker-host (requires CT stop) and service rebuild are the next physical steps

Lessons

  • Chunking strategy order matters — trying numbered steps first (assembly manuals are full of them) before generic heading detection before token windows produces far better chunks than the reverse
  • ingestProjectKey needs to be a temp UUID minted at resource-approval time, not the Supabase project ID — the project doesn't exist yet when ingestion kicks off; PI-5 will wire the linkage
  • Explicit degradation beats silent fallback — presenting four named choices when Taproot is unreachable is only slightly more code and completely changes the user's ability to recover

TODO

  • Deploy PI-4: ZFS dataset on Proxmox host → bind-mount into CT 100 → rebuild research service → push Vercel changes
  • Validate PI-4 checkpoint: ingest a real Ring doorbell PDF, confirm /retrieve returns relevant sections
  • PI-5: inject approved chunks into api/project-intake.ts draft prompt using ingestProjectKey
April 12, 2026
BARKbugaiaudio

Bug sweep — soundboard spinner, phrase match, error surfacing

Bug Fixes

  • Soundboard "Add to Soundboard" spinner hung forever — root cause: except RuntimeError too narrow, leaving FileNotFoundError (expired temp file) and discord.errors.Forbidden unhandled before followup.send() was called; broadened to except Exception
  • Public channel announcement failure masked as upload failure — upload and announcement now in independent try/except blocks; announcement errors silently ignored
  • "Phrase Not Found — Failed to analyze transcript" on valid matches — Claude was generating retry_suggestions as a second JSON block instead of embedding it in the result object; rewrote prompt to show retry_suggestions inside each schema example
  • max_tokens raised 512 → 1024 in phrase matcher — response truncation was cutting off JSON mid-object on longer explanations

Infrastructure

  • Parse error logging added to matcher fallback path — raw Claude response and exception now printed to service-stdout.log for diagnosis instead of silently swallowing

Lessons

  • Discord deferred interactions require followup.send() to resolve the spinner — any unhandled exception before that call leaves the spinner permanent and silent
  • Claude prompt structure shapes response structure: a "in ALL cases, include X" instruction after the schema produces a second JSON block, not a merged one; shared fields belong inside every schema example
  • service-stdout.log and service-stderr.log carry different signal — stdout gets Python print() output, stderr gets discord.py tracebacks; check both when debugging
TAPROOTinfrastructure

Steps 6 + 8 complete — Vaultwarden live, rootstack.dev tunnel operational

Features

  • Vaultwarden deployed and operational — password manager live at vault.rootstack.dev with real HTTPS
  • Bitwarden extension connected to self-hosted vault — all Taproot credentials transferred and accessible
  • Cloudflare Tunnel established (UUID 5f21212a-2895-42f2-9b77-ffd6056af6cf) — Taproot services reachable externally without port forwarding or exposing home IP
  • rootstack.dev registered via Cloudflare Registrar — infrastructure domain live
  • DNS routes configured: status.rootstack.dev → Uptime Kuma, vault.rootstack.dev → Vaultwarden, research.rootstack.dev pre-configured for research service
  • ISSUE-017 resolved — Vercel serverless can now reach Taproot, unblocking the product intelligence feature

Bug Fixes

  • Ubuntu 24.04 SSH blocks root via three separate mechanisms (PermitRootLogin, PasswordAuthentication, and drop-in sshd_config.d overrides) — sshd_config on CT 100 also had immutable bit set, requiring chattr -i before sed could edit it
  • docker-compose-v2 conflicts with Docker's built-in compose plugin — removed; docker compose used directly
  • cloudflared service install failed with "cannot determine default configuration path" — fixed with explicit --config /etc/cloudflared/config.yml flag
  • GPG dearmor command truncated when piped in SSH terminal — split into two steps: curl to temp file, then gpg separately
  • Vaultwarden enforces HTTPS for all operations — HTTP access non-functional by design; Cloudflare Tunnel resolves this with real TLS rather than fighting self-signed cert workarounds

Infrastructure

  • CT 101 recreated fresh (Ubuntu 24.04, nesting enabled) after SSH lockdown and package conflicts proved unresolvable on the original container
  • cloudflared installed on docker-host (CT 100) via official Cloudflare apt repo; tunnel config at /etc/cloudflared/config.yml
  • Config written locally, scp'd to server — heredoc-in-remote-terminal pattern retired; local-write + scp is now standard for any multi-line file creation on remote hosts
  • Product Intelligence plan confirmed (PI-1 through PI-6) — manufacturer site recon complete, hybrid scraping strategy validated (HTTP scraper + aggregators first, Firecrawl as last resort)

Lessons

  • Ubuntu 24.04 SSH lockdown is multi-layered — fixing one mechanism while others remain active wastes an entire session; audit all three before starting
  • The right solution is less work than the wrong one — Vaultwarden's HTTPS requirement wasn't a blocker, it was a nudge toward finishing Step 8; fighting it would have cost more time than the tunnel took
  • Heredoc in any remote terminal is unreliable — write multi-line configs locally and scp; this eliminates an entire class of paste-corruption errors
  • Manufacturer content is mostly accessible via simple HTTP scraper — Firecrawl is last-resort, not primary; most products have direct PDF URLs or are covered by aggregators (ManualsLib, Manualzz)

TODO

  • Change Vaultwarden ADMIN_TOKEN from placeholder to a strong credential
  • Tailscale (Step 8d) — private device-to-device access separate from public tunnel
  • Step 7: ClaudeVault
  • Step 9: Research Service deployment (needed for PI-2)
  • Execute product intelligence plan — PI-1 through PI-6
LOREfeatureinfrastructurephase

Usability overhaul complete — all 8 steps shipped

Features

  • Gathering vocabulary applied across all UI strings — Passage, Fireside, Section, Echoes, Whispers, The Circle, Gathered/Resting/Gone, Keeper/Elder/Guide replace the full literary terminology set
  • TopBar component with labeled navigation — BookShelf, home, and DMs all discoverable without icon hunting
  • Home screen Books grid with empty state — replaces blank loading screen with actionable landing point
  • Google OAuth wired end-to-end — /auth/google + /auth/google/callback handle consent, code exchange, profile fetch, and upsert by googleId → email → create; login and register pages gain "Continue with Google"
  • Onboarding overlay added — 4-step AnimatePresence flow with expanding dot progress, skip button, and idempotent POST /users/@me/onboarded endpoint; shows once on first sign-in, never again
  • Landing page replaces redirect spinner — SceneLayer at low presence, frosted glass hero, feature tiles, footer; authenticated users redirect to /app; copy is lorem ipsum placeholder pending Opus brainstorm
  • Presence wiring complete — useServerPresenceCount hook cross-references presenceMap against server.memberIds[]; SceneLayer presence prop is now live data

Bug Fixes

  • Fantasy theme contrast fixed — --lore-text and --lore-muted darkened to readable contrast ratios on light parchment surfaces
  • Password change route guards against null passwordHash — OAuth-only users get a clear error instead of a crash

Infrastructure

  • Schema: googleId (nullable, unique), passwordHash (nullable for OAuth-only users), onboardedAt (nullable timestamp) — two migration files created manually without a local DB
  • memberIds: string[] added to server list API response — presence hook uses it to scope presenceMap to current server's members
  • loginWithTokens() action added to auth store — reads access_token/refresh_token from URL params on /auth/callback, stores and hydrates session
  • NEXT_STEPS_FOR_NATHAN.md written — migration commands, env vars, Google OAuth app setup, Docker rebuild steps, CORS note
  • Branch feature/usability-overhaul pushed to Gitea; Vercel production deploy triggered and succeeded

Lessons

  • Landing page copy is a writing problem, not a code problem — deferring to a dedicated Opus brainstorm session was the right call; lorem ipsum commits cleanly and doesn't block the PR
  • Manual Prisma migrations (no local DB) are viable as long as they match the schema diff exactly — TypeScript catches anything that doesn't line up after prisma generate
  • OAuth without extra packages is cleaner than it looks — native fetch handles token exchange and profile fetch in ~50 lines with full control over the redirect flow

TODO

  • Nathan: run pnpm db:migrate, add Google OAuth env vars, create Google OAuth app, docker compose up -d --build api
  • Landing page copy — Opus brainstorm session, reference docs/plans/2026-04-12-usability-overhaul-vision-brief.md
  • CORS: Nathan adds lore-drab.vercel.app to EXTRA_ORIGINS to enable end-to-end testing from Vercel preview
  • Open PR on Gitea — requires logging into git.harmjoy.us as Nicole first
April 9, 2026
CURRENT OSbuginfrastructure

Fix project intake photo 413 error

Bug Fixes

  • Compressed project intake photos client-side (max 1200px, JPEG 85%) before base64 encoding — full-res phone photos exceeded Vercel's 4.5MB serverless body limit, returning HTTP 413.

Infrastructure

  • Linked .vercel project config locally — pre-push deploy hook now works for life-automation on this machine.

TODO

  • api/project-intake.ts lines 426–444: pre-existing TypeScript errors — property access on unknown type from JSON parse. Not blocking (build deploys), but should be fixed.
April 6, 2026
SAVE STATEinfrastructuretooling

Taproot onboarded — add-project script, entry cohesion audit

Features

  • add-project script ships — registers new projects via CLI flags, no Supabase dashboard required
  • add-project writes both Supabase row and globals.css CSS variable in one command — full project setup autonomous
  • Entry cohesion audit complete — 9 entries corrected across all projects: missing type fields, unapproved tags, wrapped bullets, Taproot description formatting
  • Step 4.5 added to /wrap — samples 2–3 existing entries of the same type before drafting to prevent style drift going forward

Bug Fixes

  • add-project.ts initially skipped globals.css — project color never written; detected when Taproot card showed no color on the project grid; fixed by porting ensureCssVariable logic from upsert-project.ts
  • Taproot description stored with literal \n\n instead of actual newlines — paragraphs not breaking; corrected via Supabase service role update

Infrastructure

  • scripts/add-project.ts created — CLI-flag interface (--id, --name, --tagline, --description, --tech, --color) for automation-friendly project creation; sort_order auto-detected from existing max
  • npm run add-project registered in package.json alongside npm run new-entry

Lessons

  • New scripts should read existing ones before duplicating logic — upsert-project.ts was already handling Supabase + CSS together; add-project.ts missed that step until the color gap surfaced
  • Entry drift accumulates silently — the audit found 6 missing type fields and 3 unapproved tags spread across 9 entries over a month of writing without a sampling step to catch it
April 5, 2026
TAPROOTNEW PROJECTlaunchinfrastructure

Taproot — debut

Features

  • Taproot is an old Windows PC converted to a Proxmox VE 9.1.1 homelab server — purpose-built for self-hosting services and learning infrastructure hands-on
  • The design principle is progressive self-sufficiency: start with monitoring and password management, build toward hosting AI services and running production workloads off the cloud
  • Foundation complete — Proxmox installed, ZFS single-disk pool on 2TB HDD, LXC containers running Ubuntu 24.04 with Docker, Uptime Kuma live on port 3001

Lessons

  • A single-disk ZFS pool has no redundancy, but acceptable for a learning server — the constraint forces clarity about what data actually needs protection
  • Proxmox's browser console has a display glitch that drops output; SSH into containers is the reliable path for any real terminal work
  • ISP-level DNS blocking (port 53 to 8.8.8.8) surfaces early — router-as-DNS is the practical workaround, not a configuration mistake to fix later
TAPROOTinfrastructure

Steps 5–6 — Uptime Kuma live, Vaultwarden container staged

Features

  • Uptime Kuma deployed and running — monitoring dashboard live at port 3001 on docker-host (CT 100)
  • Vaultwarden container created (CT 101, IP 192.168.1.165) — Ubuntu 24.04, Docker installed, compose deploy staged
  • Credential hygiene workflow established — Notepad scratch pad pattern now the standard for all multi-step build sessions

Infrastructure

  • docker-host (CT 100) confirmed fully operational — Docker CE, Compose plugin, hello-world verified
  • Vaultwarden container mirrors docker-host setup: same LXC config, same Docker install sequence
  • Uptime Kuma docker-compose.yml deployed to /opt/uptime-kuma with restart: unless-stopped

Bug Fixes

  • Docker apt sources malformed — command substitution in Proxmox console split across lines; fixed by hardcoding arch=amd64 and codename=noble directly in sources entry
  • Ubuntu 24.04 blocks root SSH by default — fixed with sed replace on PermitRootLogin in sshd_config
  • Gateway misconfigured to 192.168.100.1 during Proxmox install — corrected to 192.168.1.1 in network UI

Lessons

  • The Proxmox web console is unreliable for anything interactive — SSH first, console only as fallback
  • Interactive commands (passwd) produce no visible output in the console; non-interactive alternatives (echo 'root:pass' | chpasswd) are the only reliable path
  • Credential amnesia is a real session hazard — the Notepad rule exists for a reason; enforce it at session start, not after the first forgotten password

TODO

  • Complete Vaultwarden compose deploy (generate ADMIN_TOKEN, write docker-compose.yml, docker compose up -d)
  • SSH still failing on CT 101 — reset root password via chpasswd, retry
  • Transfer all session credentials from Notepad into Vaultwarden once live
  • Step 7: ClaudeVault (CT 102)
  • Step 8: Tailscale on Proxmox host
  • Step 9: git init homelab, initial commit
April 4, 2026
LOREinfrastructuretooling

CLAUDE.md landed — /wrap fixed, lore sessions now reach the changelog

Features

  • CLAUDE.md added to lore repo root — stack, literary terminology, deployment targets, collaboration conventions, and place theme architecture all documented
  • Place theme system (Library, Meadow, Tavern) captured as confirmed art direction — color-mix() frosted glass pattern included so Nathan's session has it

Infrastructure

  • /wrap skill: lore added to project mapping table, git push added to Step 7 — entries now write, commit, and deploy in one step
  • save-state was 5 commits ahead of origin — pushed and deployed, changelog now current through April 3
  • Pulled Nathan's Phader voice style system merge — settings page, phader.ts, preferences store, 19 files, 4457 insertions

Lessons

  • A missing git push in a skill's commit step makes the pipeline invisible — entries were being written and committed locally but never reached the deployed site
  • Project directory → ID mapping tables in skills decay silently as projects are added; wire new projects in at setup time, not retroactively

TODO

  • CORS blocker: lore-drab.vercel.app origin not whitelisted in Nathan's API — login/register broken on Vercel preview until EXTRA_ORIGINS is set on VPS
  • Nathan has open branches to check: add-claude-md, docs/claude-md, feature/vps-migration, merge/phader-plus-place-themes
LOREfeatureinfrastructurelaunch

Place Theme System — atmosphere picker, SceneLayer bridge, full aesthetic

Features

  • Place theme system shipped end-to-end — each Book now has an atmosphere (the-library, the-tavern, the-meadow) independent of genre
  • PlaceThemePicker component renders color swatches, name, and description for each place — wired into Create Book and Book Settings modals
  • ThemeContext bridge: active server's placeTheme drives SceneLayer and CSS variables app-wide, with 160ms opacity crossfade when switching servers
  • Auth pages (login, register) moved to (auth) route group — SceneLayer background and frosted glass card on both
  • Invite page and 404 page get library scene background — every public URL now has the aesthetic
  • Root redirect page updated from plain spinner to SceneLayer-backed loading state

Infrastructure

  • placeTheme String @default("the-library") added to Server model — Prisma migration applied to production DB
  • API validates placeTheme on POST and PATCH /servers against enum of three valid place IDs
  • PlaceThemeId type and PLACE_THEMES array added to @lore/types
  • Merge conflict resolved with Nathan's commits — adopted his animationKey improvement (coarse key groups channels by server, not per-channel) and getStatus voice fix in ReaderList
  • PR opened and merged on Gitea — branch protection on master confirmed active

Lessons

  • Scoping CSS variables as inline style on a container element overrides root vars set by ThemeProvider — clean way to isolate palette without fighting the context
  • Tailwind opacity modifiers silently fail on CSS variable colors — color-mix(in srgb, var(--color) 80%, transparent) is the fix
  • lore.harmjoy.us is the right QA target — Nathan's production is VPS-deployed, not Vercel; lore-drab.vercel.app is Nicole's preview and isn't in EXTRA_ORIGINS
  • Nathan needs to pull and rebuild Docker on VM108 for changes to appear on his production URL — auto-deploy not set up yet

TODO

  • Ask Nathan to set up Gitea Actions runner for auto-deploy on merge to master
  • lore-drab.vercel.app CORS: Nathan adds it to EXTRA_ORIGINS if Vercel URL needs to work against live API
April 3, 2026
LOREfeatureinfrastructurephase

Art Direction to Production — SceneLayer wired, POC merged to master

Features

  • POC branch merged to master — place-based themes (The Library, The Tavern, The Meadow) now in the main codebase
  • SceneLayer threaded behind the authenticated app shell — photorealistic backgrounds render beneath all UI
  • Frosted glass pattern applied to all three chat UI panels: BookShelf, ChapterSidebar, ReaderList, channel header — color-mix() inline styles replace opaque surfaces
  • Scene is hardcoded to the-library pending theme system bridge (Step 3)

Bug Fixes

  • @types/react duplicate resolved — pnpm installed React 18 types (for Expo) and React 19 types (for web) as separate physical copies; TypeScript surfaced them as incompatible ReactNode types even in .tsx source files; fixed with pnpm.overrides forcing ^19.0.0 across the monorepo

Infrastructure

  • vercel.json added at repo root — framework, installCommand, buildCommand only; rootDirectory lives as a permanent Vercel project setting (not in config file)
  • .vercel/ added to .gitignore

Lessons

  • Tailwind v3 opacity modifiers (bg-lore-surface/80) don't work with CSS custom property colors — color-mix(in srgb, var(--color) 80%, transparent) in inline styles is the correct pattern
  • skipLibCheck: true doesn't protect against duplicate @types/react in source .tsx files — the TypeScript checker still sees both ReactNode shapes when resolving JSX; the fix is at the package resolution layer, not the compiler
  • Book spines preserved by design — each Book is a portal to a place; pulling a spine takes you into that world; the gathering vocabulary (Grounds, Passages, Firesides) applies inside, not at the server list level

TODO

  • Step 3: theme system bridge — placeTheme field in Prisma schema, ThemeContext reading PlaceThemeRegistry, server creation/settings UI
  • Step 4: presence wiring — useServerPresenceCount(serverId) feeding real online count into SceneLayer
  • Step 5: terminology rename — PassagesSidebar, GatheringArea, CircleList, flame icon channel prefix
  • Step 6: root CLAUDE.md from merged draft
  • Ambient sound: deferred brainstorm item
CURRENT OSfeaturerefactorphase

Gravity Layout — self-sizing cards replace CSS grid

Features

  • Gravity layout system complete — cards size themselves by focus state, dashboard adapts to any height without manual resizing
  • GravityContext: three card states (focused / resting / collapsed), max 2 focused per column, oldest-first demotion, 60s auto-focus cooldown, localStorage persistence
  • GravityCard render-prop pattern: widgets receive variant: 'focused' | 'resting' and render purpose-built compact views at rest — not shrunken full views
  • GravityColumn: narrow prop collapses dual-column layout to a single scrollable column below 900px
  • NowPlaying pinned at top of right column — always visible, no title bar, no collapse
  • Mode system simplified: "online" | "offline" + workVisible boolean replaces the three-mode system
  • Work toggle: header pill swaps accent to teal without changing the environment — data-mode="online-work" inherits pine-forest base, overrides accent only
  • GravityWatcher: auto-focus signals wire morning→brief, music starts→lyrics, item captured→tasks

Bug Fixes

  • LyricsGravityCard and QueueGravityCard never called registerCardfocusCard("lyrics") was a silent no-op, persistence and focus limits broken for both

Infrastructure

  • DashboardShell reduced from ~1094 lines to ~400 — all grid infrastructure (MODE_LAYOUTS, getRows, GridResizeOverlay, expandedCards, rowOverrides) removed
  • /gravity-test route added for layout testing without auth

Lessons

  • Wrappers around non-GravityCard components need explicit registerCard in a useEffect — skipping it is a silent failure with no error, just no behavior
  • Keeping the environment (pine-forest base) while swapping only the accent makes the work toggle feel like a lens over personal space, not a separate room
  • GravityProvider must sit outside key={mode} — state reset on mode switch is the wrong default for a layout system
April 1, 2026
LORElaunchrefactorinfrastructure

Art direction POC deployed — /vision route live

Features

  • Art direction POC complete — all four plan steps shipped on feature/art-direction-poc
  • Three themed environments live: The Library, The Tavern, The Meadow — photo backgrounds with presence-responsive lighting
  • Four surface views: Landing, About, Server (mock chat UI layered over scene), Community (ambient presence dots)
  • Demo controls: theme crossfade, presence slider (0–50), surface tabs, auto-demo mode, sound toggle

Bug Fixes

  • Tavern scene: removed all positioned circular radial-gradient elements — last remaining source of floating orbs
  • Dev debug readout removed from VignetteOverlay before sharing

Infrastructure

  • Vercel CLI installed and project linked (nrisacher-langs-projects/lore) — repo on self-hosted Gitea requires manual CLI deploys, no GitHub integration
  • rootDirectory: apps/web set via Vercel API — not settable in vercel.json (causes deploy failure if attempted there)
  • Next.js updated 15.1.0 → 15.5.14 — Vercel blocks deployment of vulnerable versions (CVE-2025-29927, middleware auth bypass)
  • Preview live at https://lore-drab.vercel.app/vision

Lessons

  • Positioned circular radial-gradient divs always read as orbs against photorealistic backgrounds — blend modes don't fix it. Full-width linear gradients and full-scene mixBlendMode: overlay tints are the correct approach for shapeless presence indication.
  • rootDirectory is a Vercel project setting, not a vercel.json key — Vercel CLI v50 also removed --root-directory. Set it once via API: PATCH /v9/projects/{id} with {"rootDirectory": "apps/web"}.
  • Deploying from the monorepo root (not apps/web) is required — Vercel must see the pnpm lockfile at root to resolve workspace dependencies.

TODO

  • Source Lottie animation files (fire, candles, petals) — LottieLayer and Howler are both wired, waiting on assets
  • Open PR on Gitea for Nathan's review — draft description already written
March 29, 2026
LOREbrainstormnamingbrand

Gathering, Not Reading — art direction vision and vocabulary

Features

  • Art direction pivot from library/book aesthetic to campfire/gathering — Lore's identity rebuilt around the third-space concept
  • Full vocabulary rework — fire & oral tradition replaces literary metaphor: Grounds (server), Passage (text channel), Fireside (voice), Whisper (DM), Keeper/Elder/Guide (roles)
  • Community lifecycle as fire spectrum — Spark → Ember → Campfire → Bonfire → Beacon, with decline states (Banked, Coals, Cold) honoring soft-delete philosophy
  • Hearth as personal home designation — any community regardless of size can be your Hearth
  • Themes are places, not genres — each theme answers "where are we gathered tonight?" and is specific enough to drive every art decision
  • Three POC themes defined: The Library (Harry Potter common room), The Tavern (medieval warmth), The Meadow (Ghibli pastoral)
  • Presence-responsive environmental scaling — spaces get bigger and richer as people gather, driven by warm vignette that expands from cozy to expansive (never cold to warm)
  • Ambient sound in scope — layered audio per theme scales with presence alongside visuals
  • POC plan confirmed — 4 steps, /vision route on a branch, Lottie + Howler.js, scene components built production-ready from day one

Lessons

  • "Lore" was never about books — lore is oral tradition, stories passed around fires, knowledge that lives in communities because people showed up
  • The name unlocked the vocabulary: once "gathering" replaced "reading" as the core verb, every naming decision cascaded naturally
  • "Fantasy" is too broad to drive art decisions — "The Library" tells you exactly what to draw
  • The emotional arc of presence scaling is cozy → expansive, not cold → warm — an empty space should feel intimate, not lonely
  • Themes as places rather than genres solved the extensibility problem — any place people gather is a valid theme
  • Prior session's brainstorm work was lost to an unsaved close — planning artifacts must be saved to files mid-session, not accumulated for wrap
CURRENT OSbugfeature

Work block reservation — weekday personal time now realistic

Features

  • /start skill now surfaces the full unworked feature backlog at session open — deferred candidates, wishlist items, and open design questions pulled from memory and design-notes
  • Backlog section grouped into "Ready to plan" and "Wishlist" with a pointer to /scope for details

Bug Fixes

  • Online mode on weekdays no longer reports 8am–5pm as personal free time — reserveWorkBlock parameter added to computeDayContext in the context engine
  • DailyBriefWidget, RadarWidget, and the AI context string all respect the work block — eligible items, day weight, and suggestions now reflect morning/evening availability only
  • RadarWidget now mode-aware via useMode() — eligibility recomputes on mode switch
  • ISSUE-008 closed — all three sub-items resolved across two sessions

Lessons

  • The context engine grid (8am–6pm) only sees 1 hour of personal free time after a 9-hour work block — the heuristic naturally suppresses discretionary items on weekdays, which is the right behavior
  • A default parameter (reserveWorkBlock = false) kept the change backward-compatible without touching every existing call site
March 28, 2026
LORENEW PROJECTlaunchprojects

Lore — debut

Features

  • Lore is a real-time chat platform built on Discord-style infrastructure wrapped in a book metaphor — servers are Books, channels are Chapters, voice rooms are Hearths, DMs are Pages
  • Genre theming is the core differentiator — each Book carries a genre (fantasy, horror, sci-fi, romance, cyberpunk) that applies a full CSS palette and ambient effects throughout the UI
  • Core infrastructure complete — messaging, voice via LiveKit, DMs, friends, roles, bots, and the full literary UX rename in place across all components

Lessons

  • First collaborative project — built with her brother, who hosts the repo and live API on his homelab; the constraint of a protected master branch and PR-required workflow is the right default for shared work
  • The literary metaphor earns its keep by being total — partial renaming would read as affectation, but renaming every layer (server → Book, channel → Chapter, moderator → Narrator) makes the world feel consistent
CURRENT OSbuginfrastructure

Mode separation, RLS cleanup, issue sweep

Bug Fixes

  • ISSUE-006 confirmed resolved — committed items not surfacing in TaskListWidget was caused by RLS infinite recursion on the projects SELECT policy, fixed in a prior session
  • ISSUE-007 confirmed resolved — work tasks disappearing after mode switch was the same root cause: fetchTasks joins items → projects(title), triggering the same recursion; optimistic insert appeared to succeed but every re-query returned null, making persistence look broken
  • ISSUE-008 (sub-problems 1 + 3) — DailyBriefWidget lacked mode awareness entirely; weekday tasks query had no mode filter, leaking work tasks into the Online brief
  • Brief cache key was current-os-facts-{date} — shared across modes; switching Online → Work reused cached Online facts; key is now current-os-facts-{mode}-{date}
  • "Work availability" label in the AI context string renamed to "Available time" — was mode-biased in Online brief context

Infrastructure

  • DailyBriefWidget now uses useMode() — tasks query always filters by mode, AI context hint is mode + weekend aware across four combinations
  • ISSUE-009 resolved — four "Project member reads X" SELECT policies in live DB updated via Supabase SQL Editor to use is_project_collaborator() instead of inline subquery; migration file synced to match

Lessons

  • A single circular RLS policy (projects → project_collaborators → projects) silently killed every query that joined to projects — the join itself was the trigger, not the query result; two distinct-looking bugs shared one root cause
  • Mode awareness needs to be explicit at every data boundary — a missing useMode() import left the brief entirely uninformed about which context it was writing for
  • DDL statements in Supabase (DROP POLICY / CREATE POLICY) return "no rows" on success — expected behavior, not an error

TODO

  • ISSUE-008 sub-problem 2 remains: planning overlay work block reservation (8am–5pm M–F in Online mode) — requires reserveWorkBlock flag in computeDayContext so freeHours reflects evening/morning personal time on weekdays
CURRENT OSbuginfrastructure

RLS recursion root cause found and fixed — commit-to-today unblocked

Bug Fixes

  • Half-screen scroll restored — overflow-hidden on <main> was clipping the stage at ~540px; replaced with overflow-y-auto and added minHeight: 640px to the grid wrapper so fr rows have a usable definite height
  • Stale closure in TaskListWidget items-changed handler fixed — handler was capturing a stale loadTasks reference; useRef + useCallback pattern ensures the latest version is always called
  • RLS infinite recursion (42P17) resolved — projects SELECT policy queried project_collaborators, whose policy queried projects, creating a deadlock loop on any query joining projects (e.g., items → projects(title))
  • Fix: is_project_collaborator() SECURITY DEFINER function queries project_collaborators directly, bypassing its RLS and breaking the cycle; projects SELECT policy updated to use it

Infrastructure

  • Diagnostic logging added to commitToToday and TaskListWidget committed items SELECT to surface silent failures during investigation
  • supabase/schema.sql synced to match live RLS fix — is_project_collaborator() function and updated project table policies now reflected in source of truth
  • Committed items moved above regular tasks in render order — visible without scrolling

Lessons

  • RLS policies that reference each other's tables form recursion cycles invisible until a cross-table query hits them — SECURITY DEFINER functions break the loop by querying at the function owner's privilege level, not the caller's
  • Silent count=0 failures are harder to diagnose than errors — adding a read-back SELECT after an update immediately surfaces whether the write landed

TODO

  • Confirm ISSUE-006 fully resolved — RLS fix is applied, but committed items behavior not yet user-verified in live session
  • ISSUE-007: work tasks disappear after mode round-trip
  • ISSUE-008: work/personal data leaking between modes in daily brief + planning overlay
March 25, 2026
SAVE STATEfeaturetoolinginfrastructure

Data lifecycle complete — /ship, visual treatment, guardrails

Features

  • /ship skill ships — marks features in Supabase and generates voice-matched type: ship changelog entries
  • Mode A ships existing features: --check confirms current state before any write runs; Mode B debuts new projects with full Supabase upsert and debut entry
  • Ship/debut visual treatment: full colored border, ambient glow via color-mix(), SHIPPED/NEW PROJECT badges, JetBrains Mono 700 title — visually distinct from session cards
  • 6 retroactive ship entries created for all historically shipped features across bark, current-os, save-state, and claude-code
  • Guardrails complete: /save prompts for /wrap when commits exist; /start flags unlogged sessions; /health Section 5.5 checks data freshness and stale in-progress features

Infrastructure

  • scripts/update-feature.ts — update or insert Supabase features with --check dry-run mode; case-insensitive name matching within a project
  • scripts/upsert-project.ts — upsert projects to Supabase and auto-add --project-<id>: <color>; to globals.css
  • Card.tsx extended with variant prop — --card-project-color set inline, letting .card-ship and .card-debut classes reference per-project color without per-project rules
  • seed-projects.ts deleted — Supabase data now managed via /ship and write scripts
  • Supabase corrections applied: analytics dashboard marked shipped, all Bark shipped_date values corrected to 2026-03-21

Lessons

  • Separating --check from the actual update prevents accidental Supabase writes — confirm state first, execute second
  • CSS custom properties set inline (--card-project-color) are the right pattern for per-instance theming — one class handles all project colors without generating per-project rules
  • Session entries and ship entries serve different purposes — retroactive ship entries don't replace session logs, they add a celebration layer that was always missing
  • Terminal line-break issues (bash treating wrapped args as separate commands) are a recurring copy-paste hazard — the fix is always one unbroken line before Enter
SAVE STATEinfrastructuretoolingfeature

Data lifecycle — voice profile, entry types, dynamic config, /wrap

Features

  • Data lifecycle plan finalized — 6-step system for keeping Save State current without manual upkeep
  • Voice profile created — reference document at references/voice-profile.md encoding writing patterns and few-shot examples for AI-generated entries
  • Entry type system added — frontmatter now supports type: session | ship | debut with backward-compatible defaulting for existing entries
  • /wrap skill built — session-closing ceremony that saves memory, drafts a voice-matched entry, and commits on approval

Infrastructure

  • config.ts deleted — PROJECT_NAMES and PROJECT_COLORS replaced with live Supabase lookups across EntryCard, EntryList, RecentActivity, and entries.ts
  • scripts/new-entry.ts updated — project IDs fetched dynamically from Supabase at runtime, no hardcoded list
  • Entry type defaulting added to getAllEntries() — missing type field resolves to session at read time

Lessons

  • Save State had no data lifecycle — entries, feature statuses, and project metadata were write-once with no mechanism to stay current
  • The staleness problem is structural, not behavioral — fixing it requires automation with a human approval gate, not just better habits
  • Voice matching requires two layers: explicit rules and few-shot examples — abstract rules alone don't produce consistent output
  • Hardcoded maps create invisible maintenance debt — the cost isn't visible until a new project silently breaks the UI

TODO

  • Continue data lifecycle plan: Steps 4–6 remain (/ship skill, visual treatment for ship/debut entries, guardrails + cleanup)
SAVE STATESHIPPEDfeaturetooling

/ship skill — shipped

Features

  • /ship skill ships — marks features as shipped in Supabase and generates voice-matched changelog entries
  • Mode A ships existing features: checks current state, confirms, updates Supabase, drafts a type: ship entry
  • Mode B debuts new projects: gathers details, upserts to Supabase, adds CSS variable, drafts a type: debut entry
  • update-feature.ts and upsert-project.ts scripts handle all Supabase writes via service role key
  • --check flag on update-feature.ts queries without mutating — the confirmation layer before any update runs

Infrastructure

  • CSS custom property --card-project-color set inline by Card.tsx — lets .card-ship and .card-debut CSS classes reference per-project color without per-project rules
  • color-mix(in srgb, ...) used for tinted backgrounds and glow shadows — no hardcoded opacity values needed

Lessons

  • Separating check from execute prevents accidental writes — the skill runs --check first, then asks, then runs the real command
  • Ship/debut visual treatment needs real entries to validate against — build the CSS first, create a test entry second
March 23, 2026
SAVE STATEbrainstormnamingbrand

Save State — brainstorm and naming

Features

  • Understory Labs brand identity locked — warm organic names with layered meaning
  • Save State vision brief completed — purpose, audience, aesthetic direction confirmed
  • Plan finalized — 6 steps, two checkpoints, weekend-scope build

Lessons

  • Naming is a design decision, not an afterthought — the name shapes how you build
  • "Save State" works on three layers: game checkpoint, session preservation, project record
  • The Understory metaphor earns its keep — light filtering through canopy, warmth in the dark
  • Building a changelog for your own projects creates a forcing function for reflective practice
SAVE STATESHIPPEDlaunchfeaturetooling

Save State — initial launch shipped

Features

  • Save State ships — Understory Labs homepage live at save-state-two.vercel.app
  • Content engine operational: markdown files in content/entries/ parse to HTML via gray-matter + unified/remark/rehype at request time
  • Changelog filterable by project with instant client-side response — no API calls, zero filter latency
  • /log skill installed globally — writes changelog entries to this repo from any project directory, making Save State infrastructure, not just a site

Lessons

  • Static export plus client-side filtering is the right architecture for a changelog — simple, fast, zero infrastructure cost
  • Building the design system first (not as a polish pass) means every component looks right from first render
  • The /log skill as a cross-project concern is the key insight — a changelog that requires opening the right project first won't get used
SAVE STATElaunchinfrastructuretooling

Save State — built and deployed

Features

  • Save State shipped — deployed static changelog at save-state-two.vercel.app
  • Project filter works client-side with instant response — no API calls, zero latency
  • Three-tier typography system implemented — JetBrains Mono titles, Share Tech Mono labels, DM Sans body
  • Entry fade-in animation with 80ms stagger on load and on filter change

Infrastructure

  • Next.js 16 static export — builds to flat HTML/CSS/JS, no server required
  • Content engine reads markdown at build time via gray-matter + unified/remark/rehype pipeline
  • Vercel deployment connected to GitHub — pushes to master trigger automatic redeploys
  • /log skill installed globally — writes entries to this repo from any project directory
  • npm run new-entry CLI scaffolder for manual entries without Claude present

Lessons

  • Static export + client-side filtering is the right architecture for a changelog — simple, fast, zero infrastructure cost
  • Baking the design system in Step 1 (not as a polish pass at the end) meant every component looked right from first render
  • The /log skill as a cross-project concern (writing to save-state from any directory) is the right model — the changelog is infrastructure, not just another feature
  • Naming the naming convention step in /new-project means future projects start with the right frame
March 22, 2026
CURRENT OSfeatureaiprojects

Shed — Home Projects feature shipped

Features

  • Shed (Home Projects) feature complete — AI-powered home project tracker inside Current OS
  • Project intake form with scope, phase, materials, and priority fields
  • Projects overlay renders active projects with status and last-updated indicators
  • AI scheduling integration — projects surface in prioritization context

Bug Fixes

  • Project intake overlay z-index conflict with calendar resolved
  • Phase label display corrected for multi-phase projects

Lessons

  • Keeping features namespaced (Shed, not just "projects") helps maintain mental boundaries in a large app
  • AI-aware data models pay off immediately — fields added for AI context during intake proved useful in first scheduling run
CURRENT OSSHIPPEDfeatureaiprojects

Shed — AI home project tracker shipped

Features

  • Shed ships — AI-powered home project tracker operational inside Current OS
  • Natural-language intake: describe a project in plain terms, Claude structures it into category, phase, materials, and priority
  • Projects overlay surfaces active work with status and last-updated indicators — no digging through lists
  • AI scheduling integration active from day one — projects surface in the prioritization engine context at intake

Lessons

  • Naming matters: "Shed" (not just "projects") creates a mental namespace that keeps feature scope clear inside a large app
  • Building for AI context at intake pays off immediately — fields added for Claude's use proved useful in the first scheduling run before the feature shipped
  • The AI-aware data model pattern: design the schema around what AI needs to be useful, not only what humans need to enter
March 21, 2026
BARKSHIPPEDfeatureaiaudio

Bark — AI feature suite shipped

Features

  • Bark ships AI feature suite — Smart Brain, Better Ears, Cooldown, and Windows service all live
  • Smart Brain routes every trigger message through Claude before acting — intent-aware instead of pattern-aware, false positives drop to near zero on busy servers
  • Better Ears adds per-trigger detection thresholds — sensitivity tunable without touching code
  • Cooldown logic prevents rapid-fire repeat plays — same clip won't fire twice in quick succession
  • NSSM service wraps the bot as a Windows background process — survives reboots, no terminal window required

Lessons

  • Async fire-and-forget for Claude calls is the right pattern in real-time audio contexts — blocking on AI response latency kills the moment
  • Graceful fallback (play the sound anyway on Claude timeout) means the AI layer enhances reliability rather than introducing fragility
  • The gap between keyword matching and contextual understanding is where the product lives — Smart Brain makes Bark feel designed rather than scripted
BARKfeatureaiaudio

Better Ears + Smart Brain — Claude-powered audio intelligence

Features

  • Claude integration added to Bark — bot now understands context around sound triggers
  • Smart Brain mode: Claude analyzes recent activity before deciding whether to play a sound
  • Better Ears: audio detection sensitivity tuned with configurable threshold
  • Cooldown logic added to prevent rapid-fire repeat plays after a trigger

Bug Fixes

  • Fixed race condition where two overlapping audio events would both trigger playback
  • Resolved NSSM service restart loop caused by unhandled promise rejection on Claude API timeout

Infrastructure

  • Claude API key stored in .env and loaded via dotenv — not hardcoded
  • NSSM service updated to use new entry point after refactor

Lessons

  • Wrapping AI calls in try/catch with graceful fallback (play sound anyway) prevents service crashes
  • Claude's response latency is noticeable in real-time audio contexts — async fire-and-forget works better than blocking
March 20, 2026
CURRENT OSSHIPPEDfeatureinfrastructure

Calendar sync — Google Calendar integration shipped

Features

  • Calendar sync ships — two-way Google Calendar integration live in Current OS
  • Events read from Google Calendar at sync time and written into the Supabase events table
  • Lifecycle pipeline picks up new events on sync — normalization and correction run automatically on ingest
  • Changes made in Current OS propagate back to Google Calendar — source of truth stays synchronized

Lessons

  • Two-way sync requires deciding which side wins on conflict — making Current OS the write layer (with Google as display) simplified the mental model considerably
  • Sync as an event trigger (not a cron job) means the pipeline runs when data arrives, not on a schedule indifferent to activity
March 8, 2026
CURRENT OSSHIPPEDfeatureaiinfrastructure

Intelligence Foundation — AI scheduling layer shipped

Features

  • Intelligence Foundation ships — AI scheduling layer operational, event lifecycle pipeline live
  • Event correction pipeline: Claude reviews raw calendar events and normalizes titles, durations, and categories before they reach the scheduling engine
  • Lifecycle tracking: events carry state (raw, normalized, scheduled, completed) — full audit trail from ingest to action
  • Normalization runs on ingest — events are clean before they hit the scheduler, not after

Lessons

  • AI as suggestion layer (not replacement) keeps corrections reversible — original data is preserved, AI output sits alongside it
  • lifecycle_state as an enum column gives clearer audit trails than boolean flags — state machines beat is_processed columns
  • Defining "done" for an event is the hardest design decision in an AI pipeline — encoding it as a state machine forced that decision early
CURRENT OSinfrastructureaiphase

Intelligence Foundation — corrections, lifecycle, normalization

Features

  • Intelligence Foundation phase complete — AI scheduling layer operational
  • Event correction pipeline: Claude reviews raw calendar events and normalizes titles, durations, and categories
  • Lifecycle tracking: events now carry state (raw, normalized, scheduled, completed)
  • Normalization runs on ingest — events are clean before they hit the scheduling engine

Infrastructure

  • Supabase schema extended with lifecycle_state and normalized_at columns
  • Correction pipeline runs as a server action triggered by calendar sync
  • Idempotent design — re-running normalization on already-normalized events is safe

Lessons

  • Treating AI output as a suggestion layer (not a replacement for original data) made corrections reversible
  • lifecycle_state as an enum column gave clearer audit trails than boolean flags
  • The hardest part of AI pipeline design is deciding what "done" looks like for a given event — encoding that as a state machine helped
March 1, 2026
CLAUDE CODE SETUPinfrastructuretooling

Power user setup — Steps 1 through 8

Features

  • Full Claude Code power user environment built across 8 steps
  • 20+ skills installed globally — /start, /plan, /brainstorm, /commit, /save, /tdd, and more
  • MCP servers configured: GitHub MCP for repo access, Context7 for live documentation
  • Hooks configured for pre/post tool events
  • CLAUDE.md updated with full work context, stack, and learning preferences

Infrastructure

  • Skills stored in ~/.claude/commands/ — available in all projects
  • toolkit.md created as single-source inventory of all capabilities
  • power-user-setup.md documents the step-by-step build history
  • opusplan model profile set as default — Opus during planning, Sonnet during execution

Lessons

  • The skill system makes Claude Code dramatically more consistent across sessions
  • CLAUDE.md is more valuable the more specific it is — vague instructions get ignored
  • Model selection matters: Opus for architecture decisions, Sonnet for building, Haiku for quick lookups
  • /save after every session is the habit that makes everything else work
CLAUDE CODE SETUPSHIPPEDinfrastructuretooling

Claude Code Setup — power user environment shipped

Features

  • Claude Code power user environment ships — 20+ global skills, MCP servers, hooks, and model profile operational
  • 20+ global skills installed: /plan, /brainstorm, /commit, /save, /tdd, /debug, /review, /start, /log, and more — common workflow patterns encoded as single commands
  • GitHub MCP + Context7 MCP configured — repo access and live documentation lookup available in any conversation
  • Hooks registered for git and file events — auto-format, lint, env protection, and toast notifications running silently in the background
  • opusplan model profile set as default — Opus during planning sessions, Sonnet during execution, Haiku for quick lookups

Lessons

  • The skill system makes Claude Code consistent across sessions — without it, every session starts from scratch
  • CLAUDE.md specificity is the multiplier: vague global instructions produce vague behavior; specific context produces specific output
  • Model selection is a workflow decision, not just a cost tradeoff — Opus for architecture, Sonnet for building, Haiku for trivia
  • /save as a closing habit is what makes everything else accumulate value over time