From ba1a92230f223dbd1e80acbf9e0136f4102a429d Mon Sep 17 00:00:00 2001 From: Thomas Hallock Date: Mon, 22 Sep 2025 14:56:34 -0500 Subject: [PATCH] docs: add comprehensive documentation and debug tools for progressive instruction system MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PROGRESSIVE_INSTRUCTION_PLAN.md: - Complete implementation plan with 4 phases and success criteria - Technical architecture documentation - Data structure specifications and implementation approach Storybook Debug Stories: - AbacusReact.debug-arrows.stories.tsx: Simple arrow positioning debugging - AbacusReact.direction-arrows.stories.tsx: Comprehensive direction indicator showcase - Stories for testing single beads, multiple arrows, and raw SVG validation Step Advancement Test: - step-advancement.test.tsx: Isolated unit tests for step progression logic - Test component implementing full step advancement workflow - Validates expected step generation and auto-advancement behavior These tools enabled debugging and validation of the complete progressive instruction system. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- PROGRESSIVE_INSTRUCTION_PLAN.md | 244 ++++++++++ .../src/AbacusReact.debug-arrows.stories.tsx | 289 ++++++++++++ .../AbacusReact.direction-arrows.stories.tsx | 435 ++++++++++++++++++ .../src/step-advancement.test.tsx | 294 ++++++++++++ 4 files changed, 1262 insertions(+) create mode 100644 PROGRESSIVE_INSTRUCTION_PLAN.md create mode 100644 packages/abacus-react/src/AbacusReact.debug-arrows.stories.tsx create mode 100644 packages/abacus-react/src/AbacusReact.direction-arrows.stories.tsx create mode 100644 packages/abacus-react/src/step-advancement.test.tsx diff --git a/PROGRESSIVE_INSTRUCTION_PLAN.md b/PROGRESSIVE_INSTRUCTION_PLAN.md new file mode 100644 index 00000000..723e9293 --- /dev/null +++ b/PROGRESSIVE_INSTRUCTION_PLAN.md @@ -0,0 +1,244 @@ +# Progressive Multi-Step Instruction System Plan + +## Overview +Implement a comprehensive system that coordinates multi-step instructions, bead highlighting, and directional movement indicators to create a step-by-step progressive tutorial experience. + +## Current System Analysis + +### 1. Bead Highlighting System +- **Location**: `packages/abacus-react/src/AbacusReact.tsx` +- **Interface**: `BeadHighlight` (union of PlaceValueBead | ColumnIndexBead) +- **Current functionality**: Static highlighting of beads +- **Limitation**: Shows all highlighted beads at once, no progressive revelation + +### 2. Multi-Step Instructions +- **Location**: `apps/web/src/utils/abacusInstructionGenerator.ts` +- **Data**: `multiStepInstructions: string[]` +- **Current functionality**: Generates step-by-step text instructions +- **Example (99+1)**: + 1. "Click earth bead 1 in the hundreds column to add it" + 2. "Remove 9 from ones column (subtracting second part of decomposition)" + 3. "Remove 90 from tens column (subtracting first part of decomposition)" + +### 3. Progressive Display (GuidedAdditionTutorial) +- **Location**: `apps/web/src/components/GuidedAdditionTutorial.tsx` +- **State**: `multiStepProgress: number` (tracks current step) +- **Functionality**: Shows current step in bold, dims future steps +- **Limitation**: No coordination with bead highlighting + +### 4. Bead Generation Logic +- **Location**: `apps/web/src/utils/abacusInstructionGenerator.ts:generateBeadHighlights()` +- **Current**: Generates all beads for entire operation +- **Limitation**: No step-by-step bead breakdown + +## Proposed Solution Architecture + +### Phase 1: Enhanced Data Structures + +#### 1.1 Extended BeadHighlight Interface +```typescript +export interface StepBeadHighlight extends BeadHighlight { + stepIndex: number // Which instruction step this bead belongs to + direction: 'up' | 'down' | 'activate' | 'deactivate' // Movement direction + order?: number // Order within the step (for multiple beads per step) +} +``` + +#### 1.2 Enhanced GeneratedInstruction Interface +```typescript +export interface GeneratedInstruction { + // ... existing fields + multiStepInstructions?: string[] + stepBeadHighlights?: StepBeadHighlight[] // NEW: beads grouped by step + totalSteps?: number // NEW: total number of steps +} +``` + +#### 1.3 Progressive Instruction State +```typescript +interface ProgressiveInstructionState { + currentStep: number + totalSteps: number + currentStepBeads: BeadHighlight[] // Beads for current step only + completedStepBeads: BeadHighlight[] // All beads from previous steps + currentStepInstruction: string +} +``` + +### Phase 2: Enhanced Instruction Generation + +#### 2.1 Modify generateEnhancedStepInstructions() +- Generate beads per step, not just text instructions +- Map each bead operation to its corresponding instruction step +- Determine movement direction for each bead + +#### 2.2 New Function: generateStepBeadMapping() +```typescript +function generateStepBeadMapping( + startValue: number, + targetValue: number, + additions: BeadHighlight[], + removals: BeadHighlight[], + decomposition: any, + multiStepInstructions: string[] +): StepBeadHighlight[] +``` + +### Phase 3: AbacusReact Component Enhancements + +#### 3.1 Add Directional Indicators +- **New Props**: + ```typescript + interface AbacusReactProps { + // ... existing props + stepBeadHighlights?: StepBeadHighlight[] + showDirectionIndicators?: boolean + } + ``` + +- **Visual Implementation**: + - Arrow overlays on beads (↑ for up/activate, ↓ for down/deactivate) + - Different highlight colors for different directions + - Animation hints for movement direction + +#### 3.2 Enhanced Bead Styling +```typescript +interface BeadDirectionStyle extends BeadStyle { + directionIndicator?: { + show: boolean + direction: 'up' | 'down' | 'activate' | 'deactivate' + color?: string + size?: number + } +} +``` + +### Phase 4: TutorialPlayer Progressive Display + +#### 4.1 Add Progressive State Management +```typescript +interface TutorialPlayerState { + // ... existing state + progressiveInstruction: ProgressiveInstructionState | null +} +``` + +#### 4.2 Progressive Instruction Logic +- Track current step within multi-step instructions +- Update highlighted beads based on current step +- Show only current step instruction in bold +- Dim future steps, mark completed steps + +#### 4.3 Step Advancement Logic +- User completes current step → advance to next step +- Update bead highlights to show next step's beads +- Maintain previously completed beads in "completed" state + +### Phase 5: Testing Strategy + +#### 5.1 Unit Tests for Step-by-Step Generation +```typescript +describe('Progressive Instruction Generation', () => { + test('99+1 generates correct step-bead mapping', () => { + const instruction = generateAbacusInstructions(99, 100) + expect(instruction.stepBeadHighlights).toHaveLength(3) + + // Step 0: Add 1 to hundreds + expect(instruction.stepBeadHighlights[0]).toEqual({ + stepIndex: 0, + placeValue: 2, + beadType: 'earth', + position: 0, + direction: 'activate' + }) + + // Step 1: Remove 9 from ones (heaven + 4 earth) + const step1Beads = instruction.stepBeadHighlights.filter(b => b.stepIndex === 1) + expect(step1Beads).toHaveLength(5) // 1 heaven + 4 earth + + // Step 2: Remove 90 from tens (heaven + 4 earth) + const step2Beads = instruction.stepBeadHighlights.filter(b => b.stepIndex === 2) + expect(step2Beads).toHaveLength(5) // 1 heaven + 4 earth + }) + + test('3+98 generates correct step-bead mapping', () => { + // Test simpler complement case + }) +}) +``` + +#### 5.2 Integration Tests for Progressive Display +```typescript +describe('Progressive Tutorial Player', () => { + test('shows only current step beads', () => { + // Render tutorial player with 99+1 case + // Verify only step 0 beads are highlighted initially + // Advance step, verify step 1 beads appear + }) + + test('direction indicators display correctly', () => { + // Verify arrows/indicators show correct directions + }) +}) +``` + +#### 5.3 Visual Regression Tests +- Storybook stories for each progression state +- Visual comparisons for direction indicators +- Progressive highlighting behavior + +## Implementation Plan + +### Iteration 1: Data Structure Foundation +1. **Design and implement enhanced interfaces** ✓ +2. **Modify instruction generator to produce step-bead mapping** +3. **Unit tests for step-bead generation** +4. **Validate with 99+1 and 3+98 test cases** + +### Iteration 2: AbacusReact Direction Indicators +1. **Add direction indicator props to AbacusReact** +2. **Implement visual direction indicators (arrows/styling)** +3. **Create Storybook stories for direction indicators** +4. **Test various direction combinations** + +### Iteration 3: Progressive TutorialPlayer +1. **Add progressive state management to TutorialPlayer** +2. **Implement step advancement logic** +3. **Coordinate instruction text with bead highlights** +4. **Test tutorial progression flow** + +### Iteration 4: Integration and Polish +1. **Integrate all components in tutorial editor preview** +2. **End-to-end testing of complete progressive experience** +3. **Performance optimization** +4. **Accessibility improvements** + +## Success Criteria + +1. **Bead-Instruction Correspondence**: Each instruction step highlights only its relevant beads +2. **Direction Clarity**: Users can see which direction each bead needs to move +3. **Progressive Revelation**: Only current step is active, future steps are dimmed +4. **Pedagogical Alignment**: Visual progression matches mathematical decomposition +5. **99+1 Test Case**: + - Step 1: Show only hundreds bead with "up" indicator + - Step 2: Show only ones column beads with "down" indicators + - Step 3: Show only tens column beads with "down" indicators + +## Technical Considerations + +### Performance +- Minimize re-renders during step progression +- Efficient bead highlight calculations +- Smooth animations for step transitions + +### Accessibility +- Screen reader announcements for step changes +- Keyboard navigation through steps +- High contrast direction indicators + +### Backward Compatibility +- Maintain existing single-step tutorial functionality +- Graceful fallback for tutorials without multi-step data +- Preserve existing AbacusReact API surface + +This plan provides a comprehensive roadmap for implementing the progressive multi-step instruction system with proper coordination between pedagogy, visual display, and user interaction. \ No newline at end of file diff --git a/packages/abacus-react/src/AbacusReact.debug-arrows.stories.tsx b/packages/abacus-react/src/AbacusReact.debug-arrows.stories.tsx new file mode 100644 index 00000000..1ddf9278 --- /dev/null +++ b/packages/abacus-react/src/AbacusReact.debug-arrows.stories.tsx @@ -0,0 +1,289 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import { AbacusReact, StepBeadHighlight } from './AbacusReact'; +import React from 'react'; + +const meta: Meta = { + title: 'Debug/Arrow Positioning', + component: AbacusReact, + parameters: { + layout: 'centered', + docs: { + description: { + component: ` +# Arrow Positioning Debug + +Simple stories to debug arrow centering issues. + `, + }, + }, + }, + tags: ['autodocs'], +}; + +export default meta; +type Story = StoryObj; + +// Single earth bead with up arrow - minimal test case +export const SingleEarthBeadUp: Story = { + args: { + value: 0, + columns: 1, + scaleFactor: 4, // Large scale to see details + interactive: false, + animated: false, + colorScheme: 'place-value', + stepBeadHighlights: [{ + placeValue: 0, + beadType: 'earth', + position: 0, + stepIndex: 0, + direction: 'activate', + order: 0 + }], + currentStep: 0, + showDirectionIndicators: true, + }, + parameters: { + docs: { + description: { + story: ` +**Single Earth Bead with Up Arrow** + +- Value: 0 (no beads active) +- Shows green up arrow on first earth bead +- Large scale factor for debugging +- Should be centered on the bead + ` + } + } + } +}; + +// Single heaven bead with down arrow +export const SingleHeavenBeadDown: Story = { + args: { + value: 0, + columns: 1, + scaleFactor: 4, + interactive: false, + animated: false, + colorScheme: 'place-value', + stepBeadHighlights: [{ + placeValue: 0, + beadType: 'heaven', + stepIndex: 0, + direction: 'activate', + order: 0 + }], + currentStep: 0, + showDirectionIndicators: true, + }, + parameters: { + docs: { + description: { + story: ` +**Single Heaven Bead with Down Arrow** + +- Value: 0 (no beads active) +- Shows red down arrow on heaven bead +- Large scale factor for debugging +- Should be centered on the bead + ` + } + } + } +}; + +// Active earth bead with down arrow (deactivate) +export const ActiveEarthBeadDown: Story = { + args: { + value: 1, + columns: 1, + scaleFactor: 4, + interactive: false, + animated: false, + colorScheme: 'place-value', + stepBeadHighlights: [{ + placeValue: 0, + beadType: 'earth', + position: 0, + stepIndex: 0, + direction: 'deactivate', + order: 0 + }], + currentStep: 0, + showDirectionIndicators: true, + }, + parameters: { + docs: { + description: { + story: ` +**Active Earth Bead with Down Arrow** + +- Value: 1 (first earth bead active) +- Shows red down arrow for deactivation +- Large scale factor for debugging +- Should be centered on the active bead + ` + } + } + } +}; + +// Active heaven bead with up arrow (deactivate) +export const ActiveHeavenBeadUp: Story = { + args: { + value: 5, + columns: 1, + scaleFactor: 4, + interactive: false, + animated: false, + colorScheme: 'place-value', + stepBeadHighlights: [{ + placeValue: 0, + beadType: 'heaven', + stepIndex: 0, + direction: 'deactivate', + order: 0 + }], + currentStep: 0, + showDirectionIndicators: true, + }, + parameters: { + docs: { + description: { + story: ` +**Active Heaven Bead with Up Arrow** + +- Value: 5 (heaven bead active) +- Shows green up arrow for deactivation +- Large scale factor for debugging +- Should be centered on the active bead + ` + } + } + } +}; + +// Multiple arrows for comparison +export const MultipleArrows: Story = { + args: { + value: 0, + columns: 1, + scaleFactor: 3, + interactive: false, + animated: false, + colorScheme: 'place-value', + stepBeadHighlights: [ + { + placeValue: 0, + beadType: 'heaven', + stepIndex: 0, + direction: 'activate', + order: 0 + }, + { + placeValue: 0, + beadType: 'earth', + position: 0, + stepIndex: 0, + direction: 'activate', + order: 1 + }, + { + placeValue: 0, + beadType: 'earth', + position: 1, + stepIndex: 0, + direction: 'activate', + order: 2 + } + ], + currentStep: 0, + showDirectionIndicators: true, + }, + parameters: { + docs: { + description: { + story: ` +**Multiple Arrows for Comparison** + +- Shows arrows on heaven bead and first two earth beads +- All should point in correct directions +- All should be centered on their respective beads +- Helps identify if positioning is consistent across bead types + ` + } + } + } +}; + +// Raw SVG test - just arrows without beads +export const RawArrowTest: Story = { + render: () => { + return ( +
+

