Two critical issues fixed:
1. **Duplicate numerals**: Both AbacusSVGRenderer and AbacusReact were rendering
numerals when showNumbers={true}, causing two overlapping number displays.
- Disabled SVG text numerals in AbacusSVGRenderer (line 436: added `false &&`)
- NumberFlow provides better animated numerals, keep only those
2. **White numerals in dark mode**: NumberFlow components were inheriting CSS
color from parent, turning white in dark mode (unreadable on light frames).
- Added explicit color style to NumberFlow: uses themeAwareCustomStyles
- Now consistently dark (rgba(0,0,0,0.8)) regardless of page theme
This was the root cause of the "white numerals everywhere" issue - the
NumberFlow components were inheriting dark mode CSS colors.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Updates typstGenerator.ts to handle both addition and subtraction:
- Import WorksheetProblem union type instead of just AdditionProblem
- Import subtraction rendering and analysis functions
- Update calculateMaxDigits to handle both operators
- Update problem enrichment to analyze addition vs subtraction
- Generate Typst problem data with operator discriminator
- Dispatch to correct rendering function based on operator
- Update displayRules to handle both ProblemMeta and SubtractionProblemMeta
Also updates:
- problemGenerator.ts: Add operator field to AdditionProblem
- displayRules.ts: Support AnyProblemMeta union type
- validation.ts: Add operator to shared config fields
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Remove all scaffolding (carry boxes, answer boxes, place value colors) from ten-frame examples
- Show only the core addition problems with ten-frame visualization
- Add transparent background support for blog examples
- Implement smart color replacement for dark theme visibility:
- White operator symbols (+) and horizontal bars (visible on dark background)
- Black digits on white cells (preserved contrast)
- White structural borders and lines
- Update generateTenFrameExamples.ts with post-processing to handle SVG colors
- Regenerate all 4 ten-frame example SVGs with clean, minimal presentation
The examples now focus purely on demonstrating the ten-frame concept
without distracting extra scaffolding elements.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Fixes production error "Cannot read properties of undefined (reading 'carryBoxes')"
that occurred when users tried to adjust difficulty settings.
Root cause: displayRules was undefined for new users or users with old V1 config
in database. Difficulty adjustment buttons accessed displayRules.carryBoxes without
checking if displayRules existed first.
Changes:
- AdditionWorksheetClient: Initialize displayRules with defaults when missing
- ConfigPanel: Use null-coalescing operators instead of non-null assertions
- ConfigPanel: Add error logging when required fields are missing
- NEW: WorksheetErrorBoundary component to catch all errors in worksheet page
- page.tsx: Wrap client component with error boundary
This ensures users see helpful error messages instead of blank pages,
and never need to open the browser console to understand what went wrong.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Fix script to use correct displayRules property access
- Use valid rule modes (whenRegrouping, when3PlusDigits, whenMultipleRegroups)
- Generate 4 examples with same problem complexity but different scaffolding
- Add 'as const' assertions for TypeScript literal type inference
Generated SVGs:
- full-scaffolding.svg
- medium-scaffolding.svg
- minimal-scaffolding.svg
- no-scaffolding.svg
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Add debugging script to visualize the constrained 2D difficulty progression:
- Traces 30 steps from beginner → maximum difficulty
- Shows path through (regrouping, scaffolding) space as 2D ASCII grid
- Marks preset positions (BEG, EAR, INT, ADV, EXP)
- Visualizes constraint band boundaries
- Helps verify navigation completeness and detect cycles
Usage: npx tsx scripts/traceDifficultyPath.ts
Output shows diagonal progression through pedagogical constraint band,
confirming no dead ends or invalid states.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Replace custom bounding box calculations with the new cropToActiveBeads prop:
- Remove 100+ lines of manual bbox calculation code (getAbacusBoundingBox)
- Use AbacusStatic instead of AbacusReact for simpler SSR
- Let cropToActiveBeads handle all cropping logic with padding config
- Simplify script to just parse dimensions from cropped SVG and center in canvas
This eliminates code duplication and leverages the precise cropping utilities
now available in the package.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Replace fragile regex parsing in generateDayIcon.tsx with proper bead
position calculations using numberToAbacusState(), calculateStandardDimensions(),
and calculateBeadPosition() from @soroban/abacus-react
- Add query parameter support to /icon route for testing different days (e.g. /icon?day=15)
- Fix icon cropping to properly show only active beads with dynamic viewBox
- Validate day parameter (1-31) and return 400 for invalid values
- Different cache duration for production (1 hour) vs testing (1 minute)
Results:
- Day 1: 48.88px height (minimal)
- Day 5: 48.88px height (single heaven bead)
- Day 25: 100.18px height (many active beads)
- Day 31: 93.88px height
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
The calendar generation scripts were in scripts/ directory which wasn't
included in tsconfig.server.json, so they weren't being compiled during
build. This required tsx in production just to import .tsx files at runtime.
Changes:
- Moved generateCalendarComposite.tsx and generateCalendarAbacus.tsx to src/utils/calendar/
- Removed CLI interface code from these files (they're now pure utility modules)
- Updated imports in API routes to use @/utils/calendar/...
- Moved tsx back to devDependencies where it belongs
- Removed scripts/ copy from Dockerfile (no longer needed)
Now these files are compiled to JavaScript during the build process and
don't require tsx at runtime. This fixes the architecture issue properly.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Add calculateAbacusDimensions utility to AbacusUtils
- Refactor AbacusStatic to use shared dimension calculator
- Update calendar composite generator to use shared utility
- Export calculateAbacusDimensions from index and static entry points
- Ensures consistent sizing between preview and PDF generation
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Separate month name and year display in calendar header
- Render year as abacus element (15% of page width)
- Increase grid line weight from 1px to 2px
- Change grid color from light gray (#ddd) to dark (#333)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Changes:
- Scripts now export JSX elements (generateAbacusElement) or accept renderToString param
- API route imports react-dom/server (API routes are server-only, allowed)
- renderToStaticMarkup called in API route, not in imported scripts
- CLI interfaces use dynamic require() for react-dom/server when run directly
This satisfies Next.js requirement that react-dom/server not be imported in files that could be client-bundled.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Changes:
- Refactored scripts/generateCalendarAbacus.tsx to export generateAbacusSVG function
- Refactored scripts/generateCalendarComposite.tsx to export generateCalendarComposite function
- Updated API route to import and call functions directly instead of spawning subprocesses
- Removed tsx from production Dockerfile (not needed - Next.js transpiles at build time)
- Kept scripts and abacus-react package copies in Dockerfile (needed for imports)
This eliminates the need for tsx at runtime and improves performance by avoiding subprocess overhead.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Fixed persistent overlap by using proper SVG nesting:
Previous approach: Extracted SVG content and used g transforms
- Problem: Inner coordinates remained in original 120x230 space
- Caused overlapping when elements had absolute positioning
New approach: Nested SVG elements with viewBox
- Each abacus rendered at natural scale=1 (120×230)
- Wrapped in <svg> with:
- x, y: position in parent calendar
- width, height: scaled display size
- viewBox="0 0 120 230": maps content to display size
- Creates isolated coordinate space per abacus
This is the correct SVG pattern for embedding scaled content.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
BREAKING FIX: Monthly calendars were overflowing to multiple pages
New "page-first" design approach:
- Generate entire calendar as one composite SVG (850x1100px)
- Includes title, year abacus, weekday headers, and all day abacuses
- Typst scales the single image to fit page (width: 100%, fit: "contain")
- Impossible to overflow - it's just one scalable image
Benefits:
✅ Guaranteed single-page layout across all paper sizes
✅ No grid overflow issues
✅ Consistent rendering regardless of month length
✅ Fast generation (~97KB SVG vs multiple small files)
Implementation:
- Created generateCalendarComposite.tsx script
- Updated route to use composite for monthly, individual SVGs for daily
- Simplified generateMonthlyTypst to just scale one image
Daily calendars unchanged (intentionally multi-page, one per day).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Switch from AbacusReact to AbacusStatic in generateCalendarAbacus.tsx
- Fixes "failed to parse SVG (missing root node)" Typst error
- AbacusReact can't be rendered server-side with renderToStaticMarkup
- AbacusStatic is designed for pure server-side rendering
Root cause: AbacusReact has "use client" directive and uses hooks,
which causes renderToStaticMarkup to generate invalid/empty SVG.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Move hero abacus down from 50vh to 60vh to prevent it from overlapping
the subtitle text ("master the ancient art..." etc.) on the home page.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Fix two critical issues with the trophy abacus system:
1. Hero abacus appearing on all pages:
- Root cause: HomeHeroProvider now wraps all pages globally
- Solution: Use pathname check to detect actual home page routes
- Only show hero mode on: /, /en, /de, /ja, /hi, /es, /la
2. Z-index conflicts causing layering issues:
- AppNavBar had hardcoded z-index: 1000 (DROPDOWN layer)
- Should use Z_INDEX.NAV_BAR (100) for proper layering
- Tooltip had z-index: 50, should use Z_INDEX.TOOLTIP (1000)
This ensures:
- Hero abacus only appears on home page, not all pages
- Trophy abacus (z-index 30001) appears above ALL content
- Nav bar and tooltips use correct z-index constants
- No stacking context conflicts
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
The hero abacus was scrolling in the opposite direction of page content due to incorrect math. Fixed by subtracting scroll position instead of adding it.
Change: top: calc(50vh - ${scrollY}px) instead of calc(50vh + ${scrollY}px)
Now the abacus properly scrolls up with the page content when scrolling down.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Update favicon design based on user preference:
- Ones place: Gold (#fbbf24, stroke #f59e0b)
- Tens place: Purple (#a855f7, stroke #7e22ce)
- Remove background circle for clean, transparent appearance
The royal color scheme provides better contrast and a more
sophisticated look while maintaining excellent legibility at small sizes.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Use asymmetric padding to crop tighter to the active bead region:
- Top: 8px (some breathing room above)
- Bottom: 2px (minimal gap below last bead)
- Sides: 5px
This eliminates the visible gap of column post structure below the
last active bead while maintaining clean spacing above.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Previous approach used transforms to scale the entire SVG, which didn't
actually crop anything - just scaled everything uniformly.
New approach uses nested SVG with viewBox to truly crop the content:
- Outer SVG: 100x100 canvas with background circle
- Inner SVG: Uses viewBox to show only the cropped region
- viewBox dimensions vary per day based on active bead positions
Results:
- Day 1 (1 bead): viewBox height ~59px (narrow vertical crop)
- Day 15 (mixed): viewBox height ~88px (medium)
- Day 31 (many): viewBox height ~104px (tall vertical crop)
- Horizontal: Always ~110px (both columns with posts)
This actually maximizes bead size by showing only the relevant vertical region.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Clarify that horizontal and vertical cropping use different criteria:
- Horizontal (X): Always show full width of both columns (from structural rects)
- Vertical (Y): Crop dynamically to active bead positions + heights
This ensures consistent horizontal scale (0.8) across all days, with only
vertical positioning adjusting based on where active beads are located.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Refined the cropping algorithm to include structural elements (column posts)
in the bounding box calculation, not just active beads. This ensures the
complete abacus structure is visible.
Algorithm changes:
- X range: Full width from leftmost to rightmost post (all columns)
- Y range: From top active bead to bottom active bead (tight vertical crop)
Results:
- Consistent scale of 0.8 across all days (vs original 0.48)
- Shows complete column structure with posts
- Vertical position adjusts based on active bead positions
- Maintains visual context while maximizing bead visibility
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Implement bounding box calculation to crop the favicon SVG to show only
the active beads, maximizing their size within the 100x100 icon canvas.
Algorithm:
1. Parse rendered SVG to find all active bead positions (via regex)
2. Calculate bounding box with 15px padding
3. Compute optimal scale to fit within 96x96 (leaving border room)
4. Apply transform: translate + scale + translate to crop and center
Results:
- Day 1-9 (few beads): scale ~1.14 (2.4x larger than before)
- Day 31 (many beads): scale ~0.74 (1.5x larger than before)
- Original fixed scale: 0.48
This uses no external dependencies - just regex parsing of the rendered
SVG to extract active bead coordinates.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Metadata fixes:
- Update icon path from /icon.svg to /icon to match route handler
- Ensure proper MIME type (image/svg+xml) is set in both metadata and response
Favicon visibility improvements:
- Increase AbacusReact scaleFactor from 1.0 to 1.8 for larger beads
- Add hideInactiveBeads prop to hide inactive beads
- Add hide-inactive-mode class to wrapper for CSS to take effect
- Adjust outer scale to 0.48 to fit larger abacus in 100x100 viewBox
- Reposition abacus with translate(28, -2) for proper centering
CSS cleanup:
- Strip !important declarations from generated SVG (production code policy)
- Apply same fix to OG image generator
User needs to restart dev server to clear in-memory cache.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Create scripts/generateDayIcon.tsx for on-demand icon generation
- Route handler calls script via execSync (avoids Next.js react-dom/server restriction)
- Implement in-memory caching to minimize subprocess overhead
- Show current day (01-31) on 2-column abacus in US Central Time
- High-contrast design: blue/green beads, 2px strokes, gold border
- Document SSR pattern in .claude/CLAUDE.md for future reference
Fixes the Next.js limitation where route handlers cannot import react-dom/server
by using subprocess pattern - scripts CAN use react-dom/server, route handlers
call them via execSync and cache the results.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Generate icon.svg and og-image.svg from AbacusReact component via scripts/generateAbacusIcons.tsx
- OG image: huge 4-column abacus with place-value colors, dark theme with decorative diamond shapes and math operators at 0.4/0.35 opacity for visibility
- Favicon: single-column abacus showing value 5, dark brown beads, properly centered and scaled (0.7)
- opengraph-image.tsx: read pre-generated og-image.svg instead of using react-dom/server (avoids edge runtime restriction)
- All abacus visualizations now use AbacusReact component consistently
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Fixed generateAbacusIcons.tsx to properly extract SVG content from
AbacusReact server-side renders, removing invalid div wrappers that
prevented the abacus from displaying in icon.svg and og-image.svg.
Changes:
- Added extractSvgContent() function to parse rendered markup
- Regenerated icon.svg with proper SVG structure
- Regenerated og-image.svg showing abacus value 123
- Removed unused React import (modern TSX doesn't need it)
The Open Graph image now correctly displays the abacus visualization
alongside the site text, fixing the issue where only text was visible.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Update metadata in layout.tsx with full SEO, Open Graph, Twitter cards, PWA config
- Create sitemap.ts for dynamic sitemap generation
- Create robots.ts for search engine guidance
- Create icon.svg favicon generated from AbacusReact component
- Create opengraph-image.tsx for dynamic Open Graph image generation
- Create public/og-image.svg as fallback Open Graph image
- Make AbacusReact component SSR-compatible by:
- Detecting server environment and conditionally using animations
- Using regular SVG elements instead of animated ones when on server
- Making useSpring and useDrag hooks SSR-safe
- Add generateAbacusIcons.tsx script to generate icons from real AbacusReact component
All icons now use the actual AbacusReact component rendering, ensuring consistency
between static assets and the interactive version.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Fixed board rotation issues when guide is docked:
- Board now prioritizes HEIGHT when in portrait orientation
- Removed CSS constraints (maxWidth, maxHeight, aspectRatio) that were
interfering with explicit dimensions during rotation
- Board properly fills container whether guide is docked or not
- Smooth transition between portrait and landscape orientations
Technical changes:
- Set maxWidth/maxHeight to 'none' when svgDimensions are explicit
- Set aspectRatio to 'auto' when rotating to prevent override
- Cleaned up debug logging
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Add comprehensive draggable playing guide modal with quick navigation:
- Draggable modal on desktop, fixed on mobile
- 5 quick-access sections: Overview, Pieces, Capture, Harmony, Victory
- Integrated PieceRenderer SVGs for visual piece examples
- Responsive layout for desktop and mobile
- Modal persists during gameplay
- "How to Play" button on setup page with updated description
- "Guide" button in gameplay controls
- Complete guide content from PLAYING_GUIDE.md
Features:
- Desktop: draggable modal that can be positioned anywhere
- Mobile: full-screen responsive modal
- Quick navigation tabs for easy reference
- Visual piece examples with movement descriptions
- Mathematical capture relations explained
- Harmony progression examples with formulas
- Strategy tips and victory conditions
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Non-host room members were getting 403 errors when trying to select games.
Added proper UI restrictions and messaging to clarify only the host can
select games.
**Changes**:
1. **Host Detection**: Check if current user is room creator
- Find `currentMember` in `roomData.members`
- Check `isCreator` flag
2. **Visual Restrictions**:
- Game buttons disabled for non-hosts (opacity: 0.4, cursor: not-allowed)
- No hover effects when disabled
- Clear visual feedback
3. **Messaging**:
- **Host**: "👑 You're the room host. Select a game to start playing."
- **Non-host**: "⏳ Waiting for [Host Name] to select a game..."
- **Error**: "⚠️ Only the room host can select a game. Ask [Host] to choose."
4. **Error Handling**:
- Client-side check before API call
- Server error caught and displayed with host name
- Auto-dismiss after 5 seconds
**UX Flow**:
- Non-hosts see disabled games with clear "waiting for host" message
- If they somehow click, they get clear error message
- Host sees active games with confirmation they can select
Prevents confusing 403 errors and clarifies room permissions.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
This commit fixes two critical production issues:
1. **Flashcard generator dependencies** - Added all required dependencies to Dockerfile:
- Typst for PDF generation
- Python pip and setuptools
- Python packages (pyyaml, Pillow, imagehash)
- packages/core directory with generate.py script
2. **Deployment info modal** - Fixed git commit hash display on production:
- Modified generate-build-info.js to accept env vars as fallback when .git is unavailable
- Updated Dockerfile to accept GIT_* build arguments
- Updated GitHub Actions workflow to pass git information during Docker build
The deployment info modal (Ctrl+Shift+I) will now show the correct commit hash,
branch, and build time on production, matching the behavior on dev.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
**Problem:**
- player-ownership.ts imported drizzle-orm and @/db at top level
- When RoomMemoryPairsProvider imported client-safe utilities, Webpack bundled ALL imports including database code
- This caused hydration error: "The 'original' argument must be of type Function"
- Node.js util.promisify was being called in browser context
**Solution:**
1. Created player-ownership.client.ts with ONLY client-safe utilities
- No database imports
- Safe to import from 'use client' components
- Contains: buildPlayerOwnershipFromRoomData(), buildPlayerMetadata(), helper functions
2. Updated player-ownership.ts to re-export client utilities and add server-only functions
- Re-exports everything from .client.ts
- Adds buildPlayerOwnershipMap() (async, database-backed)
- Safe to import from server components/API routes
3. Updated RoomMemoryPairsProvider to import from .client.ts
**Result:**
- No more hydration errors on /arcade/room
- Client bundle doesn't include database code
- Server code can still use both client and server utilities
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Run Biome formatter on all files to ensure consistent code style:
- Single quotes for JS/TS
- Double quotes for JSX
- 2-space indentation
- 100 character line width
- Semicolons as needed
- ES5 trailing commas
This is the result of running: npx @biomejs/biome format . --write
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Add script to capture deployment metadata (git commit, branch, timestamp, version) at build time and integrate it into the build process.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>