fix(worksheets): add borrowNotation and borrowingHints to validation fallback
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 <noreply@anthropic.com>
This commit is contained in:
parent
5a8fc5735d
commit
3f700af643
|
|
@ -1,98 +1,105 @@
|
||||||
// Validation logic for worksheet configuration
|
// Validation logic for worksheet configuration
|
||||||
|
|
||||||
import type { WorksheetFormState, WorksheetConfig, ValidationResult } from './types'
|
import type {
|
||||||
import type { DisplayRules } from './displayRules'
|
WorksheetFormState,
|
||||||
|
WorksheetConfig,
|
||||||
|
ValidationResult,
|
||||||
|
} from "./types";
|
||||||
|
import type { DisplayRules } from "./displayRules";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get current date formatted as "Month Day, Year"
|
* Get current date formatted as "Month Day, Year"
|
||||||
*/
|
*/
|
||||||
function getDefaultDate(): string {
|
function getDefaultDate(): string {
|
||||||
const now = new Date()
|
const now = new Date();
|
||||||
return now.toLocaleDateString('en-US', {
|
return now.toLocaleDateString("en-US", {
|
||||||
month: 'long',
|
month: "long",
|
||||||
day: 'numeric',
|
day: "numeric",
|
||||||
year: 'numeric',
|
year: "numeric",
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validate and create complete config from partial form state
|
* Validate and create complete config from partial form state
|
||||||
*/
|
*/
|
||||||
export function validateWorksheetConfig(formState: WorksheetFormState): ValidationResult {
|
export function validateWorksheetConfig(
|
||||||
const errors: string[] = []
|
formState: WorksheetFormState,
|
||||||
|
): ValidationResult {
|
||||||
|
const errors: string[] = [];
|
||||||
|
|
||||||
// Validate total (must be positive, reasonable limit)
|
// Validate total (must be positive, reasonable limit)
|
||||||
const total = formState.total ?? 20
|
const total = formState.total ?? 20;
|
||||||
if (total < 1 || total > 100) {
|
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
|
// Validate cols and auto-calculate rows
|
||||||
const cols = formState.cols ?? 4
|
const cols = formState.cols ?? 4;
|
||||||
if (cols < 1 || cols > 10) {
|
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
|
// Auto-calculate rows to fit all problems
|
||||||
const rows = Math.ceil(total / cols)
|
const rows = Math.ceil(total / cols);
|
||||||
|
|
||||||
// Validate probabilities (0-1 range)
|
// Validate probabilities (0-1 range)
|
||||||
const pAnyStart = formState.pAnyStart ?? 0.75
|
const pAnyStart = formState.pAnyStart ?? 0.75;
|
||||||
const pAllStart = formState.pAllStart ?? 0.25
|
const pAllStart = formState.pAllStart ?? 0.25;
|
||||||
if (pAnyStart < 0 || pAnyStart > 1) {
|
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) {
|
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) {
|
if (pAllStart > pAnyStart) {
|
||||||
errors.push('pAllStart cannot be greater than pAnyStart')
|
errors.push("pAllStart cannot be greater than pAnyStart");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate fontSize
|
// Validate fontSize
|
||||||
const fontSize = formState.fontSize ?? 16
|
const fontSize = formState.fontSize ?? 16;
|
||||||
if (fontSize < 8 || fontSize > 32) {
|
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)
|
// V4: Validate digitRange (min and max must be 1-5, min <= max)
|
||||||
// Note: Same range applies to both addition and subtraction
|
// 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) {
|
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) {
|
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) {
|
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)
|
// V4: Validate operator (addition, subtraction, or mixed)
|
||||||
const operator = formState.operator ?? 'addition'
|
const operator = formState.operator ?? "addition";
|
||||||
if (!['addition', 'subtraction', 'mixed'].includes(operator)) {
|
if (!["addition", "subtraction", "mixed"].includes(operator)) {
|
||||||
errors.push('Operator must be "addition", "subtraction", or "mixed"')
|
errors.push('Operator must be "addition", "subtraction", or "mixed"');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate seed (must be positive integer)
|
// 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) {
|
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) {
|
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)
|
// 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
|
// Get primary state values
|
||||||
const problemsPerPage = formState.problemsPerPage ?? total
|
const problemsPerPage = formState.problemsPerPage ?? total;
|
||||||
const pages = formState.pages ?? 1
|
const pages = formState.pages ?? 1;
|
||||||
|
|
||||||
// Determine mode (default to 'smart' if not specified)
|
// Determine mode (default to 'smart' if not specified)
|
||||||
const mode = formState.mode ?? 'smart'
|
const mode = formState.mode ?? "smart";
|
||||||
|
|
||||||
// Shared fields for both modes
|
// Shared fields for both modes
|
||||||
const sharedFields = {
|
const sharedFields = {
|
||||||
|
|
@ -107,7 +114,7 @@ export function validateWorksheetConfig(formState: WorksheetFormState): Validati
|
||||||
rows,
|
rows,
|
||||||
|
|
||||||
// Other fields
|
// Other fields
|
||||||
name: formState.name?.trim() || 'Student',
|
name: formState.name?.trim() || "Student",
|
||||||
date: formState.date?.trim() || getDefaultDate(),
|
date: formState.date?.trim() || getDefaultDate(),
|
||||||
pAnyStart,
|
pAnyStart,
|
||||||
pAllStart,
|
pAllStart,
|
||||||
|
|
@ -117,12 +124,12 @@ export function validateWorksheetConfig(formState: WorksheetFormState): Validati
|
||||||
digitRange,
|
digitRange,
|
||||||
|
|
||||||
// V4: Operator selection (addition, subtraction, or mixed)
|
// V4: Operator selection (addition, subtraction, or mixed)
|
||||||
operator: formState.operator ?? 'addition',
|
operator: formState.operator ?? "addition",
|
||||||
|
|
||||||
// Layout
|
// Layout
|
||||||
page: {
|
page: {
|
||||||
wIn: orientation === 'portrait' ? 8.5 : 11,
|
wIn: orientation === "portrait" ? 8.5 : 11,
|
||||||
hIn: orientation === 'portrait' ? 11 : 8.5,
|
hIn: orientation === "portrait" ? 11 : 8.5,
|
||||||
},
|
},
|
||||||
margins: {
|
margins: {
|
||||||
left: 0.6,
|
left: 0.6,
|
||||||
|
|
@ -133,34 +140,37 @@ export function validateWorksheetConfig(formState: WorksheetFormState): Validati
|
||||||
|
|
||||||
fontSize,
|
fontSize,
|
||||||
seed,
|
seed,
|
||||||
}
|
};
|
||||||
|
|
||||||
// Build mode-specific config
|
// Build mode-specific config
|
||||||
let config: WorksheetConfig
|
let config: WorksheetConfig;
|
||||||
|
|
||||||
if (mode === 'smart') {
|
if (mode === "smart") {
|
||||||
// Smart mode: Use displayRules for conditional scaffolding
|
// Smart mode: Use displayRules for conditional scaffolding
|
||||||
const displayRules: DisplayRules = formState.displayRules ?? {
|
const displayRules: DisplayRules = {
|
||||||
carryBoxes: 'whenRegrouping',
|
carryBoxes: "whenRegrouping",
|
||||||
answerBoxes: 'always',
|
answerBoxes: "always",
|
||||||
placeValueColors: 'always',
|
placeValueColors: "always",
|
||||||
tenFrames: 'whenRegrouping',
|
tenFrames: "whenRegrouping",
|
||||||
problemNumbers: 'always',
|
problemNumbers: "always",
|
||||||
cellBorders: '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 = {
|
config = {
|
||||||
version: 4,
|
version: 4,
|
||||||
mode: 'smart',
|
mode: "smart",
|
||||||
displayRules,
|
displayRules,
|
||||||
difficultyProfile: formState.difficultyProfile,
|
difficultyProfile: formState.difficultyProfile,
|
||||||
...sharedFields,
|
...sharedFields,
|
||||||
}
|
};
|
||||||
} else {
|
} else {
|
||||||
// Manual mode: Use boolean flags for uniform display
|
// Manual mode: Use boolean flags for uniform display
|
||||||
config = {
|
config = {
|
||||||
version: 4,
|
version: 4,
|
||||||
mode: 'manual',
|
mode: "manual",
|
||||||
showCarryBoxes: formState.showCarryBoxes ?? true,
|
showCarryBoxes: formState.showCarryBoxes ?? true,
|
||||||
showAnswerBoxes: formState.showAnswerBoxes ?? true,
|
showAnswerBoxes: formState.showAnswerBoxes ?? true,
|
||||||
showPlaceValueColors: formState.showPlaceValueColors ?? true,
|
showPlaceValueColors: formState.showPlaceValueColors ?? true,
|
||||||
|
|
@ -172,8 +182,8 @@ export function validateWorksheetConfig(formState: WorksheetFormState): Validati
|
||||||
showBorrowingHints: formState.showBorrowingHints ?? false,
|
showBorrowingHints: formState.showBorrowingHints ?? false,
|
||||||
manualPreset: formState.manualPreset,
|
manualPreset: formState.manualPreset,
|
||||||
...sharedFields,
|
...sharedFields,
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return { isValid: true, config }
|
return { isValid: true, config };
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue