# Claude Code Instructions for apps/web ## CRITICAL: Always Check Imports Before Using React Hooks **MANDATORY: Before using ANY React hook or function, verify it's imported.** **The Process (EVERY TIME):** 1. Read the imports section at the top of the file (lines 1-20) 2. Check if the hook/function you need is already imported 3. If missing, add it to the import statement IN THE SAME EDIT as your code 4. Do NOT write code that uses a hook without checking imports first **Common mistakes:** - ❌ Using `useCallback` without checking if it's imported - ❌ Using `useMemo` without checking if it's imported - ❌ Using `useRef` without checking if it's imported - ✅ Read imports → verify → add if needed → write code **Why this matters:** - Missing imports break the app immediately - User has to reload and loses state - Wastes time debugging trivial import errors - Shows lack of attention to detail **If you forget this, you will:** - Break the user's development flow - Lose reproduction state for bugs being debugged - Annoy the user with preventable errors ## CRITICAL: When Agreeing on a Technical Approach, Actually Implement It Everywhere **This is a documented failure pattern. Do not repeat it.** When you agree with the user on a technical approach (e.g., "use getBBox() for bounding box calculation"): 1. **Identify ALL code paths affected** - not just the obvious one 2. **Explicitly verify each code path uses the agreed approach** before saying it's done 3. **When fixes don't work, FIRST verify the agreed approach was actually implemented everywhere** - don't add patches on top of a broken foundation **The failure pattern:** - User and Claude agree: "Part 1 and Part 2 should both use method X" - Claude implements method X for Part 2 (the obvious case) - Claude leaves Part 1 using the old method Y - User reports Part 1 is broken - Claude makes superficial fixes (adjust padding, tweak parameters) instead of realizing Part 1 never used method X - Cycle repeats until user is frustrated **What to do instead:** - Before implementing: "Part 1 will use [exact method], Part 2 will use [exact method]" - After implementing: Verify BOTH actually use the agreed method - When debugging: First question should be "did I actually implement what we agreed on everywhere?" **Why this matters:** - Users cannot verify every line of code you write - They trust that when you agree to do something, you actually do it - Superficial fixes waste everyone's time when the root cause is incomplete implementation ## CRITICAL: Documentation Graph Requirement **ALL documentation must be reachable from the main README via a linked path.** When creating new documentation: 1. ✅ Create the document in the appropriate directory 2. ✅ Link it from a parent README that eventually links to root README 3. ✅ Verify the chain: Root README → Area README → Your Document **Why this matters:** - Documentation that isn't linked is invisible and will be forgotten - New developers start at root README and follow links - Ensures documentation stays discoverable and maintained **Example chain:** ``` README.md (root) → apps/web/src/app/create/worksheets/README.md → PROBLEM_GENERATION_ARCHITECTURE.md → USER_WARNING_IMPROVEMENTS.md → .claude/PROBLEM_GENERATION.md ``` **Invalid:** Creating `/docs/some-feature.md` without linking from anywhere ❌ **Valid:** Creating `/docs/some-feature.md` AND linking from root README ✅ ## CRITICAL: Never Directly Modify Database Schema **NEVER modify the database schema directly (e.g., via sqlite3, manual SQL, or direct ALTER TABLE commands).** All database schema changes MUST go through the Drizzle migration system: 1. **Modify the schema file** in `src/db/schema/` 2. **Generate a migration** using `npx drizzle-kit generate --custom` 3. **Edit the generated SQL file** with the actual migration statements 4. **Run the migration** using `npm run db:migrate` 5. **Commit both** the schema change AND the migration file **Why this matters:** - The migration system tracks which changes have been applied - Production runs migrations on startup to sync schema with code - If you modify the DB directly, the migration system doesn't know about it - When you later create a migration for the "same" change, it becomes a no-op locally but fails on production **The failure pattern (December 2025):** 1. During development, columns were added directly to local DB (bypassing migrations) 2. Migration 0043 was created but as `SELECT 1;` (no-op) because "columns already exist" 3. Production ran 0043 (no-op), so it never got the columns 4. Production crashed with "no such column: is_paused" 5. Required emergency migration 0044 to fix **The correct pattern:** ```bash # 1. Modify schema file # 2. Generate migration npx drizzle-kit generate --custom # 3. Edit the generated SQL file with actual ALTER TABLE statements # 4. Test locally npm run db:migrate # 5. Commit both schema and migration git add src/db/schema/ drizzle/ git commit -m "feat: add new column" ``` **Never do this:** ```bash # ❌ WRONG - Direct database modification sqlite3 data/sqlite.db "ALTER TABLE session_plans ADD COLUMN is_paused INTEGER;" # ❌ WRONG - Then creating a no-op migration because "column exists" # drizzle/0043.sql: # SELECT 1; -- This does nothing on production! ``` ## CRITICAL: Never Modify Migration Files After Deployment **NEVER modify a migration file after it has been deployed to production.** Drizzle tracks migrations by name/tag, not by content. Once a migration is recorded in `__drizzle_migrations`, it will NEVER be re-run, even if the file content changes. ### The Failure Pattern (December 2025) 1. Migration 0047 was created and deployed (possibly as a stub or incomplete) 2. Production recorded it in `__drizzle_migrations` as "applied" 3. Developer modified 0047.sql locally with the actual CREATE TABLE statement 4. New deployment saw 0047 was already "applied" → skipped it 5. Production crashed: `SqliteError: no such column: "entry_prompt_expiry_minutes"` 6. Required emergency migration to fix **This exact pattern has caused THREE production outages:** - Migration 0043 (December 2025): `is_paused` column missing - Migration 0047 (December 2025): `entry_prompts` table missing - Migration 0048 (December 2025): `entry_prompt_expiry_minutes` column missing ### Why This Happens ``` Timeline: 1. Create migration file (empty or stub) → deployed → recorded as "applied" 2. Modify migration file with real SQL → deployed → SKIPPED (already "applied") 3. Production crashes → missing tables/columns ``` Drizzle's migrator checks: "Is migration 0047 in `__drizzle_migrations`?" → Yes → Skip it. It does NOT check: "Has the content of 0047.sql changed?" ### The Correct Pattern **Before committing a migration, ensure it contains the FINAL SQL:** ```bash # 1. Generate migration npx drizzle-kit generate --custom # 2. IMMEDIATELY edit the generated SQL with the actual statements # DO NOT commit an empty/stub migration! # 3. Run locally to verify npm run db:migrate # 4. Only THEN commit git add drizzle/ git commit -m "feat: add entry_prompts table" ``` ### If You Need to Fix a Deployed Migration **DO NOT modify the existing migration file.** Instead: ```bash # Create a NEW migration with the fix npx drizzle-kit generate --custom # Name it something like: 0050_fix_missing_entry_prompts.sql # Add the missing SQL (with IF NOT EXISTS for safety) CREATE TABLE IF NOT EXISTS `entry_prompts` (...); # OR for SQLite (which doesn't support IF NOT EXISTS for columns): # Check if column exists first, or just let it fail silently ``` ### Red Flags If you find yourself: - Editing a migration file that's already been committed - Thinking "I'll just update this migration with the correct SQL" - Seeing "migration already applied" but schema is wrong **STOP.** Create a NEW migration instead. ### Emergency Fix for Production If production is down due to missing schema, create a new migration immediately: ```bash # 1. Generate emergency migration npx drizzle-kit generate --custom # Creates: drizzle/0050_emergency_fix.sql # 2. Add the missing SQL with safety checks # For tables: CREATE TABLE IF NOT EXISTS `entry_prompts` (...); # For columns (SQLite workaround - will error if exists, but migration still records): ALTER TABLE `classrooms` ADD COLUMN `entry_prompt_expiry_minutes` integer; # 3. Commit and deploy git add drizzle/ git commit -m "fix: emergency migration for missing schema" git push ``` The new migration will run on production startup and fix the schema. ## CRITICAL: @svg-maps ES Module Imports Work Correctly **The @svg-maps packages (world, usa) USE ES module syntax and this WORKS correctly in production.** **Historical context:** - We went through multiple attempts to "fix" ES module import issues - Tried JSON data files, tried various dynamic import strategies - **The current implementation (dynamic imports in maps.ts) WORKS in production** - Games were successfully created and played in production with this approach - DO NOT try to replace with JSON files or other workarounds **If you see an error related to @svg-maps:** - Check what else changed, not the import mechanism - The imports themselves are NOT the problem - Look for validator issues, type errors, or other recent changes ## CRITICAL: Production Dependencies **NEVER add TypeScript execution tools to production dependencies.** ### Forbidden Production Dependencies The following packages must ONLY be in `devDependencies`, NEVER in `dependencies`: - ❌ `tsx` - TypeScript execution (only for scripts during development) - ❌ `ts-node` - TypeScript execution - ❌ Any TypeScript compiler/executor that runs .ts/.tsx files at runtime ### Why This Matters 1. **Docker Image Size**: These tools add 50-100MB+ to production images 2. **Security**: Running TypeScript at runtime is a security risk 3. **Performance**: Production should run compiled JavaScript, not interpret TypeScript 4. **Architecture**: If you need TypeScript at runtime, the code is in the wrong place ### What To Do Instead **❌ WRONG - Adding tsx to dependencies to run .ts/.tsx at runtime:** ```json { "dependencies": { "tsx": "^4.20.5" // NEVER DO THIS } } ``` **✅ CORRECT - Move code to proper location:** 1. **For Next.js API routes**: Move files to `src/` so Next.js bundles them during build - Example: `scripts/generateCalendar.tsx` → `src/utils/calendar/generateCalendar.tsx` - Next.js will compile and bundle these during `npm run build` 2. **For standalone scripts**: Keep in `scripts/` and use `tsx` from devDependencies - Only run during development/build, never at runtime - Scripts can use `tsx` because it's available during build 3. **For server-side TypeScript**: Compile to JavaScript during build - Use `tsc` to compile `src/` to `dist/` - Production runs the compiled JavaScript from `dist/` ### Historical Context **We've made this mistake TWICE:** 1. **First time (commit ffae9c1b)**: Added tsx to dependencies for calendar generation scripts - **Fix**: Moved scripts to `src/utils/calendar/` so Next.js bundles them 2. **Second time (would have happened again)**: Almost added tsx again for same reason - **Learning**: If you're tempted to add tsx to dependencies, the architecture is wrong ### Red Flags If you find yourself thinking: - "I need to add tsx to dependencies to run this .ts file in production" - "This script needs TypeScript at runtime" - "Production can't import this .tsx file" **STOP.** The code is in the wrong place. Move it to `src/` for bundling. ### Enforcement Before modifying `package.json` dependencies: 1. Check if any TypeScript execution tools are being added 2. Ask yourself: "Could this code be in `src/` instead?" 3. If unsure, ask the user before proceeding ## CRITICAL: Code Factoring - Never Fork, Always Factor **When told to share code between files, NEVER copy/paste. ALWAYS extract to shared utility.** ### The Mistake (Made Multiple Times) When implementing addition worksheet preview examples, I was told **THREE TIMES** to factor out the problem rendering code: - "the example should be closely associated in the codebase semantically with the template" - "just be sure to factor, not fork" - "we need to be showing exactly what the worksheet template uses" **What I did wrong:** Copied the Typst problem rendering code from `typstGenerator.ts` to `example/route.ts` **Why this is wrong:** - Changes to worksheet layout won't reflect in preview - Maintaining two copies guarantees they'll drift apart - Violates DRY (Don't Repeat Yourself) - The user explicitly said "factor, not fork" ### What To Do Instead **✅ CORRECT - Extract to shared function:** 1. Create shared function in `typstHelpers.ts`: ```typescript export function generateProblemBoxFunction(cellSize: number): string { // Returns the Typst function definition that both files can use return `#let problem-box(problem, index) = { ... }`; } ``` 2. Both `typstGenerator.ts` and `example/route.ts` import and use it: ```typescript import { generateProblemBoxFunction } from './typstHelpers' // In Typst template: ${generateProblemBoxFunction(cellSize)} // Then call it: #problem-box((a: 45, b: 27), 0) ``` **❌ WRONG - Copy/paste the code:** ```typescript // typstGenerator.ts const template = `#let problem-box = { ... }`; // ← Original // example/route.ts const template = `#let problem-box = { ... }`; // ← Copy/paste = FORKED CODE ``` ### Red Flags If you find yourself: - Copying large blocks of code between files - Saying "I'll make it match the other file" - Maintaining "two versions" of the same logic **STOP.** Extract to a shared utility function. ### Rule of Thumb When the user says "factor" or "share code" or "use the same template": 1. Find the common code 2. Extract to shared function in appropriate utility file 3. Import and call that function from both places 4. The shared function should be the SINGLE SOURCE OF TRUTH ## MANDATORY: Quality Checks for ALL Work **BEFORE declaring ANY work complete, fixed, or working**, you MUST run and pass these checks: ### When This Applies - Before every commit - Before saying "it's done" or "it's fixed" - Before marking a task as complete - Before telling the user something is working - After any code changes, no matter how small ```bash npm run pre-commit ``` This single command runs all quality checks in the correct order: 1. `npm run type-check` - TypeScript type checking (must have 0 errors) 2. `npm run format` - Auto-format all code with Biome 3. `npm run lint:fix` - Auto-fix linting issues with Biome + ESLint 4. `npm run lint` - Verify 0 errors, 0 warnings **DO NOT COMMIT** until all checks pass with zero errors and zero warnings. ## Blog Post Examples **REUSABLE PATTERN: Generating single-problem examples for blog posts** We have a **single-problem example generator** used for both the UI preview and blog post examples. This ensures blog examples use the **exact same rendering** as the live tool. See `.claude/BLOG_EXAMPLES_PATTERN.md` for complete documentation. **Quick reference:** - UI preview API: `src/app/api/create/worksheets/addition/example/route.ts` - Blog generators: `scripts/generateTenFrameExamples.ts`, `scripts/generateBlogExamples.ts` - Shared code: `src/app/create/worksheets/addition/typstHelpers.ts` **Key benefit**: Blog examples stay in sync with actual worksheet rendering. When rendering changes, just re-run the generator scripts. ## Available Scripts ```bash npm run type-check # TypeScript: tsc --noEmit npm run format # Biome: format all files npm run format:check # Biome: check formatting without fixing npm run lint # Biome + ESLint: check for errors/warnings npm run lint:fix # Biome + ESLint: auto-fix issues npm run check # Biome: full check (format + lint + imports) npm run pre-commit # Run all checks (type + format + lint) ``` ## Workflow When asked to make ANY changes: 1. Make your code changes 2. Run `npm run pre-commit` 3. If it fails, fix the issues and run again 4. **STOP - Tell user changes are ready for testing** 5. **WAIT for user to manually test and approve** 6. Only commit/push when user explicitly approves or requests it **CRITICAL:** Passing `npm run pre-commit` only verifies code quality (TypeScript, linting, formatting). It does NOT verify that features work correctly. Manual testing by the user is REQUIRED before committing. **Never auto-commit or auto-push after making changes.** ## Merge Conflict Resolution When encountering Git merge conflicts, refer to: - **`.claude/MERGE_CONFLICT_RESOLUTION.md`** - Complete guide to intelligent merge conflict resolution - How to read diff3-style conflicts (with common ancestor) - 5 resolution patterns: Compatible, Redundant, Conflicting, Delete vs Modify, Rename + References - Step-by-step analysis workflow - zdiff3 modern alternative (cleaner conflict markers) - Semantic merge concepts - Best practices and anti-patterns - Debugging failed resolutions - Resolution checklist **Quick Reference:** Enable better conflict markers (recommended): ```bash git config --global merge.conflictstyle zdiff3 ``` **diff3 Format:** ``` <<<<<<< HEAD our changes ||||||| base common ancestor (original code) ======= their changes >>>>>>> branch-name ``` **Resolution Strategy:** 1. Compare OURS vs BASE - what did we change? 2. Compare THEIRS vs BASE - what did they change? 3. Classify: Compatible (keep both), Redundant (choose better), or Conflicting (combine carefully) 4. Apply appropriate resolution pattern 5. Test thoroughly (merge conflicts can create semantic issues that compile but don't work) **Critical:** Always test merged code even if it type-checks. Conflicts can create runtime bugs. ## Dev Server Management **CRITICAL: The user manages running the dev server, NOT Claude Code.** - ❌ DO NOT run `pnpm dev`, `npm run dev`, or `npm start` - ❌ DO NOT attempt to start, stop, or restart the dev server - ❌ DO NOT kill processes on port 3000 - ❌ DO NOT use background Bash processes for the dev server - ✅ Make code changes and let the user restart the server when needed - ✅ You may run other commands like `npm run type-check`, `npm run lint`, etc. **The user runs the dev server themselves.** The user will manually start/restart the dev server after you make changes. ## Details See `.claude/CODE_QUALITY_REGIME.md` for complete documentation. ## No Pre-Commit Hooks This project does not use git pre-commit hooks for religious reasons. You (Claude Code) are responsible for enforcing code quality before commits. ## Quick Reference: package.json Scripts **Primary workflow:** ```bash npm run pre-commit # ← Use this before every commit ``` **Individual checks (if needed):** ```bash npm run type-check # TypeScript: tsc --noEmit npm run format # Biome: format code (--write) npm run lint # Biome + ESLint: check only npm run lint:fix # Biome + ESLint: auto-fix ``` **Additional tools:** ```bash npm run format:check # Check formatting without changing files npm run check # Biome check (format + lint + organize imports) ``` --- **Remember: Always run `npm run pre-commit` before creating commits.** ## Styling Framework **CRITICAL: This project uses Panda CSS, NOT Tailwind CSS.** - All styling is done with Panda CSS (`@pandacss/dev`) - Configuration: `/panda.config.ts` - Generated system: `/styled-system/` - Import styles using: `import { css } from '../../styled-system/css'` - Token syntax: `color: 'blue.200'`, `borderColor: 'gray.300'`, etc. **Common Mistakes to Avoid:** - ❌ Don't reference "Tailwind" in code, comments, or documentation - ❌ Don't use Tailwind utility classes (e.g., `className="bg-blue-500"`) - ✅ Use Panda CSS `css()` function for all styling - ✅ Use Panda's token system (defined in `panda.config.ts`) **Color Tokens:** ```typescript // Correct (Panda CSS) css({ bg: "blue.200", borderColor: "gray.300", color: "brand.600", }); // Incorrect (Tailwind) className = "bg-blue-200 border-gray-300 text-brand-600"; ``` ### Fixing Corrupted styled-system (Panda CSS) **If the CSS appears broken or styles aren't applying correctly**, the `styled-system/` directory may be corrupted. This can happen if prettier or other formatters modify the generated files. **Fix:** ```bash # 1. Delete the corrupted styled-system rm -rf apps/web/styled-system # 2. Regenerate it cd apps/web && pnpm panda codegen # 3. Clear Next.js cache (if build errors persist) rm -rf apps/web/.next # 4. Rebuild pnpm build ``` **Prevention:** The repo has a `.prettierignore` at the root that excludes `**/styled-system/**`. If this file is missing or incomplete, prettier will corrupt the generated files when running `pnpm format`. See `.claude/GAME_THEMES.md` for standardized color theme usage in arcade games. ## Data Attributes for All Elements **MANDATORY: All new elements MUST have data attributes for easy reference.** When creating ANY new HTML/JSX element (div, button, section, etc.), add appropriate data attributes: **Required patterns:** - `data-component="component-name"` - For top-level component containers - `data-element="element-name"` - For major UI elements - `data-section="section-name"` - For page sections - `data-action="action-name"` - For interactive elements (buttons, links) - `data-setting="setting-name"` - For game settings/config elements - `data-status="status-value"` - For status indicators **Why this matters:** - Allows easy element selection for testing, debugging, and automation - Makes it simple to reference elements by name in discussions - Provides semantic meaning beyond CSS classes - Enables reliable E2E testing selectors **Examples:** ```typescript // Component container
// Interactive button