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

123 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Commands
```bash
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-sm``rounded-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.