Files
soroban-abacus-flashcards/apps/web/scripts/traceDifficultyPath.ts
Thomas Hallock 7c33d0246f fix: prevent undefined displayRules error in worksheet generator
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>
2025-11-07 13:01:54 -06:00

108 lines
3.1 KiB
TypeScript

import {
DIFFICULTY_PROFILES,
makeHarder,
makeEasier,
findRegroupingIndex,
findScaffoldingIndex,
REGROUPING_PROGRESSION,
SCAFFOLDING_PROGRESSION,
} from '../src/app/create/worksheets/addition/difficultyProfiles'
// Start from beginner
let state = {
pAnyStart: DIFFICULTY_PROFILES.beginner.regrouping.pAnyStart,
pAllStart: DIFFICULTY_PROFILES.beginner.regrouping.pAllStart,
displayRules: DIFFICULTY_PROFILES.beginner.displayRules,
}
console.log('=== MAKE HARDER PATH ===\n')
console.log('Format: (regroupingIdx, scaffoldingIdx) - description\n')
const harderPath: Array<{ r: number; s: number; desc: string }> = []
// Record starting point
let rIdx = findRegroupingIndex(state.pAnyStart, state.pAllStart)
let sIdx = findScaffoldingIndex(state.displayRules)
harderPath.push({ r: rIdx, s: sIdx, desc: 'START (beginner)' })
console.log(`(${rIdx}, ${sIdx}) - START (beginner)`)
// Click "Make Harder" 30 times or until max
for (let i = 0; i < 30; i++) {
const result = makeHarder(state)
const newR = findRegroupingIndex(result.pAnyStart, result.pAllStart)
const newS = findScaffoldingIndex(result.displayRules)
if (newR === rIdx && newS === sIdx) {
console.log(`\n(${newR}, ${newS}) - ${result.changeDescription} (STOPPED)`)
break
}
rIdx = newR
sIdx = newS
state = result
harderPath.push({ r: rIdx, s: sIdx, desc: result.changeDescription })
console.log(`(${rIdx}, ${sIdx}) - ${result.changeDescription}`)
}
console.log('\n\n=== PATH VISUALIZATION ===\n')
console.log('Regrouping Index →')
console.log('Scaffolding ↓\n')
// Create 2D grid visualization
const grid: string[][] = []
for (let s = 0; s <= 12; s++) {
grid[s] = []
for (let r = 0; r <= 18; r++) {
grid[s][r] = ' ·'
}
}
// Mark path
harderPath.forEach((point, idx) => {
if (idx === 0) {
grid[point.s][point.r] = ' S' // Start
} else if (idx === harderPath.length - 1) {
grid[point.s][point.r] = ' E' // End
} else {
grid[point.s][point.r] = `${idx.toString().padStart(3)}`
}
})
// Mark presets
const presets = [
{ label: 'BEG', profile: DIFFICULTY_PROFILES.beginner },
{ label: 'EAR', profile: DIFFICULTY_PROFILES.earlyLearner },
{ label: 'INT', profile: DIFFICULTY_PROFILES.intermediate },
{ label: 'ADV', profile: DIFFICULTY_PROFILES.advanced },
{ label: 'EXP', profile: DIFFICULTY_PROFILES.expert },
]
presets.forEach((preset) => {
const r = findRegroupingIndex(
preset.profile.regrouping.pAnyStart,
preset.profile.regrouping.pAllStart
)
const s = findScaffoldingIndex(preset.profile.displayRules)
// Only mark if not already part of path
const onPath = harderPath.some((p) => p.r === r && p.s === s)
if (!onPath) {
grid[s][r] = preset.label
}
})
// Print grid (inverted so scaffolding increases upward)
console.log(' 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18')
for (let s = 12; s >= 0; s--) {
console.log(`${s.toString().padStart(2)} ${grid[s].join('')}`)
}
console.log('\nLegend:')
console.log(' S = Start (beginner)')
console.log(' E = End (maximum)')
console.log(' 1-29 = Step number')
console.log(' BEG/EAR/INT/ADV/EXP = Preset profiles')
console.log(' · = Not visited')