From 3f700af6432735aaab4387aabcc2fc302921869d Mon Sep 17 00:00:00 2001 From: Thomas Hallock Date: Sat, 8 Nov 2025 14:55:31 -0600 Subject: [PATCH] fix(worksheets): add borrowNotation and borrowingHints to validation fallback MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add missing subtraction-specific scaffold fields to the fallback displayRules object in validation.ts to ensure all DisplayRules fields have defaults. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .../create/worksheets/addition/validation.ts | 122 ++++++++++-------- 1 file changed, 66 insertions(+), 56 deletions(-) diff --git a/apps/web/src/app/create/worksheets/addition/validation.ts b/apps/web/src/app/create/worksheets/addition/validation.ts index 2e77e0a5..0360c77a 100644 --- a/apps/web/src/app/create/worksheets/addition/validation.ts +++ b/apps/web/src/app/create/worksheets/addition/validation.ts @@ -1,98 +1,105 @@ // Validation logic for worksheet configuration -import type { WorksheetFormState, WorksheetConfig, ValidationResult } from './types' -import type { DisplayRules } from './displayRules' +import type { + WorksheetFormState, + WorksheetConfig, + ValidationResult, +} from "./types"; +import type { DisplayRules } from "./displayRules"; /** * Get current date formatted as "Month Day, Year" */ function getDefaultDate(): string { - const now = new Date() - return now.toLocaleDateString('en-US', { - month: 'long', - day: 'numeric', - year: 'numeric', - }) + const now = new Date(); + return now.toLocaleDateString("en-US", { + month: "long", + day: "numeric", + year: "numeric", + }); } /** * Validate and create complete config from partial form state */ -export function validateWorksheetConfig(formState: WorksheetFormState): ValidationResult { - const errors: string[] = [] +export function validateWorksheetConfig( + formState: WorksheetFormState, +): ValidationResult { + const errors: string[] = []; // Validate total (must be positive, reasonable limit) - const total = formState.total ?? 20 + const total = formState.total ?? 20; if (total < 1 || total > 100) { - errors.push('Total problems must be between 1 and 100') + errors.push("Total problems must be between 1 and 100"); } // Validate cols and auto-calculate rows - const cols = formState.cols ?? 4 + const cols = formState.cols ?? 4; if (cols < 1 || cols > 10) { - errors.push('Columns must be between 1 and 10') + errors.push("Columns must be between 1 and 10"); } // Auto-calculate rows to fit all problems - const rows = Math.ceil(total / cols) + const rows = Math.ceil(total / cols); // Validate probabilities (0-1 range) - const pAnyStart = formState.pAnyStart ?? 0.75 - const pAllStart = formState.pAllStart ?? 0.25 + const pAnyStart = formState.pAnyStart ?? 0.75; + const pAllStart = formState.pAllStart ?? 0.25; if (pAnyStart < 0 || pAnyStart > 1) { - errors.push('pAnyStart must be between 0 and 1') + errors.push("pAnyStart must be between 0 and 1"); } if (pAllStart < 0 || pAllStart > 1) { - errors.push('pAllStart must be between 0 and 1') + errors.push("pAllStart must be between 0 and 1"); } if (pAllStart > pAnyStart) { - errors.push('pAllStart cannot be greater than pAnyStart') + errors.push("pAllStart cannot be greater than pAnyStart"); } // Validate fontSize - const fontSize = formState.fontSize ?? 16 + const fontSize = formState.fontSize ?? 16; if (fontSize < 8 || fontSize > 32) { - errors.push('Font size must be between 8 and 32') + errors.push("Font size must be between 8 and 32"); } // V4: Validate digitRange (min and max must be 1-5, min <= max) // Note: Same range applies to both addition and subtraction - const digitRange = formState.digitRange ?? { min: 2, max: 2 } + const digitRange = formState.digitRange ?? { min: 2, max: 2 }; if (!digitRange.min || digitRange.min < 1 || digitRange.min > 5) { - errors.push('Digit range min must be between 1 and 5') + errors.push("Digit range min must be between 1 and 5"); } if (!digitRange.max || digitRange.max < 1 || digitRange.max > 5) { - errors.push('Digit range max must be between 1 and 5') + errors.push("Digit range max must be between 1 and 5"); } if (digitRange.min > digitRange.max) { - errors.push('Digit range min cannot be greater than max') + errors.push("Digit range min cannot be greater than max"); } // V4: Validate operator (addition, subtraction, or mixed) - const operator = formState.operator ?? 'addition' - if (!['addition', 'subtraction', 'mixed'].includes(operator)) { - errors.push('Operator must be "addition", "subtraction", or "mixed"') + const operator = formState.operator ?? "addition"; + if (!["addition", "subtraction", "mixed"].includes(operator)) { + errors.push('Operator must be "addition", "subtraction", or "mixed"'); } // Validate seed (must be positive integer) - const seed = formState.seed ?? Date.now() % 2147483647 + const seed = formState.seed ?? Date.now() % 2147483647; if (!Number.isInteger(seed) || seed < 0) { - errors.push('Seed must be a non-negative integer') + errors.push("Seed must be a non-negative integer"); } if (errors.length > 0) { - return { isValid: false, errors } + return { isValid: false, errors }; } // Determine orientation based on columns (portrait = 2-3 cols, landscape = 4-5 cols) - const orientation = formState.orientation || (cols <= 3 ? 'portrait' : 'landscape') + const orientation = + formState.orientation || (cols <= 3 ? "portrait" : "landscape"); // Get primary state values - const problemsPerPage = formState.problemsPerPage ?? total - const pages = formState.pages ?? 1 + const problemsPerPage = formState.problemsPerPage ?? total; + const pages = formState.pages ?? 1; // Determine mode (default to 'smart' if not specified) - const mode = formState.mode ?? 'smart' + const mode = formState.mode ?? "smart"; // Shared fields for both modes const sharedFields = { @@ -107,7 +114,7 @@ export function validateWorksheetConfig(formState: WorksheetFormState): Validati rows, // Other fields - name: formState.name?.trim() || 'Student', + name: formState.name?.trim() || "Student", date: formState.date?.trim() || getDefaultDate(), pAnyStart, pAllStart, @@ -117,12 +124,12 @@ export function validateWorksheetConfig(formState: WorksheetFormState): Validati digitRange, // V4: Operator selection (addition, subtraction, or mixed) - operator: formState.operator ?? 'addition', + operator: formState.operator ?? "addition", // Layout page: { - wIn: orientation === 'portrait' ? 8.5 : 11, - hIn: orientation === 'portrait' ? 11 : 8.5, + wIn: orientation === "portrait" ? 8.5 : 11, + hIn: orientation === "portrait" ? 11 : 8.5, }, margins: { left: 0.6, @@ -133,34 +140,37 @@ export function validateWorksheetConfig(formState: WorksheetFormState): Validati fontSize, seed, - } + }; // Build mode-specific config - let config: WorksheetConfig + let config: WorksheetConfig; - if (mode === 'smart') { + 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', - } + const displayRules: DisplayRules = { + carryBoxes: "whenRegrouping", + answerBoxes: "always", + placeValueColors: "always", + tenFrames: "whenRegrouping", + problemNumbers: "always", + cellBorders: "always", + borrowNotation: "whenRegrouping", // Subtraction: show when borrowing + borrowingHints: "never", // Subtraction: no hints by default + ...((formState.displayRules as any) ?? {}), // Override with provided rules if any + }; config = { version: 4, - mode: 'smart', + mode: "smart", displayRules, difficultyProfile: formState.difficultyProfile, ...sharedFields, - } + }; } else { // Manual mode: Use boolean flags for uniform display config = { version: 4, - mode: 'manual', + mode: "manual", showCarryBoxes: formState.showCarryBoxes ?? true, showAnswerBoxes: formState.showAnswerBoxes ?? true, showPlaceValueColors: formState.showPlaceValueColors ?? true, @@ -172,8 +182,8 @@ export function validateWorksheetConfig(formState: WorksheetFormState): Validati showBorrowingHints: formState.showBorrowingHints ?? false, manualPreset: formState.manualPreset, ...sharedFields, - } + }; } - return { isValid: true, config } + return { isValid: true, config }; }