diff --git a/apps/web/scripts/validateTypstRefactoring.ts b/apps/web/scripts/validateTypstRefactoring.ts new file mode 100644 index 00000000..4cef1a9d --- /dev/null +++ b/apps/web/scripts/validateTypstRefactoring.ts @@ -0,0 +1,96 @@ +#!/usr/bin/env tsx +/** + * Validation script for typstHelpers refactoring + * + * Generates sample worksheets and verifies that the refactored code + * produces identical Typst output to ensure no regressions. + */ + +import { generateSubtractionProblemStackFunction } from '../src/app/create/worksheets/addition/typstHelpers' +import { generateTypstHelpers } from '../src/app/create/worksheets/addition/typstHelpers' +import { generatePlaceValueColors } from '../src/app/create/worksheets/addition/typstHelpers' + +console.log('šŸ” Validating typstHelpers refactoring...\n') + +// Test 1: Check that functions are exported and callable +console.log('āœ“ Test 1: Functions are exported') +console.log(` - generateSubtractionProblemStackFunction: ${typeof generateSubtractionProblemStackFunction}`) +console.log(` - generateTypstHelpers: ${typeof generateTypstHelpers}`) +console.log(` - generatePlaceValueColors: ${typeof generatePlaceValueColors}`) + +if (typeof generateSubtractionProblemStackFunction !== 'function') { + console.error('āŒ generateSubtractionProblemStackFunction is not a function!') + process.exit(1) +} + +// Test 2: Generate sample Typst code +console.log('\nāœ“ Test 2: Generate sample Typst code') +const cellSize = 0.55 +const maxDigits = 3 + +const helpers = generateTypstHelpers(cellSize) +console.log(` - Helper functions: ${helpers.length} characters`) + +const colors = generatePlaceValueColors() +console.log(` - Color definitions: ${colors.length} characters`) + +const problemStack = generateSubtractionProblemStackFunction(cellSize, maxDigits) +console.log(` - Problem stack function: ${problemStack.length} characters`) + +// Test 3: Verify key features are present +console.log('\nāœ“ Test 3: Verify key features in generated Typst') + +const checks = [ + { name: 'Borrow boxes row', pattern: /Borrow boxes row/ }, + { name: 'Minuend row', pattern: /Minuend row/ }, + { name: 'Subtrahend row', pattern: /Subtrahend row/ }, + { name: 'Answer boxes', pattern: /Answer boxes/ }, + { name: 'Ten-frames', pattern: /Ten-frames row/ }, + { name: 'Borrowing hints', pattern: /show-borrowing-hints/ }, + { name: 'Arrow rendering', pattern: /path\(/ }, + { name: 'Place value colors', pattern: /place-colors/ }, + { name: 'Scratch work boxes', pattern: /dotted.*paint: gray/ }, +] + +let allPassed = true +for (const check of checks) { + const found = check.pattern.test(problemStack) + if (found) { + console.log(` āœ“ ${check.name}`) + } else { + console.log(` āŒ ${check.name} - NOT FOUND`) + allPassed = false + } +} + +// Test 4: Verify structure +console.log('\nāœ“ Test 4: Verify Typst structure') +const structureChecks = [ + { name: 'Function definition', pattern: /#let subtraction-problem-stack\(/ }, + { name: 'Grid structure', pattern: /grid\(/ }, + { name: 'Stack structure', pattern: /stack\(/ }, + { name: 'Problem number display', pattern: /problem-number-display/ }, +] + +for (const check of structureChecks) { + const found = check.pattern.test(problemStack) + if (found) { + console.log(` āœ“ ${check.name}`) + } else { + console.log(` āŒ ${check.name} - NOT FOUND`) + allPassed = false + } +} + +// Summary +console.log('\n' + '='.repeat(60)) +if (allPassed) { + console.log('āœ… All validation checks passed!') + console.log('\nThe refactored code generates valid Typst output with all') + console.log('expected features present.') + process.exit(0) +} else { + console.log('āŒ Some validation checks failed!') + console.log('\nPlease review the output above for details.') + process.exit(1) +} diff --git a/apps/web/src/app/create/worksheets/addition/SUBTRACTION_SCAFFOLDING_ANALYSIS.md b/apps/web/src/app/create/worksheets/addition/SUBTRACTION_SCAFFOLDING_ANALYSIS.md new file mode 100644 index 00000000..b49a6b95 --- /dev/null +++ b/apps/web/src/app/create/worksheets/addition/SUBTRACTION_SCAFFOLDING_ANALYSIS.md @@ -0,0 +1,349 @@ +# Subtraction Scaffolding Analysis & Smart Difficulty Integration + +## Current State + +### Subtraction-Specific Scaffolding Options + +We have **two new subtraction-specific scaffolding options**: + +1. **`showBorrowNotation`** (Manual mode only, line 332) + - Shows dotted scratch boxes to the left of minuend digits that need borrowing + - Visual space for students to write modified digit values (e.g., "12" when borrowing from tens to ones) + - Background color comes from the place value being borrowed FROM + +2. **`showBorrowingHints`** (Manual mode only, line 333) + - Shows visual hints with arrows pointing to where students should write borrowed values + - Displays "n-1" hints showing what to write in the borrow-from place + - Includes curved arrows with arrowheads for clear visual guidance + +### Current Integration Status + +**āœ… Works in Manual Mode:** +- Both options available in manual mode schema (config-schemas.ts:332-333) +- Both options properly passed to Typst rendering (typstGenerator.ts:114-115, 225-226) +- Defaults: `showBorrowNotation: true`, `showBorrowingHints: false` + +**āŒ NOT Available in Smart Mode:** +- Smart mode explicitly sets both to `false` (typstGenerator.ts:88-89) +- Comments say: "Smart mode doesn't have borrow notation (yet)" +- No conditional rules for these options in `DisplayRules` interface + +## Gaps & Issues + +### 1. **No Smart Mode Integration** āš ļø CRITICAL + +The subtraction scaffolding is **completely absent from smart difficulty mode**. This means: + +- āŒ Smart mode worksheets never show borrow notation boxes +- āŒ Smart mode worksheets never show borrowing hints +- āŒ No way to progressively fade these scaffolds based on problem difficulty +- āŒ Subtraction problems in smart mode have LESS scaffolding than addition problems + +**Impact:** Smart mode is less useful for subtraction than for addition. Teachers using smart mode for subtraction get NO subtraction-specific scaffolding, making it harder for students to learn borrowing. + +### 2. **Missing Display Rules** + +The `DisplayRules` interface (displayRules.ts:14-21) only includes: +- `carryBoxes` (addition-focused) +- `answerBoxes` +- `placeValueColors` +- `tenFrames` (works for both, but addition-named) +- `problemNumbers` +- `cellBorders` + +**Missing:** +- `borrowNotation` - Conditional rules for scratch boxes +- `borrowingHints` - Conditional rules for visual hints + +### 3. **Problem Analysis is Good** āœ… + +`SubtractionProblemMeta` (problemAnalysis.ts:84-95) properly tracks: +- `requiresBorrowing: boolean` +- `borrowCount: number` +- `borrowPlaces: PlaceValue[]` + +This gives us the data we need to make smart decisions about when to show scaffolding. + +### 4. **Rule Evaluation Works** āœ… + +The `evaluateRule()` function (displayRules.ts:36-57) already handles both addition and subtraction: +- Line 45-48: Maps `requiresRegrouping` (addition) OR `requiresBorrowing` (subtraction) +- Line 50-52: Maps `regroupCount` (addition) OR `borrowCount` (subtraction) + +So the **infrastructure is ready** - we just need to add the rules. + +## Recommendations + +### Phase 1: Add Display Rules for Subtraction Scaffolding + +**1. Extend `DisplayRules` interface:** + +```typescript +// displayRules.ts +export interface DisplayRules { + carryBoxes: RuleMode + answerBoxes: RuleMode + placeValueColors: RuleMode + tenFrames: RuleMode + problemNumbers: RuleMode + cellBorders: RuleMode + borrowNotation: RuleMode // NEW: Scratch boxes for borrowing work + borrowingHints: RuleMode // NEW: Visual hints (arrows, "n-1") +} +``` + +**2. Update `ResolvedDisplayOptions`:** + +```typescript +// displayRules.ts +export interface ResolvedDisplayOptions { + showCarryBoxes: boolean + showAnswerBoxes: boolean + showPlaceValueColors: boolean + showTenFrames: boolean + showProblemNumbers: boolean + showCellBorder: boolean + showBorrowNotation: boolean // NEW + showBorrowingHints: boolean // NEW +} +``` + +**3. Update `resolveDisplayForProblem()`:** + +```typescript +// displayRules.ts (line 70-77) +const resolved = { + showCarryBoxes: evaluateRule(rules.carryBoxes, problem), + showAnswerBoxes: evaluateRule(rules.answerBoxes, problem), + showPlaceValueColors: evaluateRule(rules.placeValueColors, problem), + showTenFrames: evaluateRule(rules.tenFrames, problem), + showProblemNumbers: evaluateRule(rules.problemNumbers, problem), + showCellBorder: evaluateRule(rules.cellBorders, problem), + showBorrowNotation: evaluateRule(rules.borrowNotation, problem), // NEW + showBorrowingHints: evaluateRule(rules.borrowingHints, problem), // NEW +} +``` + +### Phase 2: Update Config Schemas + +**1. Add to Smart Mode schema:** + +```typescript +// config-schemas.ts (additionConfigV4SmartSchema, line 271-314) +displayRules: z.object({ + carryBoxes: z.enum([...]), + answerBoxes: z.enum([...]), + placeValueColors: z.enum([...]), + tenFrames: z.enum([...]), + problemNumbers: z.enum([...]), + cellBorders: z.enum([...]), + borrowNotation: z.enum([ // NEW + 'always', + 'never', + 'whenRegrouping', // When any borrowing needed + 'whenMultipleRegroups', // When 2+ borrows + 'when3PlusDigits', + ]), + borrowingHints: z.enum([ // NEW + 'always', + 'never', + 'whenRegrouping', + 'whenMultipleRegroups', + 'when3PlusDigits', + ]), +}), +``` + +**2. Update default config:** + +```typescript +// config-schemas.ts (defaultAdditionConfig, line 375-382) +displayRules: { + carryBoxes: 'whenRegrouping', + answerBoxes: 'always', + placeValueColors: 'always', + tenFrames: 'whenRegrouping', + problemNumbers: 'always', + cellBorders: 'always', + borrowNotation: 'whenRegrouping', // NEW: Show when borrowing needed + borrowingHints: 'never', // NEW: Advanced feature, default off +}, +``` + +### Phase 3: Update Scaffolding Progression + +Add subtraction scaffolding to the pedagogical progression: + +```typescript +// difficultyProfiles.ts (SCAFFOLDING_PROGRESSION) +export const SCAFFOLDING_PROGRESSION: DisplayRules[] = [ + // Level 0: Maximum scaffolding + { + carryBoxes: 'always', + answerBoxes: 'always', + placeValueColors: 'always', + tenFrames: 'always', + problemNumbers: 'always', + cellBorders: 'always', + borrowNotation: 'always', // NEW: Always show scratch boxes + borrowingHints: 'always', // NEW: Always show hints + }, + + // Level 1: Carry/borrow boxes become conditional + { + carryBoxes: 'whenRegrouping', + borrowNotation: 'whenRegrouping', // NEW: Only when borrowing + borrowingHints: 'always', // Still show hints + // ... rest + }, + + // Level 2: Hints become conditional + { + carryBoxes: 'whenRegrouping', + borrowNotation: 'whenRegrouping', + borrowingHints: 'whenRegrouping', // NEW: Only when borrowing + // ... rest + }, + + // Level 3-5: Keep both at whenRegrouping + // ... (intermediate levels) + + // Level 6: Hints become more conditional + { + borrowNotation: 'whenRegrouping', + borrowingHints: 'whenMultipleRegroups', // NEW: Only complex problems + // ... rest + }, + + // Level 7+: Remove hints, keep notation + { + borrowNotation: 'whenRegrouping', + borrowingHints: 'never', // NEW: No hints + // ... rest + }, + + // Level 10+: Remove all subtraction scaffolding + { + borrowNotation: 'never', + borrowingHints: 'never', + // ... rest + }, +] +``` + +### Phase 4: Update Typst Generator + +**Remove hardcoded false values:** + +```typescript +// typstGenerator.ts (line 88-89) +// BEFORE: +showBorrowNotation: false, // Smart mode doesn't have borrow notation (yet) +showBorrowingHints: false, // Smart mode doesn't have borrowing hints (yet) + +// AFTER: +showBorrowNotation: displayOptions.showBorrowNotation, // Use resolved value +showBorrowingHints: displayOptions.showBorrowingHints, // Use resolved value +``` + +### Phase 5: Update UI Components + +**1. Add controls in ConfigPanel** (if using smart mode): + - Add "Borrow Notation" dropdown (always/never/whenBorrowing/etc.) + - Add "Borrowing Hints" dropdown (always/never/whenBorrowing/etc.) + - Only show when `operator` is 'subtraction' or 'mixed' + +**2. Add preview in DisplayOptionsPreview:** + - Show subtraction example with borrow notation enabled + - Show subtraction example with borrowing hints enabled + +## Pedagogical Rationale + +### Why This Progression Makes Sense + +1. **Early Learners (Levels 0-2):** + - Show ALL scaffolding including hints with arrows + - Students need maximum support to understand borrowing concept + - Visual hints show "where to write what" + +2. **Intermediate (Levels 3-6):** + - Fade hints to only show when borrowing happens + - Keep scratch boxes for all borrowing problems + - Students understand concept but need workspace + +3. **Advanced (Levels 7-9):** + - Remove hints entirely (students know the pattern) + - Keep scratch boxes for multi-borrow problems + - Only show aids for complex problems + +4. **Mastery (Level 10+):** + - No subtraction-specific scaffolding + - Students work problems independently + - Standard worksheet format + +### Parallel with Addition + +This mirrors the addition progression: +- Carry boxes fade from "always" → "whenRegrouping" → "whenMultipleRegroups" → "never" +- Borrow notation should follow the same path +- Borrowing hints are MORE specific than carry boxes (like ten-frames), so fade faster + +## Implementation Priority + +**High Priority:** +1. āœ… Add `borrowNotation` and `borrowingHints` to `DisplayRules` interface +2. āœ… Update schemas to include these rules in smart mode +3. āœ… Remove hardcoded `false` values in typstGenerator +4. āœ… Add to default config with sensible defaults + +**Medium Priority:** +5. āœ… Update scaffolding progression +6. āœ… Add to difficulty profiles (earlyLearner, intermediate, etc.) + +**Lower Priority:** +7. āš ļø Update UI components (ConfigPanel, DisplayOptionsPreview) +8. āš ļø Update documentation/help text + +## Migration Strategy + +**Good news:** This is backward compatible! + +- **Manual mode** already has these options, no migration needed +- **Smart mode V4** doesn't have these options yet, so adding them is purely additive +- **Default values** will make existing configs work without changes: + - `borrowNotation: 'whenRegrouping'` - reasonable default + - `borrowingHints: 'never'` - conservative default (advanced feature) + +**No schema version bump needed** - V4 smart mode can be extended with optional fields. + +## Testing Checklist + +After implementation: + +- [ ] Manual mode subtraction worksheets still show borrow notation +- [ ] Manual mode can toggle borrowing hints on/off +- [ ] Smart mode subtraction worksheets show borrow notation based on rules +- [ ] Smart mode subtraction worksheets show hints based on rules +- [ ] Addition worksheets unaffected (no regression) +- [ ] Mixed worksheets apply correct rules per problem +- [ ] Early learner profile shows max scaffolding for subtraction +- [ ] Advanced profile shows minimal scaffolding for subtraction +- [ ] Preview correctly shows/hides features based on rules +- [ ] Saved configs load correctly with new fields + +## Summary + +**Current Status:** Subtraction scaffolding exists but is **manual-only**. Smart mode ignores these features entirely. + +**Key Problem:** Smart difficulty mode is less effective for subtraction than addition because it lacks subtraction-specific scaffolding rules. + +**Solution:** Extend the existing display rules system to include `borrowNotation` and `borrowingHints` as conditional options, following the same pedagogical progression as addition scaffolding. + +**Effort:** Medium (2-3 hours) +- Schema updates: 30 min +- Display rules updates: 30 min +- Scaffolding progression: 1 hour +- UI updates: 1-2 hours +- Testing: 1 hour + +**Impact:** High - Makes smart mode equally effective for subtraction as it is for addition. diff --git a/packages/abacus-react/src/AbacusReact.tsx b/packages/abacus-react/src/AbacusReact.tsx index 9bf9721e..578291b5 100644 --- a/packages/abacus-react/src/AbacusReact.tsx +++ b/packages/abacus-react/src/AbacusReact.tsx @@ -2431,6 +2431,7 @@ export const AbacusReact: React.FC = ({ fontFamily: "monospace", fontWeight: "bold", fontSize: `${Math.max(8, 14 * finalConfig.scaleFactor)}px`, + color: themeAwareCustomStyles?.numerals?.color || "rgba(0, 0, 0, 0.8)", }} /> diff --git a/packages/abacus-react/src/AbacusSVGRenderer.tsx b/packages/abacus-react/src/AbacusSVGRenderer.tsx index dec80252..de0b8361 100644 --- a/packages/abacus-react/src/AbacusSVGRenderer.tsx +++ b/packages/abacus-react/src/AbacusSVGRenderer.tsx @@ -431,7 +431,9 @@ export function AbacusSVGRenderer({ })} {/* Column numbers */} - {showNumbers && + {/* NumberFlow numerals are now rendered in AbacusReact.tsx, not here */} + {/* Keeping this code commented for reference - SVG text numerals replaced by NumberFlow */} + {false && showNumbers && beadConfigs.map((_, colIndex) => { const placeValue = columns - 1 - colIndex; const columnState = state[placeValue] || {