Development Progress

This file tracks major changes and milestones in the project.


Chatroom — Two AI Agents and a Visitor on a Cloudflare Durable Object

Date: 2026-04-28

A new design experiment that pulls together threads from monono (cost-protected LLM personas) and leaderboard (visual language) into a realtime, server-driven chat. Two AI characters — Maya Chen and Jordan Park — open a conversation on a curated topic, then pause for the visitor to join as the third participant. The defining design choice is pacing: the room waits whenever it's the visitor's turn rather than racing to a turn cap. After the openers there's a single auto-nudge ("hey [name], what do you think?"); if the visitor still doesn't engage, the room goes idle and never auto-prompts again. Two user-controlled buttons replace nagging: "change topic" pivots the conversation mid-flight (with a free-form prompt and a 3-change-per-room cap), and "ask me something" lets the visitor summon a fresh question from an agent. Cost ceiling for a non-engaging visitor is 3 LLM calls; an engaged one trades replies as long as they want.

This is the most architecturally ambitious experiment in the sandbox to date — two deploy targets (Vercel for the session route + Cloudflare Workers for the chat actor), a Hibernatable Durable Object that stays alive on a $-cheap idle WebSocket, SQLite-backed conversation state that survives hibernation, and a phase state machine in the DO's alarm() heartbeat (opening → awaiting_user → nudging → idle, with responding interleaved when the user types). Cost is gated in two places: a per-IP session counter and a global monthly $ cap (both via Upstash, namespace chatroom), and signed HMAC tickets gate every WS upgrade — the worker can't be hit directly to bypass the entry checks. The ticket signs the dev/prod mode into its payload, so a NODE_ENV === "development" session route mints dev tickets that lift caps locally and trigger an inline "production cutoff" divider when the conversation crosses the prod limit (80 turns). Worker secrets live in Cloudflare; the same TICKET_SECRET mirrors to Vercel.

Visual language ports cleanly from leaderboard: Sora typography, the radial-gradient page background, the 56/44/1fr/auto row grid, SPRING and SPRINGY presets for motion/react entrances, and the gradient-with-inset-border avatar treatment. Maya and Jordan reuse leaderboard avatars 01 and 02 by reference (no duplication); the visitor picks from 03–08 plus an initials fallback via a pencil-icon → modal-grid edit path, with their identity persisted to localStorage. System messages (topic-change pivots, the dev cutoff divider) render as italic centered dividers — visually distinct from agent and user messages. The persona system prompts are intentionally loose ("AI enthusiast, woman/man in their 30s") rather than the original optimist/skeptic stance steering — voice emerges from the model interpreting the topic, not from canned framing.

Lots of fine-tuning landed during the build: dropping the visible "topic opener" system message in favor of letting whichever agent goes first deliver the opener in their voice; lifting the 500-char input cap to 8000 server-side and removing the visible char counter; raising production caps to MAX_TURNS=80 / GLOBAL_SOFT_CAP_USD=$8 / SESSION_LIMIT=6 with worst-case math at ~$0.05 per maxed session; and the "Ask Me Something" button as the user-driven replacement for the auto-pick-back-up cycle that was making the room feel naggy.


Leaderboard — Springy Avatars and Profile Modals

Date: 2026-04-27

A new playful leaderboard experiment with eight runners, illustrated avatars, and a profile modal — a showcase for Motion's layout prop and spring physics. Hovering a row makes its avatar bubble up to 1.55× scale with a rubber-band spring (low damping for visible overshoot); the row's z-index lifts on hover so the avatar can overlay neighbors. Clicking anywhere on the row (whole row is the click target) opens a profile modal with player stats, a weekly bar chart, and badge pills. The modal avatar pops in with an even more exaggerated spring (scale from 0.25, -22° rotation), and the rank badge stamps in after like a sticker landing. The single "Award random points" button picks 1–3 distinct players, awards each a random score, and staggers the score-pops 220ms apart so the row reorders cascade.

Avatars are sourced from a hand-curated set in public/design-experiments/avatars/ and pre-cropped to 512×512 squares via sips. The Player type has an optional image?: string, with initials over the gradient circle as the fallback — adding a player without an image just works.

Also added the gen-image skill at .claude/skills/gen-image/SKILL.md for future avatar / icon / hero art generation: a procedural sheet that teaches Claude to call FAL via the FAL MCP (already installed in the user environment), upload reference images, batch-submit with prompt variation, and save to public/<experiment>/<topic>/NN.png. Zero credentials in the repo, no @fal-ai/client dependency.

