feat(worksheets): Add borrow notation scaffolding for subtraction
Implements empty scratch boxes to guide students through borrowing work without doing the arithmetic for them. Scaffolding design: 1. **Source digit box** (above) - Student crosses out original and writes reduced value (e.g., cross out 5, write 4) 2. **Destination digit box** (left side) - Student writes modified value after adding 10 (e.g., write "12" next to 2) Both boxes: - Dotted borders to indicate workspace - ~35-50% size of main digit cells - Positioned close to the digits they modify - Only appear where borrowing is needed Implementation: - Add `showBorrowNotation` boolean to V4 manual schema - Update Typst rendering with conditional notation rows - Add UI toggle (only shows for subtraction/mixed modes) - Include in validation and auto-save persistence - Update typstGenerator and example routes Pedagogical approach: Shows WHERE and provides space for WHAT, but student must determine and write the actual values. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
13dfb128a2
commit
ff161d4e30
|
|
@ -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)
|
||||
]
|
||||
`
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2285,6 +2285,15 @@ export function ConfigPanel({ formState, onChange }: ConfigPanelProps) {
|
|||
description="Organize problems visually for easier focus"
|
||||
/>
|
||||
|
||||
{formState.operator === 'subtraction' || formState.operator === 'mixed' ? (
|
||||
<ToggleOption
|
||||
checked={formState.showBorrowNotation ?? false}
|
||||
onChange={(checked) => onChange({ showBorrowNotation: checked })}
|
||||
label="Borrow Notation Boxes"
|
||||
description="Empty scratch boxes for students to write borrowing work (cross out source, write modified values)"
|
||||
/>
|
||||
) : null}
|
||||
|
||||
<ToggleOption
|
||||
checked={formState.showTenFrames ?? false}
|
||||
onChange={(checked) => {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
)
|
||||
}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -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})[
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
|
|
|
|||
Loading…
Reference in New Issue