feat(worksheets): filter operator-specific scaffolds from difficulty change descriptions
When adjusting difficulty via Make Harder/Easier buttons, filter out scaffolds that don't apply to the current operator: - Addition-only: hide borrowNotation, borrowingHints from descriptions - Subtraction-only: hide carryBoxes, tenFrames from descriptions - Mixed: show all scaffolds Updated functions: - describeScaffoldingChange(): added operator parameter with filtering logic - makeHarder(): passes operator to describeScaffoldingChange - makeEasier(): passes operator to describeScaffoldingChange - SmartModeControls: passes formState.operator to both functions 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
d23b606642
commit
cace1c75c6
|
|
@ -23,6 +23,7 @@ import {
|
|||
type DifficultyLevel,
|
||||
type DifficultyMode,
|
||||
} from '../../difficultyProfiles'
|
||||
import type { DisplayRules } from '../../displayRules'
|
||||
import { getScaffoldingSummary } from './utils'
|
||||
|
||||
export interface SmartModeControlsProps {
|
||||
|
|
@ -37,7 +38,7 @@ export function SmartModeControls({ formState, onChange, isDark = false }: Smart
|
|||
const [hoverPreview, setHoverPreview] = useState<{
|
||||
pAnyStart: number
|
||||
pAllStart: number
|
||||
displayRules: any
|
||||
displayRules: DisplayRules
|
||||
matchedProfile: string | 'custom'
|
||||
} | null>(null)
|
||||
|
||||
|
|
@ -50,13 +51,16 @@ export function SmartModeControls({ formState, onChange, isDark = false }: Smart
|
|||
}
|
||||
|
||||
const result =
|
||||
direction === 'harder' ? makeHarder(currentState, mode) : makeEasier(currentState, mode)
|
||||
direction === 'harder'
|
||||
? makeHarder(currentState, mode, formState.operator)
|
||||
: makeEasier(currentState, mode, formState.operator)
|
||||
|
||||
onChange({
|
||||
pAnyStart: result.pAnyStart,
|
||||
pAllStart: result.pAllStart,
|
||||
displayRules: result.displayRules,
|
||||
difficultyProfile: result.matchedProfile !== 'custom' ? result.matchedProfile : undefined,
|
||||
difficultyProfile:
|
||||
result.difficultyProfile !== 'custom' ? result.difficultyProfile : undefined,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -157,7 +161,7 @@ export function SmartModeControls({ formState, onChange, isDark = false }: Smart
|
|||
|
||||
// Generate custom description
|
||||
const regroupingPercent = Math.round(pAnyStart * 100)
|
||||
const scaffoldingSummary = getScaffoldingSummary(displayRules)
|
||||
const scaffoldingSummary = getScaffoldingSummary(displayRules, formState.operator)
|
||||
customDescription = (
|
||||
<>
|
||||
<div>{regroupingPercent}% regrouping</div>
|
||||
|
|
@ -176,7 +180,8 @@ export function SmartModeControls({ formState, onChange, isDark = false }: Smart
|
|||
pAllStart,
|
||||
displayRules,
|
||||
},
|
||||
'both'
|
||||
'both',
|
||||
formState.operator
|
||||
)
|
||||
|
||||
const easierResultChallenge = makeEasier(
|
||||
|
|
@ -185,7 +190,8 @@ export function SmartModeControls({ formState, onChange, isDark = false }: Smart
|
|||
pAllStart,
|
||||
displayRules,
|
||||
},
|
||||
'challenge'
|
||||
'challenge',
|
||||
formState.operator
|
||||
)
|
||||
|
||||
const easierResultSupport = makeEasier(
|
||||
|
|
@ -194,7 +200,8 @@ export function SmartModeControls({ formState, onChange, isDark = false }: Smart
|
|||
pAllStart,
|
||||
displayRules,
|
||||
},
|
||||
'support'
|
||||
'support',
|
||||
formState.operator
|
||||
)
|
||||
|
||||
const harderResultBoth = makeHarder(
|
||||
|
|
@ -203,7 +210,8 @@ export function SmartModeControls({ formState, onChange, isDark = false }: Smart
|
|||
pAllStart,
|
||||
displayRules,
|
||||
},
|
||||
'both'
|
||||
'both',
|
||||
formState.operator
|
||||
)
|
||||
|
||||
const harderResultChallenge = makeHarder(
|
||||
|
|
@ -212,7 +220,8 @@ export function SmartModeControls({ formState, onChange, isDark = false }: Smart
|
|||
pAllStart,
|
||||
displayRules,
|
||||
},
|
||||
'challenge'
|
||||
'challenge',
|
||||
formState.operator
|
||||
)
|
||||
|
||||
const harderResultSupport = makeHarder(
|
||||
|
|
@ -221,7 +230,8 @@ export function SmartModeControls({ formState, onChange, isDark = false }: Smart
|
|||
pAllStart,
|
||||
displayRules,
|
||||
},
|
||||
'support'
|
||||
'support',
|
||||
formState.operator
|
||||
)
|
||||
|
||||
const canMakeEasierBoth =
|
||||
|
|
@ -350,7 +360,8 @@ export function SmartModeControls({ formState, onChange, isDark = false }: Smart
|
|||
(() => {
|
||||
const regroupingPercent = Math.round(hoverPreview.pAnyStart * 100)
|
||||
const scaffoldingSummary = getScaffoldingSummary(
|
||||
hoverPreview.displayRules
|
||||
hoverPreview.displayRules,
|
||||
formState.operator
|
||||
)
|
||||
return (
|
||||
<>
|
||||
|
|
@ -367,7 +378,10 @@ export function SmartModeControls({ formState, onChange, isDark = false }: Smart
|
|||
const regroupingPercent = Math.round(
|
||||
preset.regrouping.pAnyStart * 100
|
||||
)
|
||||
const scaffoldingSummary = getScaffoldingSummary(preset.displayRules)
|
||||
const scaffoldingSummary = getScaffoldingSummary(
|
||||
preset.displayRules,
|
||||
formState.operator
|
||||
)
|
||||
return (
|
||||
<>
|
||||
<div>{regroupingPercent}% regrouping</div>
|
||||
|
|
@ -418,7 +432,10 @@ export function SmartModeControls({ formState, onChange, isDark = false }: Smart
|
|||
preset.regrouping.pAllStart
|
||||
) * 10
|
||||
)
|
||||
const scaffoldingSummary = getScaffoldingSummary(preset.displayRules)
|
||||
const scaffoldingSummary = getScaffoldingSummary(
|
||||
preset.displayRules,
|
||||
formState.operator
|
||||
)
|
||||
const presetDescription = (
|
||||
<>
|
||||
<div>{regroupingPercent}% regrouping</div>
|
||||
|
|
|
|||
|
|
@ -24,6 +24,8 @@ export const SCAFFOLDING_PROGRESSION: DisplayRules[] = [
|
|||
tenFrames: 'always',
|
||||
problemNumbers: 'always',
|
||||
cellBorders: 'always',
|
||||
borrowNotation: 'always',
|
||||
borrowingHints: 'always',
|
||||
},
|
||||
|
||||
// Level 1: Carry boxes become conditional
|
||||
|
|
@ -34,6 +36,8 @@ export const SCAFFOLDING_PROGRESSION: DisplayRules[] = [
|
|||
tenFrames: 'always',
|
||||
problemNumbers: 'always',
|
||||
cellBorders: 'always',
|
||||
borrowNotation: 'whenRegrouping',
|
||||
borrowingHints: 'always',
|
||||
},
|
||||
|
||||
// Level 2: Place value colors become conditional
|
||||
|
|
@ -44,6 +48,8 @@ export const SCAFFOLDING_PROGRESSION: DisplayRules[] = [
|
|||
tenFrames: 'always',
|
||||
problemNumbers: 'always',
|
||||
cellBorders: 'always',
|
||||
borrowNotation: 'whenRegrouping',
|
||||
borrowingHints: 'whenRegrouping',
|
||||
},
|
||||
|
||||
// Level 3: Answer boxes become conditional
|
||||
|
|
@ -54,6 +60,8 @@ export const SCAFFOLDING_PROGRESSION: DisplayRules[] = [
|
|||
tenFrames: 'always',
|
||||
problemNumbers: 'always',
|
||||
cellBorders: 'always',
|
||||
borrowNotation: 'whenRegrouping',
|
||||
borrowingHints: 'whenRegrouping',
|
||||
},
|
||||
|
||||
// Level 4: Multiple helpers become more conditional
|
||||
|
|
@ -64,6 +72,8 @@ export const SCAFFOLDING_PROGRESSION: DisplayRules[] = [
|
|||
tenFrames: 'always',
|
||||
problemNumbers: 'always',
|
||||
cellBorders: 'always',
|
||||
borrowNotation: 'whenRegrouping',
|
||||
borrowingHints: 'whenMultipleRegroups',
|
||||
},
|
||||
|
||||
// Level 5: More helpers get more conditional
|
||||
|
|
@ -74,6 +84,8 @@ export const SCAFFOLDING_PROGRESSION: DisplayRules[] = [
|
|||
tenFrames: 'always',
|
||||
problemNumbers: 'always',
|
||||
cellBorders: 'always',
|
||||
borrowNotation: 'whenRegrouping',
|
||||
borrowingHints: 'whenMultipleRegroups',
|
||||
},
|
||||
|
||||
// Level 6: Ten frames become conditional (only when regrouping)
|
||||
|
|
@ -84,6 +96,8 @@ export const SCAFFOLDING_PROGRESSION: DisplayRules[] = [
|
|||
tenFrames: 'whenRegrouping',
|
||||
problemNumbers: 'always',
|
||||
cellBorders: 'always',
|
||||
borrowNotation: 'whenRegrouping',
|
||||
borrowingHints: 'whenMultipleRegroups',
|
||||
},
|
||||
|
||||
// Level 7: Ten frames become more conditional
|
||||
|
|
@ -94,6 +108,8 @@ export const SCAFFOLDING_PROGRESSION: DisplayRules[] = [
|
|||
tenFrames: 'whenMultipleRegroups',
|
||||
problemNumbers: 'always',
|
||||
cellBorders: 'always',
|
||||
borrowNotation: 'whenRegrouping',
|
||||
borrowingHints: 'never',
|
||||
},
|
||||
|
||||
// Level 8: Carry boxes removed
|
||||
|
|
@ -104,6 +120,8 @@ export const SCAFFOLDING_PROGRESSION: DisplayRules[] = [
|
|||
tenFrames: 'whenMultipleRegroups',
|
||||
problemNumbers: 'always',
|
||||
cellBorders: 'always',
|
||||
borrowNotation: 'whenRegrouping',
|
||||
borrowingHints: 'never',
|
||||
},
|
||||
|
||||
// Level 9: Answer boxes removed
|
||||
|
|
@ -114,6 +132,8 @@ export const SCAFFOLDING_PROGRESSION: DisplayRules[] = [
|
|||
tenFrames: 'whenMultipleRegroups',
|
||||
problemNumbers: 'always',
|
||||
cellBorders: 'always',
|
||||
borrowNotation: 'whenRegrouping',
|
||||
borrowingHints: 'never',
|
||||
},
|
||||
|
||||
// Level 10: Ten frames removed
|
||||
|
|
@ -124,6 +144,8 @@ export const SCAFFOLDING_PROGRESSION: DisplayRules[] = [
|
|||
tenFrames: 'never',
|
||||
problemNumbers: 'always',
|
||||
cellBorders: 'always',
|
||||
borrowNotation: 'whenRegrouping',
|
||||
borrowingHints: 'never',
|
||||
},
|
||||
|
||||
// Level 11: Place value colors only for large numbers
|
||||
|
|
@ -134,6 +156,8 @@ export const SCAFFOLDING_PROGRESSION: DisplayRules[] = [
|
|||
tenFrames: 'never',
|
||||
problemNumbers: 'always',
|
||||
cellBorders: 'always',
|
||||
borrowNotation: 'whenRegrouping',
|
||||
borrowingHints: 'never',
|
||||
},
|
||||
|
||||
// Level 12: Minimal scaffolding - place value colors removed
|
||||
|
|
@ -144,6 +168,8 @@ export const SCAFFOLDING_PROGRESSION: DisplayRules[] = [
|
|||
tenFrames: 'never',
|
||||
problemNumbers: 'always',
|
||||
cellBorders: 'always',
|
||||
borrowNotation: 'never',
|
||||
borrowingHints: 'never',
|
||||
},
|
||||
]
|
||||
|
||||
|
|
@ -236,11 +262,16 @@ export function findRegroupingIndex(pAnyStart: number, pAllStart: number): numbe
|
|||
|
||||
/**
|
||||
* Describe what changed between two scaffolding levels
|
||||
* @param fromRules - Previous display rules
|
||||
* @param toRules - New display rules
|
||||
* @param direction - Whether scaffolding was 'added' or 'reduced'
|
||||
* @param operator - Current worksheet operator (filters out irrelevant scaffolds)
|
||||
*/
|
||||
function describeScaffoldingChange(
|
||||
fromRules: DisplayRules,
|
||||
toRules: DisplayRules,
|
||||
direction: 'added' | 'reduced'
|
||||
direction: 'added' | 'reduced',
|
||||
operator?: 'addition' | 'subtraction' | 'mixed'
|
||||
): string {
|
||||
const changes: string[] = []
|
||||
|
||||
|
|
@ -251,10 +282,25 @@ function describeScaffoldingChange(
|
|||
tenFrames: 'ten frames',
|
||||
problemNumbers: 'problem numbers',
|
||||
cellBorders: 'cell borders',
|
||||
borrowNotation: 'borrow notation',
|
||||
borrowingHints: 'borrowing hints',
|
||||
}
|
||||
|
||||
// Operator-specific scaffolds to filter
|
||||
const additionOnlyScaffolds: Array<keyof DisplayRules> = ['carryBoxes', 'tenFrames']
|
||||
const subtractionOnlyScaffolds: Array<keyof DisplayRules> = ['borrowNotation', 'borrowingHints']
|
||||
|
||||
for (const key of Object.keys(ruleNames) as Array<keyof DisplayRules>) {
|
||||
if (fromRules[key] !== toRules[key]) {
|
||||
// Filter out operator-specific scaffolds when not relevant
|
||||
if (operator === 'addition' && subtractionOnlyScaffolds.includes(key)) {
|
||||
continue // Skip subtraction scaffolds for addition-only worksheets
|
||||
}
|
||||
if (operator === 'subtraction' && additionOnlyScaffolds.includes(key)) {
|
||||
continue // Skip addition scaffolds for subtraction-only worksheets
|
||||
}
|
||||
// For 'mixed' or undefined operator, show all scaffolds
|
||||
|
||||
changes.push(ruleNames[key])
|
||||
}
|
||||
}
|
||||
|
|
@ -377,6 +423,8 @@ export const DIFFICULTY_PROFILES: Record<string, DifficultyProfile> = {
|
|||
tenFrames: 'always', // Visual aid for understanding place value
|
||||
problemNumbers: 'always', // Help track progress
|
||||
cellBorders: 'always', // Visual organization
|
||||
borrowNotation: 'always', // Subtraction: show scratch work
|
||||
borrowingHints: 'always', // Subtraction: show visual hints
|
||||
},
|
||||
},
|
||||
|
||||
|
|
@ -392,6 +440,8 @@ export const DIFFICULTY_PROFILES: Record<string, DifficultyProfile> = {
|
|||
tenFrames: 'whenRegrouping', // Visual aid for new concept
|
||||
problemNumbers: 'always',
|
||||
cellBorders: 'always',
|
||||
borrowNotation: 'whenRegrouping', // Subtraction: show when borrowing
|
||||
borrowingHints: 'always', // Subtraction: keep hints visible
|
||||
},
|
||||
},
|
||||
|
||||
|
|
@ -408,6 +458,8 @@ export const DIFFICULTY_PROFILES: Record<string, DifficultyProfile> = {
|
|||
tenFrames: 'whenRegrouping', // Visual aid for regrouping
|
||||
problemNumbers: 'always',
|
||||
cellBorders: 'always',
|
||||
borrowNotation: 'whenRegrouping', // Subtraction: show when borrowing
|
||||
borrowingHints: 'whenRegrouping', // Subtraction: show hints during practice
|
||||
},
|
||||
},
|
||||
|
||||
|
|
@ -423,6 +475,8 @@ export const DIFFICULTY_PROFILES: Record<string, DifficultyProfile> = {
|
|||
tenFrames: 'whenRegrouping', // Concrete aid when needed
|
||||
problemNumbers: 'always',
|
||||
cellBorders: 'always',
|
||||
borrowNotation: 'whenRegrouping', // Subtraction: show when borrowing
|
||||
borrowingHints: 'whenMultipleRegroups', // Subtraction: only for complex problems
|
||||
},
|
||||
},
|
||||
|
||||
|
|
@ -438,6 +492,8 @@ export const DIFFICULTY_PROFILES: Record<string, DifficultyProfile> = {
|
|||
tenFrames: 'never', // Beyond concrete representations
|
||||
problemNumbers: 'always',
|
||||
cellBorders: 'always',
|
||||
borrowNotation: 'whenRegrouping', // Subtraction: still helpful
|
||||
borrowingHints: 'never', // Subtraction: no hints
|
||||
},
|
||||
},
|
||||
|
||||
|
|
@ -453,6 +509,8 @@ export const DIFFICULTY_PROFILES: Record<string, DifficultyProfile> = {
|
|||
tenFrames: 'never',
|
||||
problemNumbers: 'always',
|
||||
cellBorders: 'always',
|
||||
borrowNotation: 'never', // Subtraction: no scaffolding
|
||||
borrowingHints: 'never', // Subtraction: no hints
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
@ -638,6 +696,8 @@ function levelToScaffoldingRules(level: number): DisplayRules {
|
|||
tenFrames: level < 1 ? 'never' : 'whenRegrouping', // Ten-frames only when regrouping introduced
|
||||
problemNumbers: 'always',
|
||||
cellBorders: 'always',
|
||||
borrowNotation: 'always',
|
||||
borrowingHints: 'always',
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -650,6 +710,8 @@ function levelToScaffoldingRules(level: number): DisplayRules {
|
|||
tenFrames: 'whenRegrouping',
|
||||
problemNumbers: 'always',
|
||||
cellBorders: 'always',
|
||||
borrowNotation: 'whenRegrouping',
|
||||
borrowingHints: 'always',
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -662,6 +724,8 @@ function levelToScaffoldingRules(level: number): DisplayRules {
|
|||
tenFrames: 'whenRegrouping',
|
||||
problemNumbers: 'always',
|
||||
cellBorders: 'always',
|
||||
borrowNotation: 'whenRegrouping',
|
||||
borrowingHints: level < 5.5 ? 'whenRegrouping' : 'whenMultipleRegroups',
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -674,6 +738,8 @@ function levelToScaffoldingRules(level: number): DisplayRules {
|
|||
tenFrames: 'never',
|
||||
problemNumbers: 'always',
|
||||
cellBorders: 'always',
|
||||
borrowNotation: 'whenRegrouping',
|
||||
borrowingHints: 'never',
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -685,6 +751,8 @@ function levelToScaffoldingRules(level: number): DisplayRules {
|
|||
tenFrames: 'never',
|
||||
problemNumbers: 'always',
|
||||
cellBorders: 'always',
|
||||
borrowNotation: level < 9 ? 'whenRegrouping' : 'never',
|
||||
borrowingHints: 'never',
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -763,6 +831,7 @@ export type DifficultyMode = 'both' | 'challenge' | 'support'
|
|||
* 5. Ensure resulting state respects pedagogical constraints
|
||||
*
|
||||
* @param mode - 'both' (default smart diagonal), 'challenge' (regrouping only), 'support' (scaffolding only)
|
||||
* @param operator - Current worksheet operator (filters scaffolding descriptions)
|
||||
*/
|
||||
export function makeHarder(
|
||||
currentState: {
|
||||
|
|
@ -770,7 +839,8 @@ export function makeHarder(
|
|||
pAllStart: number
|
||||
displayRules: DisplayRules
|
||||
},
|
||||
mode: DifficultyMode = 'both'
|
||||
mode: DifficultyMode = 'both',
|
||||
operator?: 'addition' | 'subtraction' | 'mixed'
|
||||
): {
|
||||
pAnyStart: number
|
||||
pAllStart: number
|
||||
|
|
@ -913,7 +983,8 @@ export function makeHarder(
|
|||
const scaffoldingChange = describeScaffoldingChange(
|
||||
currentState.displayRules,
|
||||
newRules,
|
||||
'reduced'
|
||||
'reduced',
|
||||
operator
|
||||
)
|
||||
description = `Increase regrouping to ${Math.round(newRegrouping.pAnyStart * 100)}% + ${scaffoldingChange.toLowerCase()}`
|
||||
} else if (newRegroupingIdx > validCurrent.regroupingIdx) {
|
||||
|
|
@ -922,12 +993,18 @@ export function makeHarder(
|
|||
const scaffoldingChange = describeScaffoldingChange(
|
||||
currentState.displayRules,
|
||||
newRules,
|
||||
newScaffoldingIdx < validCurrent.scaffoldingIdx ? 'added' : 'reduced'
|
||||
newScaffoldingIdx < validCurrent.scaffoldingIdx ? 'added' : 'reduced',
|
||||
operator
|
||||
)
|
||||
description += ` (auto-adjust: ${scaffoldingChange.toLowerCase()})`
|
||||
}
|
||||
} else if (newScaffoldingIdx > validCurrent.scaffoldingIdx) {
|
||||
description = describeScaffoldingChange(currentState.displayRules, newRules, 'reduced')
|
||||
description = describeScaffoldingChange(
|
||||
currentState.displayRules,
|
||||
newRules,
|
||||
'reduced',
|
||||
operator
|
||||
)
|
||||
}
|
||||
|
||||
// Check if result matches a preset
|
||||
|
|
@ -957,6 +1034,7 @@ export function makeHarder(
|
|||
* 5. Ensure resulting state respects pedagogical constraints
|
||||
*
|
||||
* @param mode - 'both' (default smart diagonal), 'challenge' (regrouping only), 'support' (scaffolding only)
|
||||
* @param operator - Current worksheet operator (filters scaffolding descriptions)
|
||||
*/
|
||||
export function makeEasier(
|
||||
currentState: {
|
||||
|
|
@ -964,7 +1042,8 @@ export function makeEasier(
|
|||
pAllStart: number
|
||||
displayRules: DisplayRules
|
||||
},
|
||||
mode: DifficultyMode = 'both'
|
||||
mode: DifficultyMode = 'both',
|
||||
operator?: 'addition' | 'subtraction' | 'mixed'
|
||||
): {
|
||||
pAnyStart: number
|
||||
pAllStart: number
|
||||
|
|
@ -1096,7 +1175,8 @@ export function makeEasier(
|
|||
const scaffoldingChange = describeScaffoldingChange(
|
||||
currentState.displayRules,
|
||||
newRules,
|
||||
'added'
|
||||
'added',
|
||||
operator
|
||||
)
|
||||
description = `Reduce regrouping to ${Math.round(newRegrouping.pAnyStart * 100)}% + ${scaffoldingChange.toLowerCase()}`
|
||||
} else if (newRegroupingIdx < validCurrent.regroupingIdx) {
|
||||
|
|
@ -1105,12 +1185,13 @@ export function makeEasier(
|
|||
const scaffoldingChange = describeScaffoldingChange(
|
||||
currentState.displayRules,
|
||||
newRules,
|
||||
newScaffoldingIdx < validCurrent.scaffoldingIdx ? 'added' : 'reduced'
|
||||
newScaffoldingIdx < validCurrent.scaffoldingIdx ? 'added' : 'reduced',
|
||||
operator
|
||||
)
|
||||
description += ` (auto-adjust: ${scaffoldingChange.toLowerCase()})`
|
||||
}
|
||||
} else if (newScaffoldingIdx < validCurrent.scaffoldingIdx) {
|
||||
description = describeScaffoldingChange(currentState.displayRules, newRules, 'added')
|
||||
description = describeScaffoldingChange(currentState.displayRules, newRules, 'added', operator)
|
||||
}
|
||||
|
||||
// Check if result matches a preset
|
||||
|
|
|
|||
Loading…
Reference in New Issue