← HOME

PROJECT

Save State

ACTIVE

Understory Labs homepage and project portfolio

Next.jsSupabaseTypeScriptTailwind CSSVercel

ABOUT

Save State is the Understory Labs homepage — a living record of everything being built. It started as a static changelog (deployed March 2026) and is evolving into a dynamic portfolio site that serves two audiences: the builder who wants a mission control view, and the visitor who wants to understand what's being made and how.

The content engine reads markdown entry files at request time, parsing frontmatter and converting to HTML via a unified/remark/rehype pipeline. A global /log skill makes it frictionless to record a session from any project directory — one command, no context switching.

Phase 2 adds server infrastructure (Uptime Kuma for health monitoring, ClaudeVault for persistent AI memory) on a self-hosted second computer, exposing services via Cloudflare Tunnel. The boundary is clean: Supabase handles content and analytics, the server handles operations.

FEATURES

SHIPPED

  • Changelog — filterable session log

    Markdown-driven entry list with client-side project filtering and staggered animations

    MAR 2026
  • Content engine — markdown-to-HTML pipeline

    gray-matter + unified/remark/rehype pipeline reads entries at request time

    MAR 2026
  • /log skill — cross-project entry authoring

    Global Claude Code skill — writes entries to this repo from any project directory

    MAR 2026
  • Analytics dashboard — cross-project work patterns

    Recharts visualizations: entries over time, by project, tag distribution, timeline

    MAR 2026

IN PROGRESS

  • Dynamic site — Supabase-backed portfolio

    Project deep-dives, landing page, analytics dashboard powered by Supabase

PLANNED

  • Health monitoring — live service status

    Uptime Kuma integration via self-hosted server (Phase 2)

CHANGELOG

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