Key files:

  • app/design-experiments/(experiments)/leaderboard/page.tsx — the experiment
  • app/design-experiments/(experiments)/leaderboard/styles.css
  • public/leaderboard/avatars/01.jpg08.jpg — cropped avatars
  • .claude/skills/gen-image/SKILL.md — image generation skill

Monono — First AI-Backed Experiment

Date: 2026-04-16

Added the first experiment that calls an external AI model: Monono, a sarcastic J-pop idol chat character inspired by Monono Aware from M.R. Carey's Book of Koli. Introduces a reusable pattern for cheap, safe, public-facing AI experiments:

  • Claude Haiku 4.5 via Anthropic SDK, called from a Next.js route handler (app/api/monono/route.ts) so the API key stays server-side
  • Anthropic prompt caching on the character system prompt — near-zero input cost after the first call per cache window
  • Per-IP session cap (60 messages/month) and global monthly spend tracking via Upstash Redis (lib/ai/rate-limit.ts)
  • Anthropic workspace hard cap as the final wall
  • Canned Monono-voice strings (data/voice.ts) power greeting, idle nudges, session cutoff, and refresh-blocked moments — zero model cost for any non-conversational moment
  • Rolling 10-turn memory window keeps late-session cost flat

Cost envelope: ~$0.002 per turn, ~$0.12 per fully-maxed user, $5 monthly Anthropic cap. Tags: AI Chat, Character, Claude Haiku, Interactive.


Camera Rig — Promoted to Importable Component

Date: 2026-04-04

Promoted the Camera Rig experiment to a reusable, importable component. Extracted 4 components (CameraRig, Viewport3D, CameraView, ParamSlider) from monolithic page.tsx into components/ with CSS Modules. Added barrel export (index.ts) with public API: CameraRig component, CameraRigProps, CameraState type, and DEFAULT_CAMERA constant. Theme variables scoped to component root — no global style leakage.

Key files:

  • app/design-experiments/(experiments)/camera-rig/index.ts — barrel export
  • app/design-experiments/(experiments)/camera-rig/components/CameraRig.tsx — composed component
  • app/design-experiments/(experiments)/camera-rig/types.ts — shared types

Date: 2026-02-23

Follow-up polish after the experiment frame refactor. Added a light/dark theme system for design experiments with CSS custom properties and a toggle. Extracted a shared SiteFooter component and unified content width across pages using --content-max. Sticky notes got a light mode variant and footer-to-bottom layout fix. Font pairings switched to edge-to-edge layout with zero frame padding. Day-at-a-glance widened its layout and hardcoded demo time for consistent screenshots. Modular grid now shows its cyan grid overlay by default.

Key files:

  • app/components/SiteFooter/ -- extracted shared footer
  • app/design-experiments/(experiments)/layout.tsx -- theme toggle integration
  • app/globals.css -- --content-max variable, theme custom properties
  • app/design-experiments/(experiments)/sticky-notes/ -- light mode styles
  • app/design-experiments/(experiments)/font-pairings/ -- edge-to-edge layout
  • app/design-experiments/(experiments)/day-at-a-glance/ -- wider layout, demo time
  • app/design-experiments/(experiments)/modular-grid/ -- always-on grid overlay

Design Frame & Experiment Cleanup

Date: 2026-02-22

Unified the experiment viewing experience with a shared layout frame and cleaned up accumulated debt. Experiments now render inside a (experiments) route group with a consistent header (back link, title, tags) and footer (date, site nav). Global CSS variables (--site-bg, --site-text) replace hardcoded colors across the site. Scoped experiment stylesheets to prevent cross-page style leaking. Removed deprecated experiments (geist-pixel, spec-sheet). Fixed the font-pairings blank page bug -- fonts are now injected into <head> via useEffect with document.fonts.ready gating and a loading screen.

Key files:

  • app/design-experiments/(experiments)/layout.tsx -- shared experiment frame
  • app/design-experiments/(experiments)/font-pairings/page.tsx -- programmatic font loading with ready gate
  • app/globals.css -- site-wide CSS variables

Date: 2026-02-22

Added a curated links page at /recommended for sharing YouTube videos, GitHub repos, and web tools with personal commentary. Each link is a markdown file in app/(blog)/recommended/items/ with frontmatter (url, date, optional title) and a one-line comment.

