interactives/CLAUDE.md
Claude 014d90a36b
CLAUDE.md: document Fizzy todo/idea mirror workflow
Agents processing todos/ideas from admin-data.json should mirror them
as cards on the author's Fizzy board. Slug → tag, progress → Review
column, staging branch link in the card description. PAT is read
from FIZZY_PAT env var; never hardcoded.
2026-04-21 11:12:45 +00:00

11 KiB
Raw Permalink Blame History

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

Commands

npm run dev          # astro dev (local dev server)
npm run build        # astro build → dist/ (static output)
npm run preview      # serve built site
npm run lint         # ESLint over .js/.jsx/.ts/.tsx/.astro
npm run lint:fix     # auto-fix lint issues
npm run format       # prettier --write .
npm run format:check # prettier --check .

No test suite is configured. Requires Node >= 22.12.0.

src/components/ui/** is ignored by ESLint (shadcn-generated code).

Architecture

Astro 6 + React 19 + Tailwind 4 + shadcn/ui. Output is static (astro.config.mjs). The site is deployed to Netlify; a single serverless function (netlify/functions/admin.mts, routed at /api/admin) backs the admin UI. The root README and components.json still reference the Hatch template this was forked from — marketing/sections components (src/components/sections/hatch-*) and the work/services MDX collections are leftover from that template.

The interactives system (core content)

Each interactive is a self-contained React component in src/components/interactives/<Name>.tsx — they freely import shadcn UI, framer-motion, katex, recharts, canvas-confetti, three/@react-three/fiber, etc. Many are 5002000+ LOC single-file components with their own state machines.

Three registrations must be kept in sync when adding or renaming an interactive:

  1. src/lib/interactives.ts — the canonical registry. Exports allInteractives (with slug, title, description, tags, themes, optional subcategory, dateAdded, hasGreenScreen, status: "published" | "draft" | "idea") and allThemes. publishedInteractives filters out drafts/ideas; this is what public pages use. getStaticPaths() here drives the /i/[slug] and /embed/[slug] routes and excludes idea entries.
  2. src/components/InteractiveRenderer.tsx — a componentMap of slug → () => import(...) that lazy-loads the component. <InteractiveRenderer client:load slug={...} /> is the single entry point used by both the full page and embed routes.
  3. src/lib/interactive-components.ts — a second near-duplicate componentMap. Both must be updated together (the duplication is historical; grep for the slug before editing).

The admin API (applyStatusChange / applyDelete in netlify/functions/admin.mts) parses src/lib/interactives.ts with string matching against slug: "..." and status: "..." as const,. Preserve that exact formatting when editing entries — otherwise admin-driven status commits and deletes will silently fail.

Routing

  • /HomepageHero (src/pages/index.astro)
  • /i/[slug] → full interactive page with navbar, breadcrumb, admin bar, share bar (InteractiveLayout)
  • /embed/[slug] → same interactive in a bare shell (EmbedLayout) for iframe embedding; generated for every non-idea interactive
  • /themes and /themes/[theme] → theme landing pages. "Discrete Math" is the one theme with subcategories (rendered via ThemeContent and /themes/discrete-math/[sub]); other themes list interactives directly and show an AddIdeaForm when the admin is logged in.
  • /admin → unlinked dashboard (<meta name="robots" content="noindex">)
  • Blog (/blog, /blog/[...slug]) reads from the blog content collection (src/content.config.ts). scripts/sync-blog.sh copies posts from a hardcoded Obsidian vault path — it only works on the author's machine.

Admin system

Client code in src/lib/admin.ts keeps state in localStorage (auth flag, notes, status overrides, ideas, todos) and mirrors every write to POST /api/admin. The admin password is hard-coded in src/lib/admin.ts and echoed in the Netlify function (override via the ADMIN_PASSWORD env var in production) — this is a personal tool, not a real auth boundary. Don't add secrets here.

The Netlify function persists state by committing to this same repo via the GitHub Contents API:

  • src/data/admin-data.json — notes, status overrides, ideas, todos. Bundled at build time and imported as a fallback when the API is unreachable (e.g. under astro dev).
  • src/lib/interactives.ts — for commitStatus and deleteInteractive, the function text-edits this source file and commits the result. Commits from the admin UI use descriptive messages like admin: set <slug> to <status>.

Requires GITHUB_TOKEN env var on Netlify; without it, writes silently no-op.

Todo & idea workflow (Fizzy kanban mirror)

The admin UI lets the author create todos (per-interactive, stored in src/data/admin-data.json under todos[slug]) and ideas (proposals for new interactives, under ideas[]). When an agent is asked to review or work on these, mirror the work to the author's Fizzy kanban board.

Board

  • URL: https://fizzy.neeldhara.cloud/7/boards/03fziwyb6y5amnepbensjq0sv/
  • Account: 7
  • Auth: Fizzy personal access token. Do not commit it to this repo. Read it from the FIZZY_PAT env var (or whatever secret store the author provides). If the token is unavailable, skip the mirror step and flag it to the user — never hardcode a fallback.

Card conventions

  • One card per todo (title = the todo text) or per idea (title = idea title).
  • The interactive slug (e.g. parity-bits-game, three-bank-accounts) becomes a tag on the card, grouping cards by interactive. For ideas not yet tied to a slug, use the idea's working title as a tag.
  • Column placement:
    • Todo the agent has made progress on → Review column.
    • Idea the agent has implemented → Review column, with an implementation overview in the card body (what was built, files touched, trade-offs).
    • Todos/ideas without progress → whatever the board's inbox/backlog column is. Confirm with the user if unclear.
  • Card description must include a review link — the GitHub URL for the staging branch (compare view vs. main) or, if a PR exists, the PR URL.

Commit flow

  • Progress on todos/ideas goes to a staging branch, not directly to main and not to a per-session claude/* branch (unless the user explicitly says so).
  • Prefer a stable branch name (e.g. staging) so card links stay valid as more work lands, rather than a new branch per todo.

Green screen mode

Interactives marked hasGreenScreen: true can be wrapped in GreenScreenWrapper to render on a pure #00ff00 background with forced black/white foreground colors (for chroma-keying into videos). The wrapper injects !important CSS overrides; be aware when debugging styling inside one.

Aliases and conventions

TS path aliases from tsconfig.json: @/*src/*, plus @components/*, @layouts/*, @lib/*. shadcn aliases in components.json point components to @/components, utils to @/lib/utils (the cn() helper). Tailwind 4 is wired via @tailwindcss/vite (no tailwind.config.js); design tokens live in src/styles/global.css. Icon library is lucide-react.

ESLint enforces simple-import-sort — imports and exports get auto-sorted on lint:fix.

Design & aesthetics

The site has a distinctive editorial-meets-playful feel: serif display type over restrained neutrals, one warm red-orange accent, and generous whitespace. Interactives are the payoff — they should feel polished and a little delightful, not enterprise-CRUD.

Reach for shadcn before hand-rolling. Card / CardHeader / CardTitle / CardContent is the canonical container for an interactive's sections. Button, Badge, Alert, Tabs, Dialog, Slider, Switch, Tooltip, Input, Label are already themed and wired up — use them. Edit src/components/ui/* only with deliberate reason (ESLint ignores this dir because it's shadcn-generated).

Always use design tokens, never raw hex. Colors live as CSS variables in src/styles/global.css and are exposed as Tailwind utilities:

  • Surfaces: bg-background, bg-card, bg-muted, bg-accent
  • Text: text-foreground, text-muted-foreground
  • Brand accent: bg-primary / text-primary / text-primary-foreground — use sparingly, for true calls-to-action
  • Borders: border-border
  • Status: text-destructive, text-success, chart colors chart-1..chart-5

Colors are authored in OKLCH. If you need a shade that isn't in the palette, add a token rather than inlining.

Dark mode is mandatory. The .dark class on <html> flips the tokens. Any literal tailwind color (bg-green-100, text-amber-700, etc.) must come with a dark: counterpart — see ThreeBankAccounts.tsx's RANK_COLORS for the pattern. Better yet, lean on design tokens so dark mode is automatic.

Typography. Castoro (serif) for h1/h2 via .font-display; Imprima/Geist (sans) for body and h3+. This pairing is load-bearing for the editorial feel — don't swap h1 to sans on a whim. The container utility caps at 1200px; container-sm caps at 960px for denser reading layouts.

Radii. Base --radius is 0.625rem, exposed as rounded-smrounded-4xl on the Tailwind scale. Cards and panels are typically rounded-xl; pill buttons/badges are rounded-full; small chips are rounded-md.

Motion and delight. framer-motion for layout/presence animations, canvas-confetti for win states and discovery moments, katex for math (katex/dist/katex.min.css must be imported where used). prefers-reduced-motion is honored globally in global.css — you don't need to guard individual transitions, but don't gate critical state changes behind animation either.

Interactive layout patterns. Looking across the folder, published interactives tend to:

  • Open with a short framing Alert or intro card explaining the premise
  • Put the play area center stage, controls below or to the side
  • Use Tabs to separate modes (e.g. Play / Explore / Solution in ThreeBankAccounts)
  • Use Badge for status, tags, and hint chips
  • Reset with a RotateCcw icon button; celebrate wins with confetti + a toast (sonner is wired up)
  • Stay responsive — test at mobile widths, since these embed into blog posts and videos

Taste defaults. Prefer fewer, cleaner controls over exhaustive settings. If a new feature needs an options panel, that's a signal to reconsider the UX. When in doubt, match the visual weight and density of an existing, well-regarded interactive in the same theme rather than inventing a new idiom.

Build gotchas

astro.config.mjs sanitizes Rollup chunk filenames ([^\w./-]_) and pins esbuild to es2022 / utf8 — these work around Netlify's esbuild choking on ! and ~ in chunk names. Don't revert without retesting on Netlify. netlify.toml also disables Netlify's own JS/CSS bundling so Vite's output is shipped as-is.