Raw SVG Arrow Test

+ + {/* Grid lines for reference */} + + + + {/* Center point */} + + + {/* Up arrow at center */} + + + + + {/* Text label */} + + Up Arrow (should be centered at intersection) + + + + + {/* Grid lines for reference */} + + + + {/* Center point */} + + + {/* Down arrow at center */} + + + + + {/* Text label */} + + Down Arrow (should be centered at intersection) + + +
+ ); + }, + parameters: { + docs: { + description: { + story: ` +**Raw SVG Arrow Test** + +Pure SVG arrows to verify our arrow shapes and positioning work correctly. +The arrows should be perfectly centered at the grid intersection. + ` + } + } + } +}; \ No newline at end of file diff --git a/packages/abacus-react/src/AbacusReact.direction-arrows.stories.tsx b/packages/abacus-react/src/AbacusReact.direction-arrows.stories.tsx new file mode 100644 index 00000000..e07bfba2 --- /dev/null +++ b/packages/abacus-react/src/AbacusReact.direction-arrows.stories.tsx @@ -0,0 +1,435 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import { action } from '@storybook/addon-actions'; +import { AbacusReact, StepBeadHighlight } from './AbacusReact'; +import React, { useState } from 'react'; + +const meta: Meta = { + title: 'Soroban/AbacusReact/Direction Arrows', + component: AbacusReact, + parameters: { + layout: 'centered', + docs: { + description: { + component: ` +# Direction Arrows Feature + +Progressive instruction system with direction indicators for tutorial guidance. + +## Features + +- 🎯 **Step-based highlighting** - Show beads for current instruction step only +- ⬆️ **Direction arrows** - Visual indicators showing which direction to move beads +- 🔄 **Progressive revelation** - Steps advance as user follows instructions +- 🎨 **Correct movement logic** - Heaven beads activate down, earth beads activate up + +## Arrow Logic + +- **Earth beads**: 'activate' → ⬆️ (push up), 'deactivate' → ⬇️ (release down) +- **Heaven beads**: 'activate' → ⬇️ (pull down), 'deactivate' → ⬆️ (release up) + `, + }, + }, + }, + tags: ['autodocs'], + argTypes: { + currentStep: { + control: { type: 'number', min: 0, max: 5 }, + description: 'Current step index to highlight', + }, + showDirectionIndicators: { + control: { type: 'boolean' }, + description: 'Show direction arrows on beads', + }, + }, +}; + +export default meta; +type Story = StoryObj; + +// Helper function to create step bead highlights for different scenarios +const createStepBeads = (scenario: 'earth-activate' | 'heaven-activate' | 'multi-step' | 'heaven-earth-combo'): StepBeadHighlight[] => { + switch (scenario) { + case 'earth-activate': + return [ + { + placeValue: 0, + beadType: 'earth', + position: 0, + stepIndex: 0, + direction: 'activate', + order: 0 + } + ]; + + case 'heaven-activate': + return [ + { + placeValue: 0, + beadType: 'heaven', + stepIndex: 0, + direction: 'activate', + order: 0 + } + ]; + + case 'multi-step': + return [ + // Step 0: Add heaven bead (5) + { + placeValue: 0, + beadType: 'heaven', + stepIndex: 0, + direction: 'activate', + order: 0 + }, + // Step 1: Remove 2 earth beads (subtract 2) + { + placeValue: 0, + beadType: 'earth', + position: 0, + stepIndex: 1, + direction: 'deactivate', + order: 0 + }, + { + placeValue: 0, + beadType: 'earth', + position: 1, + stepIndex: 1, + direction: 'deactivate', + order: 1 + } + ]; + + case 'heaven-earth-combo': + return [ + // Step 0: Multiple beads in same step + { + placeValue: 1, + beadType: 'earth', + position: 0, + stepIndex: 0, + direction: 'activate', + order: 0 + }, + { + placeValue: 0, + beadType: 'heaven', + stepIndex: 0, + direction: 'deactivate', + order: 1 + }, + { + placeValue: 0, + beadType: 'earth', + position: 0, + stepIndex: 0, + direction: 'deactivate', + order: 2 + } + ]; + + default: + return []; + } +}; + +export const EarthBeadActivate: Story = { + args: { + value: 0, + columns: 1, + scaleFactor: 3, + interactive: true, + animated: true, + colorScheme: 'place-value', + colorPalette: 'default', + stepBeadHighlights: createStepBeads('earth-activate'), + currentStep: 0, + showDirectionIndicators: true, + onValueChange: action('value-changed'), + }, + parameters: { + docs: { + description: { + story: ` +**Earth Bead Activation (0 + 1)** + +- Shows green up arrow on first earth bead +- Earth beads activate by moving UP +- Click the highlighted bead to see the value change to 1 + ` + } + } + } +}; + +export const HeavenBeadActivate: Story = { + args: { + value: 0, + columns: 1, + scaleFactor: 3, + interactive: true, + animated: true, + colorScheme: 'place-value', + stepBeadHighlights: createStepBeads('heaven-activate'), + currentStep: 0, + showDirectionIndicators: true, + onValueChange: action('value-changed'), + }, + parameters: { + docs: { + description: { + story: ` +**Heaven Bead Activation (0 + 5)** + +- Shows red down arrow on heaven bead +- Heaven beads activate by moving DOWN +- Click the highlighted bead to see the value change to 5 + ` + } + } + } +}; + +export const MultiStepSequence: Story = { + args: { + value: 2, + columns: 1, + scaleFactor: 3, + interactive: true, + animated: true, + colorScheme: 'place-value', + stepBeadHighlights: createStepBeads('multi-step'), + currentStep: 0, + showDirectionIndicators: true, + onValueChange: action('value-changed'), + }, + parameters: { + docs: { + description: { + story: ` +**Multi-Step Complement (2 + 3 = 5)** + +Step progression using five complement: 3 = 5 - 2 + +**Step 0**: Red down arrow on heaven bead (add 5) +**Step 1**: Red down arrows on earth beads (remove 2) + +Use the currentStep control to see different steps highlighted. + ` + } + } + } +}; + +export const ComplexOperation: Story = { + args: { + value: 7, + columns: 2, + scaleFactor: 2.5, + interactive: true, + animated: true, + colorScheme: 'place-value', + stepBeadHighlights: createStepBeads('heaven-earth-combo'), + currentStep: 0, + showDirectionIndicators: true, + onValueChange: action('value-changed'), + }, + parameters: { + docs: { + description: { + story: ` +**Complex Multi-Column Operation** + +Shows multiple beads across different columns and types: +- Green up arrow on tens earth bead (activate) +- Red up arrow on ones heaven bead (deactivate) +- Red down arrow on ones earth bead (deactivate) + +All beads are part of step 0, showing simultaneous actions. + ` + } + } + } +}; + +// Interactive story to demonstrate step progression +export const InteractiveStepProgression: Story = { + render: (args) => { + const [currentStep, setCurrentStep] = useState(0); + const [value, setValue] = useState(2); + + const stepBeads = createStepBeads('multi-step'); + const maxSteps = Math.max(...stepBeads.map(bead => bead.stepIndex)) + 1; + + const handleValueChange = (newValue: number) => { + setValue(newValue); + action('value-changed')(newValue); + + // Auto-advance step when value changes (simple demo logic) + if (currentStep < maxSteps - 1) { + setTimeout(() => setCurrentStep(prev => prev + 1), 500); + } + }; + + const resetDemo = () => { + setValue(2); + setCurrentStep(0); + }; + + return ( +
+ +
+

Step {currentStep + 1} of {maxSteps}

+

Value: {value}

+ +
+
+ ); + }, + args: { + columns: 1, + scaleFactor: 3, + interactive: true, + animated: true, + colorScheme: 'place-value', + showDirectionIndicators: true, + }, + parameters: { + docs: { + description: { + story: ` +**Interactive Step Progression Demo** + +Demonstrates automatic step advancement: +1. Start with value 2 +2. Click the heaven bead (red down arrow) to add 5 → value becomes 7 +3. Step automatically advances to show earth bead deactivation arrows +4. Click earth beads to remove 2 → final value becomes 5 + +This simulates the tutorial experience where steps progress as users follow instructions. + ` + } + } + } +}; + +// Static showcase of all arrow types +export const AllArrowTypes: Story = { + render: () => { + const earthActivate: StepBeadHighlight[] = [ + { placeValue: 0, beadType: 'earth', position: 0, stepIndex: 0, direction: 'activate' } + ]; + + const earthDeactivate: StepBeadHighlight[] = [ + { placeValue: 0, beadType: 'earth', position: 0, stepIndex: 0, direction: 'deactivate' } + ]; + + const heavenActivate: StepBeadHighlight[] = [ + { placeValue: 0, beadType: 'heaven', stepIndex: 0, direction: 'activate' } + ]; + + const heavenDeactivate: StepBeadHighlight[] = [ + { placeValue: 0, beadType: 'heaven', stepIndex: 0, direction: 'deactivate' } + ]; + + return ( +
+
+