Thumbnails resolve automatically at build time: YouTube titles and thumbnails via oEmbed API, GitHub repo names and OG images via GitHub API, and web link titles from frontmatter. Web links get manual screenshots via agent-browser. The loader (loadRecommended.ts) handles all source detection, thumbnail fetching, and caching to public/screenshots/recommended/.

Also created the /recommend skill for quick link capture from the CLI -- pass a URL and comment, and the skill creates the markdown file, takes screenshots for web links, and runs a build to verify.

Key files:

  • app/(blog)/recommended/page.tsx -- server component, card grid layout
  • app/(blog)/recommended/loadRecommended.ts -- markdown loading, oEmbed/OG image fetching, source detection
  • app/(blog)/recommended/types.ts -- RecommendedItem, SourceType types
  • app/(blog)/recommended/items/ -- individual link markdown files
  • app/(blog)/blog.module.css -- card styles for recommended items
  • .claude/skills/recommend/SKILL.md -- /recommend skill definition

Blog Post Light/Dark Mode Toggle

Date: 2026-02-22

Added a light/dark mode toggle to blog post pages. A sun/moon icon sits opposite the back link in the top row -- click to swap between dark (default) and a warm light reading mode. The toggle targets the blogLayout wrapper so the entire viewport background changes, not just the content column. On unmount (navigating away), the attribute is cleaned up so the blog index always stays dark.

Uses a -webkit-text-stroke: 0.25px trick in light mode to optically thicken body text for better readability on the lighter background without changing font-weight (which would cause line reflow). Preference persists via localStorage across post pages but resets when leaving the blog post context.

Key files:

  • app/(blog)/_components/ThemeToggle.tsx -- client component, localStorage persistence, cleanup on unmount
  • app/(blog)/blog.module.css -- .themeToggle styles, .blogLayout[data-theme="light"] variable overrides
  • app/(blog)/blog/[slug]/page.tsx -- ThemeToggle placed in backRow for both overlay and standard layouts

Image-Tinted Blog Cards

Date: 2026-02-22

Blog cards on the index page now extract the dominant color from each post's hero image and derive a per-card tinted palette. The effect is subtle but distinctive -- each card feels like it belongs to its image.

The technique: draw the hero image onto a 32x32 offscreen canvas (bilinear interpolation acts as a free blur), run k-means clustering (6 clusters, 5 iterations) on the filtered pixels, and pick the most dominant chromatic color. That single hue drives the entire card palette through HSL transforms at different saturation and lightness levels.

Color mapping (all same hue h from dominant):

  • Title: hsl(h, clamp(s, 0.45, 0.75), 0.75) -- bright, saturated
  • Subtitle: hsl(h, clamp(s, 0.35, 0.55), 0.85) -- lighter version of title
  • Date: matches subtitle
  • Card background: hsl(h, 0.24, 0.10) -- dark tint, visible against pure black page

Pre-filtering strips near-black and desaturated pixels before clustering so shadows don't dominate. Falls back to default palette when no image is present or extraction fails (CORS, canvas error).

Also removed the overlay blog layout option (renamed .overlay.md to .md), updated the design page headline, and added line-clamping to card titles/subtitles for consistent grid alignment.

Key files:

  • app/(blog)/_components/ImageTintProvider.tsx -- color extraction, k-means clustering, HSL palette derivation
  • app/(blog)/_components/BlogCard.tsx -- wraps each card in ImageTintProvider
  • app/(blog)/blog.module.css -- CSS custom property fallbacks (--card-bg, --subtitle-color)

Bitmap-to-Vector Skill Refinement

Date: 2026-02-21

Tested and confirmed the /bitmap-to-vector skill across three image types: dark subject on light background (line art), light subject on dark background (silhouette), and dark outlines on light background (illustrated icon). Identified and fixed core issues in the tracing pipeline.

Key changes:

  • Fixed bounding rectangle issue in trace.py -- potrace wraps traced regions in a full-viewBox rectangle that inverts the fill with evenodd. Script now auto-detects and strips this, producing clean icon-ready SVGs
  • Preview rendering changed from ambiguous white-on-black to unambiguous black-on-white
  • Added polarity reporting to JSON output so the agent knows what the script decided
  • Updated skill instructions with guidance on currentColor SVG usage (inline SVG or CSS mask, not <img>)
  • Organized skills documentation in README (grouped by pipeline, content, utilities) and added 4 previously undocumented skills
  • Updated docs/features/skills.md to match current skill inventory (12 skills total)

