diff --git a/apps/web/src/app/api/create/worksheets/addition/example/route.ts b/apps/web/src/app/api/create/worksheets/addition/example/route.ts index e2056e29..1c4d04c9 100644 --- a/apps/web/src/app/api/create/worksheets/addition/example/route.ts +++ b/apps/web/src/app/api/create/worksheets/addition/example/route.ts @@ -134,7 +134,7 @@ ${generateSubtractionProblemStackFunction(cellSize, 3)} #let subtrahend = ${subtrahend} #align(center + horizon)[ - #subtraction-problem-stack(minuend, subtrahend, if show-numbers { 0 } else { none }, show-borrows, show-answers, show-colors, show-ten-frames, show-numbers) + #subtraction-problem-stack(minuend, subtrahend, if show-numbers { 0 } else { none }, show-borrows, show-answers, show-colors, show-ten-frames, show-numbers, false) ] ` } diff --git a/apps/web/src/app/create/worksheets/addition/components/ConfigPanel.tsx b/apps/web/src/app/create/worksheets/addition/components/ConfigPanel.tsx index ccc7eb14..fb450439 100644 --- a/apps/web/src/app/create/worksheets/addition/components/ConfigPanel.tsx +++ b/apps/web/src/app/create/worksheets/addition/components/ConfigPanel.tsx @@ -2285,6 +2285,15 @@ export function ConfigPanel({ formState, onChange }: ConfigPanelProps) { description="Organize problems visually for easier focus" /> + {formState.operator === 'subtraction' || formState.operator === 'mixed' ? ( + onChange({ showBorrowNotation: checked })} + label="Borrow Notation Boxes" + description="Empty scratch boxes for students to write borrowing work (cross out source, write modified values)" + /> + ) : null} + { diff --git a/apps/web/src/app/create/worksheets/addition/hooks/useWorksheetAutoSave.ts b/apps/web/src/app/create/worksheets/addition/hooks/useWorksheetAutoSave.ts index f049498e..d83d99a2 100644 --- a/apps/web/src/app/create/worksheets/addition/hooks/useWorksheetAutoSave.ts +++ b/apps/web/src/app/create/worksheets/addition/hooks/useWorksheetAutoSave.ts @@ -63,6 +63,7 @@ export function useWorksheetAutoSave( showCellBorder, showTenFrames, showTenFramesForAll, + showBorrowNotation, fontSize, mode, difficultyProfile, @@ -93,6 +94,7 @@ export function useWorksheetAutoSave( showCellBorder, showTenFrames, showTenFramesForAll, + showBorrowNotation, fontSize, mode, difficultyProfile, diff --git a/apps/web/src/app/create/worksheets/addition/typstGenerator.ts b/apps/web/src/app/create/worksheets/addition/typstGenerator.ts index aeeb8da9..6261c76a 100644 --- a/apps/web/src/app/create/worksheets/addition/typstGenerator.ts +++ b/apps/web/src/app/create/worksheets/addition/typstGenerator.ts @@ -108,6 +108,7 @@ function generatePageTypst( showTenFrames: config.showTenFrames, showProblemNumbers: config.showProblemNumbers, showCellBorder: config.showCellBorder, + showBorrowNotation: 'showBorrowNotation' in config ? config.showBorrowNotation : false, } } }) @@ -116,9 +117,9 @@ function generatePageTypst( const problemsTypst = enrichedProblems .map((p) => { if (p.operator === '+') { - return ` (operator: "+", a: ${p.a}, b: ${p.b}, showCarryBoxes: ${p.showCarryBoxes}, showAnswerBoxes: ${p.showAnswerBoxes}, showPlaceValueColors: ${p.showPlaceValueColors}, showTenFrames: ${p.showTenFrames}, showProblemNumbers: ${p.showProblemNumbers}, showCellBorder: ${p.showCellBorder}),` + return ` (operator: "+", a: ${p.a}, b: ${p.b}, showCarryBoxes: ${p.showCarryBoxes}, showAnswerBoxes: ${p.showAnswerBoxes}, showPlaceValueColors: ${p.showPlaceValueColors}, showTenFrames: ${p.showTenFrames}, showProblemNumbers: ${p.showProblemNumbers}, showCellBorder: ${p.showCellBorder}, showBorrowNotation: ${p.showBorrowNotation}),` } else { - return ` (operator: "−", minuend: ${p.minuend}, subtrahend: ${p.subtrahend}, showCarryBoxes: ${p.showCarryBoxes}, showAnswerBoxes: ${p.showAnswerBoxes}, showPlaceValueColors: ${p.showPlaceValueColors}, showTenFrames: ${p.showTenFrames}, showProblemNumbers: ${p.showProblemNumbers}, showCellBorder: ${p.showCellBorder}),` + return ` (operator: "−", minuend: ${p.minuend}, subtrahend: ${p.subtrahend}, showCarryBoxes: ${p.showCarryBoxes}, showAnswerBoxes: ${p.showAnswerBoxes}, showPlaceValueColors: ${p.showPlaceValueColors}, showTenFrames: ${p.showTenFrames}, showProblemNumbers: ${p.showProblemNumbers}, showCellBorder: ${p.showCellBorder}, showBorrowNotation: ${p.showBorrowNotation}),` } }) .join('\n') @@ -214,7 +215,8 @@ ${generateSubtractionProblemStackFunction(cellSize, maxDigits)} problem.showAnswerBoxes, problem.showPlaceValueColors, problem.showTenFrames, - problem.showProblemNumbers + problem.showProblemNumbers, + problem.showBorrowNotation ) } ] diff --git a/apps/web/src/app/create/worksheets/addition/typstHelpers.ts b/apps/web/src/app/create/worksheets/addition/typstHelpers.ts index b6e662b4..04b92109 100644 --- a/apps/web/src/app/create/worksheets/addition/typstHelpers.ts +++ b/apps/web/src/app/create/worksheets/addition/typstHelpers.ts @@ -355,8 +355,8 @@ export function generateSubtractionProblemStackFunction( return String.raw` // Subtraction problem rendering function (supports 1-${maxDigits} digit problems) // Returns the stack/grid structure for rendering a single subtraction problem -// Per-problem display flags: show-borrows, show-answers, show-colors, show-ten-frames, show-numbers -#let subtraction-problem-stack(minuend, subtrahend, index-or-none, show-borrows, show-answers, show-colors, show-ten-frames, show-numbers) = { +// Per-problem display flags: show-borrows, show-answers, show-colors, show-ten-frames, show-numbers, show-borrow-notation +#let subtraction-problem-stack(minuend, subtrahend, index-or-none, show-borrows, show-answers, show-colors, show-ten-frames, show-numbers, show-borrow-notation) = { // Place value colors array for dynamic lookup let place-colors = (${placeColors.join(', ')}) @@ -450,20 +450,74 @@ export function generateSubtractionProblemStackFunction( } }, - // Minuend row (top number) + // Borrow notation row (scratch boxes for student work) + ..if show-borrow-notation { + ( + [], // Empty cell for operator column + ..for i in range(0, grid-digits).rev() { + // Check if this place needs borrowing FROM (source) + // We borrow FROM position i when position i-1 needs to borrow + let is-source = i > 0 and (m-digits.at(i - 1) < s-digits.at(i - 1)) + + if is-source and i <= m-highest { + // Small dotted box above this digit for student to write reduced value + (box(width: ${cellSizeIn}, height: ${cellSizeIn} * 0.4)[ + #align(center + bottom)[ + #box( + width: ${cellSizeIn} * 0.5, + height: ${cellSizeIn} * 0.35, + stroke: (dash: "dotted", thickness: 0.5pt, paint: gray) + )[] + ] + ],) + } else { + // No notation needed + (v(${cellSizeIn} * 0.4),) + } + }, + ) + } else { + () + }, + + // Minuend row (top number with optional borrow notation) [], // Empty cell for operator column ..for i in range(0, grid-digits).rev() { let digit = m-digits.at(i) let place-color = place-colors.at(i) let fill-color = if show-colors { place-color } else { color-none } + // Check if this place needs to borrow (destination) + let needs-borrow = i < grid-digits and (m-digits.at(i) < s-digits.at(i)) + // Show digit if within minuend's actual range if i <= m-highest { - (box(width: ${cellSizeIn}, height: ${cellSizeIn}, fill: fill-color)[ - #align(center + horizon)[ - #text(size: ${cellSizePt.toFixed(1)}pt, font: "New Computer Modern Math")[#str(digit)] - ] - ],) + if show-borrow-notation and needs-borrow { + // Show digit with small scratch box to the left for modified value (e.g., "12") + (box(width: ${cellSizeIn}, height: ${cellSizeIn}, fill: fill-color)[ + #stack( + dir: ltr, + spacing: 2pt, + // Small dotted box for student to write modified digit + box( + width: ${cellSizeIn} * 0.35, + height: ${cellSizeIn} * 0.35, + stroke: (dash: "dotted", thickness: 0.5pt, paint: gray) + )[], + // Original digit + align(center + horizon)[ + #text(size: ${cellSizePt.toFixed(1)}pt, font: "New Computer Modern Math")[#str(digit)] + ] + ) + ],) + } else { + // Normal digit display + (box(width: ${cellSizeIn}, height: ${cellSizeIn}, fill: fill-color)[ + #align(center + horizon)[ + #text(size: ${cellSizePt.toFixed(1)}pt, font: "New Computer Modern Math")[#str(digit)] + ] + ],) + } } else { // Leading zero position - don't show (box(width: ${cellSizeIn}, height: ${cellSizeIn})[ diff --git a/apps/web/src/app/create/worksheets/addition/validation.ts b/apps/web/src/app/create/worksheets/addition/validation.ts index 55cfd492..64e215af 100644 --- a/apps/web/src/app/create/worksheets/addition/validation.ts +++ b/apps/web/src/app/create/worksheets/addition/validation.ts @@ -168,6 +168,7 @@ export function validateWorksheetConfig(formState: WorksheetFormState): Validati showProblemNumbers: formState.showProblemNumbers ?? true, showCellBorder: formState.showCellBorder ?? true, showTenFramesForAll: formState.showTenFramesForAll ?? false, + showBorrowNotation: formState.showBorrowNotation ?? false, manualPreset: formState.manualPreset, ...sharedFields, } diff --git a/apps/web/src/app/create/worksheets/config-schemas.ts b/apps/web/src/app/create/worksheets/config-schemas.ts index cf0104e3..84fa59f0 100644 --- a/apps/web/src/app/create/worksheets/config-schemas.ts +++ b/apps/web/src/app/create/worksheets/config-schemas.ts @@ -329,6 +329,7 @@ const additionConfigV4ManualSchema = additionConfigV4BaseSchema.extend({ showProblemNumbers: z.boolean(), showCellBorder: z.boolean(), showTenFramesForAll: z.boolean(), + showBorrowNotation: z.boolean(), // Scratch boxes for borrowing work // Optional: Which manual preset is selected manualPreset: z.string().optional(),