Earth Activate (⬆️)

+ +

+ Green up arrow - push earth bead up to activate +

+
+ +
+

Earth Deactivate (⬇️)

+ +

+ Red down arrow - release earth bead down to deactivate +

+
+ +
+

Heaven Activate (⬇️)

+ +

+ Red down arrow - pull heaven bead down to activate +

+
+ +
+

Heaven Deactivate (⬆️)

+ +

+ Green up arrow - release heaven bead up to deactivate +

+
+
+ ); + }, + parameters: { + docs: { + description: { + story: ` +**Complete Arrow Reference** + +Shows all four arrow types with correct colors and directions: + +- **Green arrows**: Positive/activating actions +- **Red arrows**: Negative/deactivating actions +- **Direction**: Based on physical bead movement direction + ` + } + } + } +}; \ No newline at end of file diff --git a/packages/abacus-react/src/step-advancement.test.tsx b/packages/abacus-react/src/step-advancement.test.tsx new file mode 100644 index 00000000..d915346f --- /dev/null +++ b/packages/abacus-react/src/step-advancement.test.tsx @@ -0,0 +1,294 @@ +import React, { useState, useEffect, useRef, useCallback, useMemo } from 'react'; +import { render, fireEvent, screen } from '@testing-library/react'; +import { vi } from 'vitest'; + +// Mock the instruction generator +const generateAbacusInstructions = (startValue: number, targetValue: number) => { + // Mock implementation for 3+14=17 case + if (startValue === 3 && targetValue === 8) { + return { + stepBeadHighlights: [{ + placeValue: 0, + beadType: 'heaven' as const, + stepIndex: 0, + direction: 'activate' as const, + order: 0 + }] + }; + } + + if (startValue === 8 && targetValue === 18) { + return { + stepBeadHighlights: [{ + placeValue: 1, + beadType: 'earth' as const, + position: 0, + stepIndex: 0, + direction: 'activate' as const, + order: 0 + }] + }; + } + + if (startValue === 18 && targetValue === 17) { + return { + stepBeadHighlights: [{ + placeValue: 0, + beadType: 'earth' as const, + position: 0, + stepIndex: 0, + direction: 'deactivate' as const, + order: 0 + }] + }; + } + + return { stepBeadHighlights: [] }; +}; + +// Test component that implements the step advancement logic +const StepAdvancementTest: React.FC = () => { + const [currentValue, setCurrentValue] = useState(3); + const [currentMultiStep, setCurrentMultiStep] = useState(0); + + const lastValueForStepAdvancement = useRef(currentValue); + const userHasInteracted = useRef(false); + + // Mock current step data (3 + 14 = 17) + const currentStep = { + startValue: 3, + targetValue: 17, + stepBeadHighlights: [ + { + placeValue: 0, + beadType: 'heaven' as const, + stepIndex: 0, + direction: 'activate' as const, + order: 0 + }, + { + placeValue: 1, + beadType: 'earth' as const, + position: 0, + stepIndex: 1, + direction: 'activate' as const, + order: 0 + }, + { + placeValue: 0, + beadType: 'earth' as const, + position: 0, + stepIndex: 2, + direction: 'deactivate' as const, + order: 0 + } + ], + totalSteps: 3 + }; + + // Define the static expected steps + const expectedSteps = useMemo(() => { + if (!currentStep.stepBeadHighlights || !currentStep.totalSteps || currentStep.totalSteps <= 1) { + return []; + } + + const stepIndices = [...new Set(currentStep.stepBeadHighlights.map(bead => bead.stepIndex))].sort(); + const steps = []; + let value = currentStep.startValue; + + if (currentStep.startValue === 3 && currentStep.targetValue === 17) { + const milestones = [8, 18, 17]; + for (let i = 0; i < stepIndices.length && i < milestones.length; i++) { + steps.push({ + index: i, + stepIndex: stepIndices[i], + targetValue: milestones[i], + startValue: value, + description: `Step ${i + 1}` + }); + value = milestones[i]; + } + } + + console.log('📋 Generated expected steps:', steps); + return steps; + }, []); + + // Get arrows for immediate next action + const getCurrentStepBeads = useCallback(() => { + if (currentValue === currentStep.targetValue) return undefined; + if (expectedSteps.length === 0) return currentStep.stepBeadHighlights; + + const currentExpectedStep = expectedSteps[currentMultiStep]; + if (!currentExpectedStep) return undefined; + + try { + const instruction = generateAbacusInstructions(currentValue, currentExpectedStep.targetValue); + const immediateAction = instruction.stepBeadHighlights?.filter(bead => bead.stepIndex === 0); + + console.log('🎯 Expected step progression:', { + currentValue, + expectedStepIndex: currentMultiStep, + expectedStepTarget: currentExpectedStep.targetValue, + expectedStepDescription: currentExpectedStep.description, + immediateActionBeads: immediateAction?.length || 0, + totalExpectedSteps: expectedSteps.length + }); + + return immediateAction && immediateAction.length > 0 ? immediateAction : undefined; + } catch (error) { + console.warn('⚠️ Failed to generate step guidance:', error); + return undefined; + } + }, [currentValue, expectedSteps, currentMultiStep]); + + // Step advancement logic + useEffect(() => { + const valueChanged = currentValue !== lastValueForStepAdvancement.current; + const currentExpectedStep = expectedSteps[currentMultiStep]; + + console.log('🔍 Expected step advancement check:', { + currentValue, + lastValue: lastValueForStepAdvancement.current, + valueChanged, + userHasInteracted: userHasInteracted.current, + expectedStepIndex: currentMultiStep, + expectedStepTarget: currentExpectedStep?.targetValue, + expectedStepReached: currentExpectedStep ? currentValue === currentExpectedStep.targetValue : false, + totalExpectedSteps: expectedSteps.length, + finalTargetReached: currentValue === currentStep?.targetValue + }); + + if (valueChanged && userHasInteracted.current && expectedSteps.length > 0 && currentExpectedStep) { + if (currentValue === currentExpectedStep.targetValue) { + const hasMoreExpectedSteps = currentMultiStep < expectedSteps.length - 1; + + console.log('🎯 Expected step completed:', { + completedStep: currentMultiStep, + targetReached: currentExpectedStep.targetValue, + hasMoreSteps: hasMoreExpectedSteps, + willAdvance: hasMoreExpectedSteps + }); + + if (hasMoreExpectedSteps) { + const timeoutId = setTimeout(() => { + console.log('⚡ Advancing to next expected step:', currentMultiStep, '→', currentMultiStep + 1); + setCurrentMultiStep(prev => prev + 1); + lastValueForStepAdvancement.current = currentValue; + }, 100); // Shorter delay for testing + + return () => clearTimeout(timeoutId); + } + } + } + }, [currentValue, currentMultiStep, expectedSteps]); + + // Update reference when step changes + useEffect(() => { + lastValueForStepAdvancement.current = currentValue; + userHasInteracted.current = false; + }, [currentMultiStep]); + + const handleValueChange = (newValue: number) => { + userHasInteracted.current = true; + setCurrentValue(newValue); + }; + + const currentStepBeads = getCurrentStepBeads(); + + return ( +
+
{currentValue}
+
{currentMultiStep}
+
{expectedSteps.length}
+
+ {expectedSteps[currentMultiStep]?.targetValue || 'N/A'} +
+
{currentStepBeads ? 'yes' : 'no'}
+ + + + + +
+ {JSON.stringify(expectedSteps)} +
+
+ ); +}; + +// Test cases +describe('Step Advancement Logic', () => { + beforeEach(() => { + vi.clearAllMocks(); + console.log = vi.fn(); + }); + + test('should generate expected steps for 3+14=17', () => { + render(); + + expect(screen.getByTestId('expected-steps-length')).toHaveTextContent('3'); + expect(screen.getByTestId('current-expected-target')).toHaveTextContent('8'); + expect(screen.getByTestId('expected-step-index')).toHaveTextContent('0'); + }); + + test('should advance from step 0 to step 1 when reaching value 8', async () => { + render(); + + // Initial state + expect(screen.getByTestId('current-value')).toHaveTextContent('3'); + expect(screen.getByTestId('expected-step-index')).toHaveTextContent('0'); + expect(screen.getByTestId('current-expected-target')).toHaveTextContent('8'); + + // Click to set value to 8 + fireEvent.click(screen.getByTestId('set-value-8')); + + // Should still be step 0 immediately + expect(screen.getByTestId('current-value')).toHaveTextContent('8'); + expect(screen.getByTestId('expected-step-index')).toHaveTextContent('0'); + + // Wait for timeout to advance step + await new Promise(resolve => setTimeout(resolve, 150)); + + // Should now be step 1 + expect(screen.getByTestId('expected-step-index')).toHaveTextContent('1'); + expect(screen.getByTestId('current-expected-target')).toHaveTextContent('18'); + }); + + test('should advance through all steps', async () => { + render(); + + // Step 0 → 1 (3 → 8) + fireEvent.click(screen.getByTestId('set-value-8')); + await new Promise(resolve => setTimeout(resolve, 150)); + expect(screen.getByTestId('expected-step-index')).toHaveTextContent('1'); + + // Step 1 → 2 (8 → 18) + fireEvent.click(screen.getByTestId('set-value-18')); + await new Promise(resolve => setTimeout(resolve, 150)); + expect(screen.getByTestId('expected-step-index')).toHaveTextContent('2'); + + // Step 2 → complete (18 → 17) + fireEvent.click(screen.getByTestId('set-value-17')); + await new Promise(resolve => setTimeout(resolve, 150)); + // Should stay at step 2 since it's the last step + expect(screen.getByTestId('expected-step-index')).toHaveTextContent('2'); + }); +}); + +export default StepAdvancementTest; \ No newline at end of file