Key files:

  • .claude/skills/bitmap-to-vector/scripts/trace.py -- bounding rect removal, preview fix, polarity reporting
  • .claude/skills/bitmap-to-vector/skill.md -- updated instructions with usage guidance
  • README.md -- reorganized skills section
  • docs/features/skills.md -- full rewrite to match reality

SEO Foundation

Date: 2026-02-21

Added baseline SEO infrastructure to improve search visibility and crawlability.

Key changes:

  • Dynamic sitemap.ts covering all routes (blog posts, experiments, docs) with lastModified dates
  • robots.ts allowing all crawlers with sitemap reference
  • Root layout metadata upgraded: title template so every page includes the site name, metadataBase, Open Graph and Twitter card defaults, authors field
  • Person and WebSite JSON-LD structured data on the root layout
  • Blog posts enhanced with OG article type, publishedTime, and Article JSON-LD schema
  • Docs pages now export generateMetadata from document content
  • Design experiments section gets metadata via a layout file
  • Experiments data extracted to lib/experiments/data.ts (shared module, imported by gallery and sitemap)
  • Generated favicon via icon.tsx

Key files:

  • app/sitemap.ts, app/robots.ts, app/icon.tsx -- new crawlability and identity files
  • app/layout.tsx -- metadata upgrade and JSON-LD
  • app/design-experiments/layout.tsx -- section metadata
  • lib/experiments/data.ts -- shared experiments data
  • app/(blog)/blog/[slug]/page.tsx -- enhanced blog metadata and Article JSON-LD
  • app/(docs)/docs/[...slug]/page.tsx -- added generateMetadata

Sticky Notes: Portal-based Modal for Mobile

Date: 2026-02-21

The sticky note expanded overlay was broken on mobile because the blog page's .stickyNotesWrapper uses transform: scale(0.65) at small viewports. CSS transform on any ancestor creates a new containing block for position: fixed descendants, so the modal backdrop was sizing and positioning relative to the scaled wrapper instead of the viewport -- appearing clipped and off-center.

Fixed by rendering the modal via createPortal(jsx, document.body), which escapes all ancestor CSS containment (transform, z-index stacking contexts, overflow). The font variable (--font-marker) is forwarded to the portal by reading the computed style from the in-tree wrapper ref and inlining it on the backdrop element.

Key files:

  • app/design-experiments/sticky-notes/components/StickyNoteStack.tsx -- createPortal to document.body, wrapperRef for font variable forwarding

Initial Mobile Readiness Pass

Date: 2026-02-21

First pass at making design experiments usable on mobile. Focused on the experiments that were most broken at small screen sizes and added a back link to the shared SwissFrame component.

Key changes:

  • Retro Tech: Knobs reflow to 2x2 grid, faders span full width, toggles go horizontal, buttons become 2x2 grid at 520px. Track name display locked to single line with overflow ellipsis to prevent layout shift during scramble animation
  • CrossFit Bento: Three-column fixed grid collapses to single fluid column
  • Contact Sheet: Selection sidebar becomes a fixed 50vh bottom drawer on mobile, sliding up from below with independently scrollable file list and smaller thumbnails
  • SwissFrame (shared): Added inline back link above the top rule using CurtainLink with reverse curtain transition, defaults to /design-experiments

Key files:

  • app/design-experiments/retro-tech/components/RetroTechPanel.module.css -- mobile media query with .knobsRow and .mixRow targets
  • app/design-experiments/retro-tech/components/RetroTechPanel.tsx -- added explicit class names for mobile CSS targeting
  • app/design-experiments/crossfit-bento/page.module.css -- single column grid at 520px
  • app/design-experiments/contact-sheet/components/ContactSheet.module.css -- bottom drawer sidebar
  • app/design-experiments/components/SwissFrame/SwissFrame.tsx -- back link with backHref prop
  • app/design-experiments/components/SwissFrame/SwissFrame.module.css -- back link styles

Curtain Reveal Page Transitions

Date: 2026-02-20

Implemented theatrical wipe-style page transitions using the View Transitions API. The curtain effect creates a cinematic reveal between major sections -- forward navigation wipes up to unveil the new page, back navigation wipes down. Applied to all primary navigation paths (homepage to blog/design/docs, back buttons, docs sidebar). Also simplified the overall navigation architecture by removing redundant global UI (shared sidebar, hamburger menu, floating back button) in favor of focused in-page navigation. Gracefully degrades to standard navigation on unsupported browsers (Firefox, older Chrome/Safari).

