feat(worksheets): update validation and generation for V3 mode-aware schema

validation.ts:
- Build mode-specific configs (Smart vs Manual)
- Smart mode: uses difficultyProfile + displayRules
- Manual mode: uses boolean flags for display options

typstGenerator.ts:
- Per-problem display enrichment based on mode
- Smart mode: conditional scaffolding via analyzeProblem + resolveDisplayForProblem
- Manual mode: uniform display across all problems
- Fix showTenFramesForAll property access (Manual mode only)

page.tsx:
- Server-side settings loading with V3 schema
- Auto-migration from V2→V3 via parseAdditionConfig
This commit is contained in:
Thomas Hallock
2025-11-07 17:01:27 -06:00
parent 4ffd47a6b6
commit ada96005f5
3 changed files with 66 additions and 63 deletions

View File

@@ -45,7 +45,7 @@ async function loadWorksheetSettings(): Promise<
return {
...defaultAdditionConfig,
seed: Date.now() % 2147483647,
}
} as unknown as Omit<WorksheetFormState, 'date' | 'rows' | 'total'>
}
// Parse and validate config (auto-migrates to latest version)
@@ -53,14 +53,14 @@ async function loadWorksheetSettings(): Promise<
return {
...config,
seed: Date.now() % 2147483647,
}
} as unknown as Omit<WorksheetFormState, 'date' | 'rows' | 'total'>
} catch (error) {
console.error('Failed to load worksheet settings:', error)
// Return defaults on error with a stable seed
return {
...defaultAdditionConfig,
seed: Date.now() % 2147483647,
}
} as unknown as Omit<WorksheetFormState, 'date' | 'rows' | 'total'>
}
}

View File

@@ -25,13 +25,27 @@ function generatePageTypst(
problemOffset: number,
rowsPerPage: number
): string {
// Analyze each problem and resolve display options
// Enrich problems with display options based on mode
const enrichedProblems = pageProblems.map((p) => {
const meta = analyzeProblem(p.a, p.b)
const displayOptions = resolveDisplayForProblem(config.displayRules, meta)
return {
...p,
...displayOptions,
if (config.mode === 'smart') {
// Smart mode: Per-problem conditional display based on problem complexity
const meta = analyzeProblem(p.a, p.b)
const displayOptions = resolveDisplayForProblem(config.displayRules, meta)
return {
...p,
...displayOptions,
}
} else {
// Manual mode: Uniform display across all problems
return {
...p,
showCarryBoxes: config.showCarryBoxes,
showAnswerBoxes: config.showAnswerBoxes,
showPlaceValueColors: config.showPlaceValueColors,
showTenFrames: config.showTenFrames,
showProblemNumbers: config.showProblemNumbers,
showCellBorder: config.showCellBorder,
}
}
})
@@ -82,7 +96,7 @@ function generatePageTypst(
#block(breakable: false)[
#let heavy-stroke = 0.8pt
#let show-ten-frames-for-all = ${config.showTenFramesForAll ? 'true' : 'false'}
#let show-ten-frames-for-all = ${config.mode === 'manual' && config.showTenFramesForAll ? 'true' : 'false'}
${generateTypstHelpers(cellSize)}

View File

@@ -72,51 +72,11 @@ export function validateWorksheetConfig(formState: WorksheetFormState): Validati
const problemsPerPage = formState.problemsPerPage ?? total
const pages = formState.pages ?? 1
// Handle V2 displayRules or V1 boolean flags
let displayRules: DisplayRules
let showCarryBoxes: boolean
let showAnswerBoxes: boolean
let showPlaceValueColors: boolean
let showProblemNumbers: boolean
let showCellBorder: boolean
let showTenFrames: boolean
if (formState.displayRules) {
// V2: Use displayRules from formState
displayRules = formState.displayRules
// Derive V1 compatibility flags (use 'always' as true, anything else as false for now)
showCarryBoxes = displayRules.carryBoxes === 'always'
showAnswerBoxes = displayRules.answerBoxes === 'always'
showPlaceValueColors = displayRules.placeValueColors === 'always'
showProblemNumbers = displayRules.problemNumbers === 'always'
showCellBorder = displayRules.cellBorders === 'always'
showTenFrames = displayRules.tenFrames === 'always'
} else {
// V1: Use individual boolean flags, convert to displayRules
showCarryBoxes = formState.showCarryBoxes ?? true
showAnswerBoxes = formState.showAnswerBoxes ?? true
showPlaceValueColors = formState.showPlaceValueColors ?? true
showProblemNumbers = formState.showProblemNumbers ?? true
showCellBorder = formState.showCellBorder ?? true
showTenFrames = formState.showTenFrames ?? false
displayRules = {
carryBoxes: showCarryBoxes ? 'always' : 'never',
answerBoxes: showAnswerBoxes ? 'always' : 'never',
placeValueColors: showPlaceValueColors ? 'always' : 'never',
problemNumbers: showProblemNumbers ? 'always' : 'never',
cellBorders: showCellBorder ? 'always' : 'never',
tenFrames: showTenFrames ? 'always' : 'never',
}
}
// Build complete config with defaults
const config: WorksheetConfig = {
// V2 fields
version: 2,
displayRules,
difficultyProfile: formState.difficultyProfile,
// Determine mode (default to 'smart' if not specified)
const mode = formState.mode ?? 'smart'
// Shared fields for both modes
const sharedFields = {
// Primary state
problemsPerPage,
cols,
@@ -146,18 +106,47 @@ export function validateWorksheetConfig(formState: WorksheetFormState): Validati
bottom: 0.7,
},
// V1 compatibility flags (derived from displayRules)
showCarryBoxes,
showAnswerBoxes,
showPlaceValueColors,
showProblemNumbers,
showCellBorder,
showTenFrames,
showTenFramesForAll: formState.showTenFramesForAll ?? false,
fontSize,
seed,
}
// Build mode-specific config
let config: WorksheetConfig
if (mode === 'smart') {
// Smart mode: Use displayRules for conditional scaffolding
const displayRules: DisplayRules = formState.displayRules ?? {
carryBoxes: 'whenRegrouping',
answerBoxes: 'always',
placeValueColors: 'always',
tenFrames: 'whenRegrouping',
problemNumbers: 'always',
cellBorders: 'always',
}
config = {
version: 3,
mode: 'smart',
displayRules,
difficultyProfile: formState.difficultyProfile,
...sharedFields,
}
} else {
// Manual mode: Use boolean flags for uniform display
config = {
version: 3,
mode: 'manual',
showCarryBoxes: formState.showCarryBoxes ?? true,
showAnswerBoxes: formState.showAnswerBoxes ?? true,
showPlaceValueColors: formState.showPlaceValueColors ?? true,
showTenFrames: formState.showTenFrames ?? false,
showProblemNumbers: formState.showProblemNumbers ?? true,
showCellBorder: formState.showCellBorder ?? true,
showTenFramesForAll: formState.showTenFramesForAll ?? false,
manualPreset: formState.manualPreset,
...sharedFields,
}
}
return { isValid: true, config }
}