feat: integrate bead diff algorithm with tutorial editor

Integrates the dynamic bead diff algorithm into the tutorial editor's
multi-step instruction display. Now shows real-time bead movement
calculations and summaries for each step in the editing interface.

Features:
- calculateStepStatesAndDiffs() function for incremental state transitions
- Dynamic bead diff display with movement summaries
- Enhanced debugging output showing state transitions
- Visual feedback for each multi-step instruction

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Thomas Hallock
2025-09-22 21:15:23 -05:00
parent c43090aa7d
commit 472bdf8e74

View File

@@ -10,6 +10,7 @@ import { generateSingleProblem } from '../../utils/problemGenerator'
import { skillConfigurationToSkillSets, createBasicAllowedConfiguration } from '../../utils/skillConfiguration'
import { generateAbacusInstructions } from '../../utils/abacusInstructionGenerator'
import { generateUnifiedInstructionSequence } from '../../utils/unifiedStepGenerator'
import { calculateBeadDiffFromValues, calculateMultiStepBeadDiffs } from '../../utils/beadDiff'
import { EditorLayout, TextInput, NumberInput, FormGroup, CompactStepItem, BetweenStepAdd } from './shared/EditorComponents'
import Resizable from 'react-resizable-layout'
@@ -1014,6 +1015,72 @@ export function TutorialEditor({
setEditorState(prev => ({ ...prev, isDirty: true }))
}, [tutorial.steps])
// Calculate step-by-step states and bead diffs for multi-step instructions
const calculateStepStatesAndDiffs = useCallback((stepIndex: number) => {
const step = tutorial.steps[stepIndex]
if (!step || !step.multiStepInstructions || step.multiStepInstructions.length === 0) {
return []
}
try {
// Use unified sequence if available, otherwise generate it
let unifiedSequence = (step as any).unifiedSequence
if (!unifiedSequence) {
unifiedSequence = generateUnifiedInstructionSequence(step.startValue, step.targetValue)
}
const stepStates = []
let currentValue = step.startValue
console.log('=== Calculating Step States and Diffs ===')
console.log('Start value:', currentValue)
console.log('Target value:', step.targetValue)
console.log('Unified sequence steps:', unifiedSequence.steps.length)
console.log('Multi-step instructions:', step.multiStepInstructions.length)
for (let i = 0; i < step.multiStepInstructions.length && i < unifiedSequence.steps.length; i++) {
const unifiedStep = unifiedSequence.steps[i]
const expectedValue = unifiedStep.expectedValue
console.log(`\nStep ${i}:`)
console.log(` From: ${currentValue} → To: ${expectedValue}`)
console.log(` Math term: ${unifiedStep.mathematicalTerm}`)
// Calculate bead diff from current to expected state (incremental)
const beadDiff = calculateBeadDiffFromValues(currentValue, expectedValue)
console.log(` Bead diff summary: "${beadDiff.summary}"`)
console.log(` Has changes: ${beadDiff.hasChanges}`)
console.log(` Changes count: ${beadDiff.changes.length}`)
console.log(` Changes:`, beadDiff.changes.map(c =>
`${c.placeValue}:${c.beadType}:${c.direction}${c.position !== undefined ? `:pos${c.position}` : ''}`
).join(', '))
stepStates.push({
stepIndex: i,
instruction: step.multiStepInstructions[i],
currentValue,
expectedValue,
beadDiff,
mathematicalTerm: unifiedStep.mathematicalTerm
})
// Update current value for next iteration (incremental approach)
currentValue = expectedValue
}
console.log(`\nGenerated ${stepStates.length} step states`)
stepStates.forEach((state, i) => {
console.log(` Step ${i}: ${state.currentValue}${state.expectedValue}, changes: ${state.beadDiff.changes.length}`)
})
return stepStates
} catch (error) {
console.error('Failed to calculate step states:', error)
return []
}
}, [tutorial.steps])
// Generate unified instructions for a specific step
const generateUnifiedInstructionsForStep = useCallback((stepIndex: number) => {
const step = tutorial.steps[stepIndex]
@@ -1657,36 +1724,15 @@ export function TutorialEditor({
</div>
</div>
<div className={css({ space: 2 })}>
{(tutorial.steps[editorState.selectedStepIndex].multiStepInstructions || []).map((instruction, index) => {
// Calculate expected state for this step
// FIXED: multiStepInstructions now use pedagogical order, matching stepBeadHighlights
const currentStep = tutorial.steps[editorState.selectedStepIndex]
let expectedValue = "Calculating..."
{(() => {
// Use the new bead diff calculation for each step
const stepStatesAndDiffs = calculateStepStatesAndDiffs(editorState.selectedStepIndex)
try {
const fullInstruction = generateAbacusInstructions(currentStep.startValue, currentStep.targetValue)
if (fullInstruction.stepBeadHighlights) {
const stepIndices = [...new Set(fullInstruction.stepBeadHighlights.map(bead => bead.stepIndex))].sort()
let currentValue = currentStep.startValue
for (let i = 0; i <= index && i < stepIndices.length; i++) {
const stepIndex = stepIndices[i]
const stepBeads = fullInstruction.stepBeadHighlights.filter(bead => bead.stepIndex === stepIndex)
stepBeads.forEach(bead => {
const multiplier = Math.pow(10, bead.placeValue)
const value = bead.beadType === 'heaven' ? 5 * multiplier : multiplier
currentValue += bead.direction === 'activate' ? value : -value
})
}
expectedValue = currentValue.toString()
} else {
expectedValue = `Final: ${currentStep.targetValue}`
}
} catch (error) {
expectedValue = "Error calculating"
}
return (tutorial.steps[editorState.selectedStepIndex].multiStepInstructions || []).map((instruction, index) => {
const stepData = stepStatesAndDiffs[index]
const expectedValue = stepData ? stepData.expectedValue.toString() : "Calculating..."
const beadDiff = stepData ? stepData.beadDiff : null
const mathematicalTerm = stepData ? stepData.mathematicalTerm : ""
return (
<div key={index} className={css({ display: 'flex', gap: 2, mb: 2 })}>
@@ -1715,7 +1761,59 @@ export function TutorialEditor({
mt: 1
})}>
Expected state after step: {expectedValue}
{mathematicalTerm && (
<span className={css({ color: 'blue.600', ml: 2, fontWeight: 'medium' })}>
(Math: {mathematicalTerm})
</span>
)}
</div>
{/* Dynamic Bead Diff Display */}
{beadDiff && beadDiff.hasChanges && (
<div className={css({
fontSize: 'xs',
mt: 2,
p: 2,
bg: 'yellow.50',
border: '1px solid',
borderColor: 'yellow.200',
borderRadius: 'sm'
})}>
<div className={css({ fontWeight: 'medium', color: 'yellow.800', mb: 1 })}>
🎯 Bead Movements for this Step:
</div>
<div className={css({ color: 'yellow.700', mb: 1 })}>
{beadDiff.summary}
</div>
<div className={css({
display: 'flex',
flexWrap: 'wrap',
gap: 1,
mt: 1
})}>
{beadDiff.changes.map((change, changeIndex) => (
<span
key={changeIndex}
className={css({
fontSize: 'xs',
px: 1,
py: 0.5,
borderRadius: 'xs',
bg: change.direction === 'activate' ? 'green.100' : 'red.100',
color: change.direction === 'activate' ? 'green.800' : 'red.800',
border: '1px solid',
borderColor: change.direction === 'activate' ? 'green.300' : 'red.300'
})}
>
{change.direction === 'activate' ? '↗️' : '↙️'}
{change.beadType === 'heaven' ? 'H' : 'E'}
{change.position !== undefined ? change.position : ''}
@P{change.placeValue}
</span>
))}
</div>
</div>
)}
</div>
<button
onClick={() => {
@@ -1738,7 +1836,8 @@ export function TutorialEditor({
</button>
</div>
)
})}
})
})()}
<button
onClick={() => {
const newInstructions = [...(tutorial.steps[editorState.selectedStepIndex].multiStepInstructions || []), '']