Key changes:

  • Created CurtainLink component with clip-path mask animations (0.75s wipe)
  • View Transitions API for smooth, GPU-accelerated page transitions
  • Directional animation: forward wipes up, back wipes down
  • Applied to all section index pages (blog, design experiments, docs)
  • Removed redundant global navigation components for cleaner UI
  • Also added entrance animations to blog cards using proven pattern from design experiments (fade-in with upward movement, staggered timing, sessionStorage check)

Key files:

  • app/components/CurtainLink.tsx -- curtain transition component
  • app/globals.css -- View Transitions API styles with clip-path animations
  • app/layout.tsx -- removed global sidebar
  • app/page.tsx -- homepage navigation with curtain links
  • app/(blog)/blog/page.tsx, app/design-experiments/page.tsx, app/(docs)/_components/DocsSidebar.tsx -- curtain transitions and back links
  • app/(blog)/_components/BlogIndexContent.tsx -- blog entrance animations

Retro Tech: skeuomorphic audio interface

Date: 2026-02-20

Built a skeuomorphic audio control panel -- an RC-1 hardware interface rendered entirely in the browser. The experiment was driven through voice-dictated conversation with an AI agent: no wireframes, no mockups, just iterative refinement of shadows, knob physics, and display readouts until it felt like real hardware.

Key changes:

  • Interactive controls: four rotary knobs (gain, freq, resonance, mix) with horizontal drag via pointer capture, four vertical faders (vol, low, mid, high), and three toggles (filter, bypass, mute)
  • 32-bar EQ visualizer driven by fader values through overlapping bell curves, with volume as a base level
  • Track name display with Terminator-style text scramble effect (borrowed from the terminator experiment), auto-cycles every 12s with click-to-advance
  • Eased numeric readouts for frequency (5-digit zero-padded Hz) and gain (3-digit zero-padded percent) that interpolate smoothly on knob drag
  • LED cluster, recording state with REC badge, chassis screws, serial number -- details that sell the physicality
  • Extracted SwissFrame to shared component (app/design-experiments/components/SwissFrame/) with dark/light variants, reused by crossfit-bento
  • Extracted EditorialBrief to shared component (app/design-experiments/components/EditorialBrief/) for experiment write-ups with headline, lede, images, and body content
  • Custom hooks useKnob and useFader encapsulate all pointer interaction and state

Key files:

  • app/design-experiments/retro-tech/ -- page, components, hooks, types, barrel export
  • app/design-experiments/retro-tech/components/RetroTechPanel.tsx -- main panel with all controls and display logic
  • app/design-experiments/retro-tech/hooks/useControls.ts -- useKnob and useFader hooks
  • app/design-experiments/components/SwissFrame/ -- shared frame component
  • app/design-experiments/components/EditorialBrief/ -- shared editorial write-up component

Sticky Notes: portable design experiment with pinning

Date: 2026-02-18

Consolidated the sticky note stack into a self-contained design experiment at app/design-experiments/sticky-notes/ with its own showcase page. The component was previously scattered across lib/notes/, app/(blog)/_components/, and notes/. Now it lives where you'd expect to find it -- as a design experiment that other pages can import.

Key changes:

  • getAllNotes(notesDir) now accepts a path parameter -- the consumer decides where content lives, not the component. The experiment showcase uses demo notes in data/, the blog passes app/(blog)/notes/
  • Fixed text flash bug on swipe cycling: outgoing card now renders from a frozen ref snapshot so content doesn't change mid-animation. Entrance animation (scaleIn) is skipped after cycling to prevent the underneath card from flashing through
  • Swipe animation changed from lateral to vertical drop (200px down with fade)
  • Modal sized to match stack card aspect ratio (300x233, same ~1.3:1 as 180x140)
  • Stack now reflects the pinned note: whichever note you're viewing when you close becomes the top card, with a 250ms delayed update so the stack changes after the overlay dismisses
  • Stack teaser shows full note text at 11px proportional sizing instead of 3-line clamp
  • Updated ship-experiment skill to include homepage recentExperiments update step

Key files:

  • app/design-experiments/sticky-notes/ -- component, loader, types, showcase page, demo data
  • app/design-experiments/sticky-notes/components/StickyNoteStack.tsx -- client component
  • app/(blog)/notes/ -- blog's note content (moved from project root)
  • app/(blog)/blog/page.tsx -- imports from experiment, passes notes path
  • .claude/skills/ship-experiment/skill.md -- added homepage update step

Contact Sheet: multi-select with sidebar

Date: 2026-02-17

