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>
The example route was also missing generatePlaceValueColors() calls,
causing "unknown variable: color-ones" errors when rendering subtraction
examples. Added color definitions to both addition and subtraction templates.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
The PDF generation route was ignoring the operator and digitRange settings,
always generating 2-digit addition problems regardless of configuration.
The preview worked correctly but PDF generation was broken.
Changes:
- Add conditional logic to call appropriate problem generator based on operator
- Pass digitRange parameter to all problem generators
- Add generatePlaceValueColors() to Typst template for color definitions
- Import DisplayOptions type for internal use in typstHelpers
Fixes worksheet PDF generation for subtraction, mixed, and multi-digit problems.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
The arrows in borrowing hints were being covered by the diagonal-split-box
colored backgrounds in adjacent cells. This occurred because Typst renders
elements in document order, and arrows placed inside colored boxes appear
below backgrounds from cells rendered later.
Solution: Disable place value colors in borrow boxes entirely. Borrow boxes
now always use stroke-only rendering (no colored backgrounds), which ensures
arrows are never covered. Place value colors still work everywhere else
(minuend, subtrahend, and answer rows).
This is simpler and more maintainable than complex double-render or
compositing approaches, and avoids the fundamental layering limitation.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Change arrow and text from white to gray.darken(40%) when place
value colors are enabled. This ensures visibility on all pastel
backgrounds (blue, green, yellow, pink, purple, peach).
Maintains consistency with non-colored version which already uses gray.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Improve borrowing hints to properly show cascading borrows:
- Detect when borrowing from 0 (cascade situation)
- Show "10 - 1" for intermediate cascade steps
- Show "n - 1" for source non-zero digit
- Example: 300 - 157 shows "3 - 1" at hundreds, "10 - 1" at tens
This makes the hints pedagogically correct for all subtraction
cases, avoiding confusing "0 - 1" displays for young learners.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Complete borrowing hints visualization with smooth bezier curve:
- Curve starts near "1" in borrow box (dx: 0.9, dy: 0.15)
- Smooth quadratic bezier with control point for natural flow
- Ends at arrowhead position (0.24, 0.70 relative)
- Small arrowhead (0.35x font size) at (0.96, 0.62)
- White curve on colored backgrounds, gray on plain
- Typst bezier syntax: vertex with relative control point
Visual guide shows "n − 1" with arrow pointing to borrowed 10s box.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Add visual arrow connecting borrow box to borrowed 10s box:
- Straight diagonal line from top of borrow box
- Points to center of borrowed 10s box below
- White line (1.5pt) on colored backgrounds
- Gray line on non-colored backgrounds
- Coordinates: (0, 0) to (0.24×cellSize, 0.75×cellSize)
Arrow appears when showBorrowingHints toggle is enabled.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Position arrowhead at (2.0 - 0.03, 1.0 - 0.05) to be at arrow endpoint
- Increase arrowhead size from 0.4x to 0.5x for better visibility
- Remove bold weight so it renders as solid triangle pointing down
The borrowed 10s box is visually to the RIGHT (ones place is right of tens).
Changed anchor from 'top + right' to 'top + left' with dx offset to position
at the right edge of borrow box, then curve goes right and down to the
borrowed 10s box.
Replace straight diagonal line with proper curved arrow:
- Starts at top-right corner of borrow box
- Curves with convex side on upper right (control point at 0.3x, 0.2y)
- Ends at top-center of borrowed 10s box (1.225x right, 1.0y down)
- Arrowhead positioned just above endpoint
- Path anchored to top-right of borrow box for correct positioning
Draw a diagonal line from the right of the '1' in 'n − 1' down to the
borrowed 10s box below. Arrow shows the flow of the borrowed 1 moving
to become 10 in the next column.
- Line starts at dx: 0.08in (right of '1')
- Line ends at full cell width/height (top of borrowed 10s box)
- Arrowhead (▼) placed at endpoint
- White line/arrow for colored backgrounds, gray for no-color mode
- Keep borrow box at full size (not 70%)
- Use place() to position hint text at top center of box
- Add white text with black stroke for contrast on colored backgrounds
- Gray text when colors are disabled for better visibility
Replace 'n + 10' text inside borrowed 10s box with an arrow (→) after
the '1' in 'n − 1' that points toward the borrowed 10s box. This shows
the flow of the borrowed 1 moving to the next column.
- Made the '1' bold to emphasize it
- Added rightward arrow (→) after the 1
- Removed the 'n + 10' text from inside the borrowed 10s box
Change from cellSizeIn * 0.7 (which produces NaN since cellSizeIn is a string)
to cellSize * 0.7 with unit added inline. This fixes the Typst compilation error
when borrowing hints are enabled.
Add visual hints when showBorrowingHints is enabled:
- Show "n − 1" calculation above borrow boxes (what to write after borrowing)
- Show "n + 10" inside borrowed 10s box (what value the digit becomes)
- Hints appear in gray text to distinguish from student work
- Font sizes scaled appropriately (0.5x for borrow box hints, 0.45x for borrowed 10s)
This provides step-by-step guidance for young students learning borrowing,
showing them exactly what calculations to perform and what to write in each box.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Add new showBorrowingHints toggle to guide students through borrowing:
- Add showBorrowingHints field to V4 manual config schema
- Add toggle to ConfigPanel UI (shows for subtraction/mixed only)
- Wire through validation, auto-save, preview, and example route
- Update typstGenerator and typstHelpers to accept parameter
- Default to false for backward compatibility
This commit adds the plumbing/infrastructure. The actual Typst rendering
logic for arrows and calculations will be implemented next.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Convert "Borrowed 10s Box" from sub-option to peer toggle for clarity
- Remove confusing parent/child relationship with "Borrow Boxes"
- Fix preview not updating when toggling borrowed 10s box (default was false, should be true)
- Add place value color from source digit to borrowed 10s box background
- Fix missing operator field in defaultAdditionConfig and V3→V4 migration
- Simplify description to "Box for adding 10 to borrowing digit"
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Updated destination borrow boxes to 95% cell height (was 50%)
- Increased width to 45% (was 40%) for better visibility
- Added showBorrowNotation to DisplayOptionsPreview component
- Fixed example route to pass showBorrowNotation parameter
- Boxes now extend nearly full height of digit cell for kids to write big
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Increase size and border thickness of destination digit scratch box:
- Width: 35% → 40% of cell size
- Height: 35% → 50% of cell size
- Border: 0.5pt → 1pt thickness
- Add proper alignment wrapper around stack
Makes the box easier to see and write in.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Smart mode doesn't have the showBorrowNotation field in config, so
enrichedProblems had undefined for this field. This caused Typst
compilation errors.
Fix: Explicitly set showBorrowNotation to false for smart mode problems.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Implements empty scratch boxes to guide students through borrowing work
without doing the arithmetic for them.
Scaffolding design:
1. **Source digit box** (above) - Student crosses out original and writes
reduced value (e.g., cross out 5, write 4)
2. **Destination digit box** (left side) - Student writes modified value
after adding 10 (e.g., write "12" next to 2)
Both boxes:
- Dotted borders to indicate workspace
- ~35-50% size of main digit cells
- Positioned close to the digits they modify
- Only appear where borrowing is needed
Implementation:
- Add `showBorrowNotation` boolean to V4 manual schema
- Update Typst rendering with conditional notation rows
- Add UI toggle (only shows for subtraction/mixed modes)
- Include in validation and auto-save persistence
- Update typstGenerator and example routes
Pedagogical approach: Shows WHERE and provides space for WHAT,
but student must determine and write the actual values.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Two fixes:
1. **Preview cache invalidation**: Add formState.operator to WorksheetPreview
query key so preview refreshes when switching between addition/subtraction/mixed
2. **Dynamic UI labels**: Update "Carry Boxes" label to show:
- "Carry Boxes" for addition mode
- "Borrow Boxes" for subtraction mode
- "Carry/Borrow Boxes" for mixed mode
Description text also updates to match operator type.
Scaffolding for borrowing:
- Borrow boxes (diagonal split boxes showing source → destination)
- Displayed FROM higher place TO lower place (opposite of carries)
- Same underlying field (showCarryBoxes) controls both
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Adds validation for operator field in worksheet config:
- Validate operator is one of: addition, subtraction, or mixed
- Add clarifying comment that digit range applies to both operations
- All other validation rules apply uniformly to both operators
Completes the 10-phase implementation plan for subtraction support.
The worksheet creator now fully supports:
- Addition only
- Subtraction only
- Mixed mode (50/50 addition and subtraction)
All phases complete:
1. ✅ Operator selection UI
2. ✅ Subtraction problem generation
3. ✅ Problem analysis for smart mode
4. ✅ Typst rendering with borrow boxes
5. ✅ Unified typstGenerator dispatch
6. ✅ Display rules for both operators
7. ✅ Auto-save persistence
8. ✅ Preview/example routes
9. ✅ DisplayOptionsPreview component
10. ✅ Validation
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Updates DisplayOptionsPreview component to support operator selection:
- Add operator field to fetchExample options
- Build request options dynamically based on operator type
- Use addend1/addend2 for addition, minuend/subtrahend for subtraction
- Update MathSentence to show correct operator symbol (+ or −)
- Update input labels based on operator (addend vs minuend/subtrahend)
- Include operator in debounce dependencies
The preview now correctly updates when switching between addition/subtraction/mixed modes.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Updates worksheet generation routes to support operator selection:
Example route (example/route.ts):
- Add operator, minuend, subtrahend fields to ExampleRequest
- Split generateExampleTypst into addition/subtraction branches
- Use generateSubtractionProblems for subtraction examples
- Render with subtraction-problem-stack function
Preview generation (generatePreview.ts):
- Import subtraction and mixed problem generators
- Dispatch to correct generator based on operator field
- Support addition, subtraction, and mixed modes
Both routes now correctly generate previews for all three operator modes.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Updates useWorksheetAutoSave hook to persist operator field:
- Add operator to destructured fields
- Include operator in saved config object
This ensures the user's operator selection (addition/subtraction/mixed)
is preserved across sessions.
🤖 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>
Phase 1: Operator Selection UI
- Added WorksheetOperator type ('addition' | 'subtraction' | 'mixed')
- Added operator field to V4 config schema with default 'addition'
- Added operator selector UI in ConfigPanel with 3 buttons
- Unified problem types (AdditionProblem, SubtractionProblem, WorksheetProblem)
Phase 2: Subtraction Problem Generation
- Implemented generateNonBorrow() - no borrowing in any place
- Implemented generateOnesOnlyBorrow() - borrow in ones place only
- Implemented generateBothBorrow() - multiple borrows
- Implemented generateSubtractionProblems() - main generation with digit ranges
- Implemented generateMixedProblems() - 50/50 addition/subtraction mix
- All functions ensure minuend ≥ subtrahend (no negative results)
- Borrow detection works across all place values (1-5 digits)
Next phases will add:
- Subtraction problem analysis
- Typst rendering for subtraction (borrow boxes, etc.)
- Display rules and smart mode support
- Auto-save persistence
- Preview/example routes
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Problem: Display options preview failed with "unexpected argument" error.
Root cause: Example route was calling problem-stack with old signature
that had individual digit parameters (aT, aO, bT, bO), but the function
was refactored to extract digits internally from full numbers.
Solution: Updated to new signature:
- Old: problem-stack(a, b, aT, aO, bT, bO, index, ...)
- New: problem-stack(a, b, index, show-carries, show-answers, ...)
Removed manual digit extraction and let the function handle it internally.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Added missing V4 fields to auto-save hook:
- digitRange (min/max digit configuration)
- manualPreset (manual mode preset selection)
These fields were already in the form state but weren't being saved
to the server, causing settings to not persist across sessions.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Problem: All problems rendered with max-digits columns (e.g., 5 columns
for 2-digit problems), showing scaffolding for unused place values.
Root cause: Grid column count was fixed at page-level max-digits, and
all rendering loops used max-digits instead of per-problem actual-digits.
Solution:
- Calculate actual-digits per problem (max of addends + sum, accounting
for overflow like 99+1=100)
- Extract max-digits+1 positions to capture sum overflow
- Generate column-list dynamically in Typst based on actual-digits
- Update all loops to use actual-digits instead of max-digits
- Hide leading zeros by checking i <= highest position
Now a 2-digit problem gets a 3-column grid (allowing sum overflow),
and a 5-digit problem gets a 6-column grid. Each problem renders
exactly the scaffolding it needs.
Includes:
- Leading zero detection (a-highest, b-highest, sum-highest)
- Dynamic column list generation in Typst
- Ten-frames support for all place values (removed digit restrictions)
- Proper overflow handling for sums
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Replaced button groups with @radix-ui/react-slider for min/max digit
range selection. Features:
- Double-thumbed range slider (1-5 digits)
- Tick marks above slider showing digit values
- Visual feedback (grab cursor, scale on hover, focus rings)
- More intuitive UX for selecting digit ranges
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Problem: Preview wasn't regenerating when digitRange changed because
the query key was missing V4 fields (digitRange, mode, displayRules,
difficultyProfile, manualPreset).
Solution: Added all V4 fields to the queryKey array so TanStack Query
properly invalidates and refetches when these settings change.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Major improvements to the difficulty space visualization:
**2D Map Enhancements:**
- Fixed Y-axis click handling (scaffolding index was inverted)
- Polished for production: blue theme, responsive sizing (up to 500px)
- Collapsed by default with disclosure control
- Better typography, shadows, and spacing
**Hover Preview System:**
- Orange marker shows exact click target on map
- Dashed line connects current position to hover target
- Preset snapping within 1.0 unit threshold
- Real-time difficulty description updates with "(hover preview)" label
- Orange color theme ties hover state together visually
**UI Polish:**
- Removed obsolete "You're here (Custom)" status text
- Reduced Overall Difficulty slider margins for better spacing
- Cleaner, more professional appearance throughout
The map now provides complete feedback before clicking: visual marker
on map + detailed description preview + preset snapping.
🎨 Generated with Claude Code
Co-Authored-By: Claude <noreply@anthropic.com>
Fixed bug where ten-frames wouldn't show in smart mode even when
displayRules.tenFrames was set to 'always' (e.g., beginner preset).
Now sets show-ten-frames-for-all Typst variable to true when:
- Manual mode: config.showTenFramesForAll is true
- Smart mode: config.displayRules.tenFrames === 'always'
This ensures beginner mode shows ten-frames on all problems as intended.
Changed button labels from past tense to imperative:
- 'Increased regrouping' → 'Increase regrouping'
- 'Reduced regrouping' → 'Reduce regrouping'
- 'Added scaffolding' → 'Add scaffolding'
Buttons now describe what WILL happen when clicked, not what was done.
Moved the progressive difficulty checkbox outside mode-specific sections
so it's available for both Smart and Manual modes.
Previously: Only available in Manual mode
Now: Available in both modes as a shared setting
The checkbox is now in its own card section below mode-specific controls,
making it clear that it applies regardless of which mode is selected.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Reverting commits:
- 0a5812c9 fix(worksheets): use white text on colored backgrounds
- 5d8ac63c feat(worksheets): use more vibrant and distinct difficulty colors
- e641b5e3 feat(worksheets): implement true RGB color interpolation
- a7cef56f fix(worksheets): increase color visibility for difficulty presets
- a09a1cbb feat(worksheets): add color-coding to difficulty presets
Returning to simple, uncolored preset buttons.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Changed from light backgrounds with dark text to saturated backgrounds
with white text. This provides much better contrast and visibility.
Before: Light .100 backgrounds with dark .700-.800 text (invisible!)
After: Saturated .500-.600 backgrounds with white text (high contrast)
All difficulty levels now use white text on vibrant colored backgrounds:
- Emerald.500 + white text
- Cyan.500 + white text
- Purple.500 + white text
- Amber.500 + white text
- Red.600 + white text
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
New color progression:
- Beginner: Emerald (fresh, encouraging green)
- Early Learner: Cyan (calm, learning blue)
- Intermediate: Purple (transitional, neutral)
- Advanced: Amber (warming up, getting challenging)
- Expert: Crimson (intense red)
Changes from previous:
- More saturated and visually distinct colors
- Purple breaks up the green→yellow→red monotony
- Better visual separation between adjacent levels
- All colors maintain high contrast text (.700-.800)
- Backgrounds remain light (.100) for readability
The new palette creates a more interesting visual journey
through the difficulty progression and makes it easier to
distinguish levels at a glance.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Previously was using binary selection (pick closer preset's color).
Now performs smooth linear RGB interpolation based on pythagorean
distance in 2D difficulty space.
Implementation:
- Added DIFFICULTY_RGB with actual RGB values for each preset
- Created interpolateRGB() to blend colors channel-by-channel
- getInterpolatedColor() now returns rgb() strings instead of tokens
- Panda CSS accepts both tokens (presets) and RGB strings (custom)
When moving slider between presets, the button color now smoothly
transitions through the color spectrum (green → blue → yellow →
orange → red) based on exact position in difficulty space.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Changed from .50 backgrounds to .100 for better visibility
Changed early learner from cyan to blue for better distinction
Changed borders from .400 to .500 for more prominent accents
Changed text from .700 to .800 for better contrast
The cyan.50 background was nearly invisible against white.
New colors are still subtle but clearly visible.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Added subtle color progression from green (easy) to red (hard):
- Beginner: Green
- Early Learner: Cyan
- Intermediate: Yellow
- Advanced: Orange
- Expert: Red
Features:
- Dropdown button background/border uses preset color
- Dropdown menu items have colored left border accent
- Custom configurations interpolate color based on pythagorean distance
between two nearest presets in 2D difficulty space
- Hover states use subtle opacity changes to avoid visual clash
Colors are intentionally subtle (using .50 backgrounds, .400 borders,
.700 text) to avoid being distracting while still providing visual
feedback about difficulty level.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>