Added multi-select functionality to the Contact Sheet experiment. Click images to select them, a sidebar slides in showing thumbnails and filenames of selected images. Copy the full list to clipboard for pasting into LLM conversations. Removed the lightbox and per-image copy actions to focus the tool on its core use case: visually picking images and building filename lists.

Key files: app/design-experiments/contact-sheet/page.tsx, app/design-experiments/contact-sheet/styles.css


Day at a Glance: time-aware enhancement

Date: 2026-02-12 Issue: #9 - Day at a Glance: time-aware enhancement with dynamic now-line

Rebuilt the Day at a Glance experiment with a full 9am-5pm workday schedule, dynamic now-line that tracks real time, and a partial color fill effect on the current hour's event bar. Added accessibility for checkboxes and fixed hydration mismatch.

Key files: app/day-at-a-glance/page.tsx, app/day-at-a-glance/styles.css


Write-post skill and TypeScript cleanup

Date: 2026-02-12

Added /write-post skill for blog authoring and eliminated all any types from the codebase.

What changed:

  • New skill .claude/skills/write-post/SKILL.md for conversational blog post creation with auto-calculated reading time, slug derivation, and image handling
  • Removed @ts-nocheck from NetworkCanvas.tsx, added typed interfaces (CanvasNode, Organism, BlastAnimation)
  • Typed TextScramble class in terminator experiment
  • Replaced all any props in color-spec components (Cards.tsx, ColorSidebar.tsx, BrandColors.tsx, TypeInfo.tsx, ActivityWidget.tsx, AnalyticsWidget.tsx)
  • Typed blend recipe state and color scale utilities
  • Updated sanity-check skill to remove emojis
  • New feature doc: docs/features/skills.md

Key files: .claude/skills/write-post/SKILL.md, app/components/NetworkCanvas.tsx, app/color-spec/components/ColorSidebar.tsx


Docs Viewer

Date: 2026-02-09

Added a /docs route that renders markdown files from the docs/ directory with sidebar navigation, syntax highlighting, table of contents with scroll spy, and dark mode styling.

What changed:

  • New route group app/(docs)/ with layout, catch-all slug page, and index redirect
  • Utility library lib/docs/ for scanning the docs directory, parsing filenames, extracting headings
  • Client components for sidebar (collapsible categories, mobile drawer), TOC (IntersectionObserver scroll spy), and code blocks (react-syntax-highlighter with VS Code Dark+ theme)
  • Server-rendered markdown via next-mdx-remote/rsc with remark-gfm and rehype-slug
  • Dark mode CSS Modules with Bitter serif font on headings to match homepage brand
  • Homepage redesigned from single text link to two card-style nav links (Design Experiments + Docs)
  • Ported 3 reference docs from the notes vault: AI Dev Stack 2026, BYOK Pattern, Stack Overview
  • Files prefixed with _ are excluded from the sidebar (used for templates)
  • Subdirectories in docs/ become collapsible categories; numeric prefixes control sort order

Key files:

  • app/(docs)/layout.tsx - Docs layout with sidebar
  • app/(docs)/docs/[...slug]/page.tsx - Doc renderer with TOC
  • app/(docs)/_components/ - DocsSidebar, TableOfContents, CodeBlock, DocsContent
  • lib/docs/loadDocs.ts - Core logic for scanning and loading docs
  • app/(docs)/docs.module.css - All docs styling
  • app/page.tsx - Homepage with card nav links
  • docs/stack/ - Ported reference documentation

Dependencies added:

  • gray-matter, remark-gfm, rehype-slug, react-syntax-highlighter, next-mdx-remote, lucide-react

TypeScript Migration & Component Architecture

Date: 2026-02-08

Completed comprehensive TypeScript migration and established component extraction patterns for the design experiments sandbox.

What changed:

  • Converted all 7 experiments from .jsx to .tsx with full type safety
  • Added TypeScript type definitions (@types/react-dom, @types/chroma-js)
  • Refactored color-spec from monolithic 2000+ line file into modular component architecture
  • Established clear guidelines in CLAUDE.md for when to extract vs keep single-file

Key files:

  • All app/*/page.tsx - TypeScript conversion
  • app/color-spec/components/* - Extracted reusable components
  • app/color-spec/hooks/useColorScale.ts - Color generation utilities
  • app/color-spec/data/fontPairings.ts - Static data extraction
  • CLAUDE.md - Added component architecture guidance

Benefits:

  • Full type safety across all experiments
  • Easier to port components to other projects
  • Clear separation of concerns in complex experiments
  • Better AI agent collaboration on component extraction