From 9a3afb17ba85a64a28c0dd25980b4c92e3da5483 Mon Sep 17 00:00:00 2001 From: Thomas Hallock Date: Sat, 20 Sep 2025 18:17:35 -0500 Subject: [PATCH] feat: implement skill-based practice step editor system MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Added comprehensive practice problem generation system with skill-based constraints: ## New Components - SkillSelector: Visual interface for configuring abacus calculation skills - PracticeStepEditor: Full editor for practice step configuration - Enhanced TutorialEditor with practice step management ## Skill System Features - Five complements (4=5-1, 3=5-2, 2=5-3, 1=5-4) - Ten complements (9=10-1, 8=10-2, ..., 1=10-9) - Basic operations (direct addition, heaven bead, combinations) - Required/target/forbidden skill modes - Quick preset configurations ## Editor Capabilities - Visual skill selection with color-coded states - Advanced constraints (number ranges, sum limits) - Real-time configuration summary - Problem generation based on user's mastered skills - Integration with existing tutorial editor workflow ## Documentation - Comprehensive practice problem system documentation - Storybook stories for all components - Type safety with TypeScript interfaces This implements the sophisticated skill-based problem generation discussed in requirements, ensuring learners only encounter problems they can solve with their current abacus technique knowledge. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- .../tutorial/PracticeStepEditor.stories.tsx | 200 +++++++ .../tutorial/PracticeStepEditor.tsx | 506 ++++++++++++++++++ .../src/components/tutorial/SkillSelector.tsx | 240 +++++++++ .../tutorial/TutorialEditor.stories.tsx | 70 +++ .../components/tutorial/TutorialEditor.tsx | 104 +++- apps/web/src/types/tutorial.ts | 104 +++- 6 files changed, 1222 insertions(+), 2 deletions(-) create mode 100644 apps/web/src/components/tutorial/PracticeStepEditor.stories.tsx create mode 100644 apps/web/src/components/tutorial/PracticeStepEditor.tsx create mode 100644 apps/web/src/components/tutorial/SkillSelector.tsx diff --git a/apps/web/src/components/tutorial/PracticeStepEditor.stories.tsx b/apps/web/src/components/tutorial/PracticeStepEditor.stories.tsx new file mode 100644 index 00000000..184a2bbe --- /dev/null +++ b/apps/web/src/components/tutorial/PracticeStepEditor.stories.tsx @@ -0,0 +1,200 @@ +import type { Meta, StoryObj } from '@storybook/react' +import { action } from '@storybook/addon-actions' +import { PracticeStepEditor } from './PracticeStepEditor' +import { createBasicSkillSet, createEmptySkillSet } from '../../types/tutorial' + +const meta: Meta = { + title: 'Tutorial/PracticeStepEditor', + component: PracticeStepEditor, + parameters: { + layout: 'padded', + docs: { + description: { + component: ` +The PracticeStepEditor component provides a comprehensive interface for configuring skill-based practice problem generation. + +## Features +- Visual skill selection with color-coded modes (required, target, forbidden) +- Quick preset configurations for common skill levels +- Advanced constraints for number ranges and sum limits +- Real-time configuration summary and validation +- Support for five complements (4=5-1, 3=5-2, etc.) and ten complements (9=10-1, 8=10-2, etc.) + +## Skill System +The editor implements a sophisticated skill-based system where problems are generated based on specific abacus calculation techniques the user has mastered. This ensures learners only encounter problems they can solve with their current knowledge. + ` + } + } + }, + tags: ['autodocs'] +} + +export default meta +type Story = StoryObj + +// Basic practice step for beginners +const basicPracticeStep = { + id: 'practice-basic', + title: 'Practice: Basic Addition (1-4)', + description: 'Practice adding numbers 1-4 using only earth beads', + problemCount: 12, + maxTerms: 3, + requiredSkills: createBasicSkillSet(), + numberRange: { min: 1, max: 4 }, + sumConstraints: { maxSum: 9 } +} + +// Advanced practice step with five complements +const fiveComplementsPracticeStep = { + id: 'practice-five-complements', + title: 'Practice: Five Complements', + description: 'Practice using five complement techniques when earth section is full', + problemCount: 15, + maxTerms: 4, + requiredSkills: { + basic: { + directAddition: true, + heavenBead: true, + simpleCombinations: true + }, + fiveComplements: { + "4=5-1": true, + "3=5-2": true, + "2=5-3": false, + "1=5-4": false + }, + tenComplements: createEmptySkillSet().tenComplements + }, + targetSkills: { + fiveComplements: { + "4=5-1": true, + "3=5-2": true + } + }, + numberRange: { min: 1, max: 9 }, + sumConstraints: { maxSum: 9 } +} + +// Advanced practice with ten complements +const tenComplementsPracticeStep = { + id: 'practice-ten-complements', + title: 'Practice: Ten Complements & Carrying', + description: 'Practice advanced carrying operations using ten complement techniques', + problemCount: 20, + maxTerms: 5, + requiredSkills: { + basic: { + directAddition: true, + heavenBead: true, + simpleCombinations: true + }, + fiveComplements: { + "4=5-1": true, + "3=5-2": true, + "2=5-3": true, + "1=5-4": true + }, + tenComplements: { + "9=10-1": true, + "8=10-2": true, + "7=10-3": true, + "6=10-4": false, + "5=10-5": false, + "4=10-6": false, + "3=10-7": false, + "2=10-8": false, + "1=10-9": false + } + }, + targetSkills: { + tenComplements: { + "9=10-1": true, + "8=10-2": true, + "7=10-3": true + } + }, + numberRange: { min: 1, max: 99 }, + sumConstraints: { maxSum: 99, minSum: 10 } +} + +export const BasicPractice: Story = { + args: { + step: basicPracticeStep, + onChange: action('practice-step-changed'), + onDelete: action('practice-step-deleted') + }, + parameters: { + docs: { + description: { + story: 'Basic practice step configuration for beginners learning earth bead addition (1-4).' + } + } + } +} + +export const FiveComplements: Story = { + args: { + step: fiveComplementsPracticeStep, + onChange: action('practice-step-changed'), + onDelete: action('practice-step-deleted') + }, + parameters: { + docs: { + description: { + story: 'Practice step focused on five complement techniques (4=5-1, 3=5-2, etc.) with target skills specified.' + } + } + } +} + +export const TenComplements: Story = { + args: { + step: tenComplementsPracticeStep, + onChange: action('practice-step-changed'), + onDelete: action('practice-step-deleted') + }, + parameters: { + docs: { + description: { + story: 'Advanced practice step with ten complement operations for multi-column arithmetic with carrying.' + } + } + } +} + +export const EmptyStep: Story = { + args: { + step: { + id: 'practice-empty', + title: 'New Practice Step', + description: '', + problemCount: 10, + maxTerms: 3, + requiredSkills: createBasicSkillSet() + }, + onChange: action('practice-step-changed'), + onDelete: action('practice-step-deleted') + }, + parameters: { + docs: { + description: { + story: 'Empty practice step configuration showing the default state when creating a new practice step.' + } + } + } +} + +export const WithoutDelete: Story = { + args: { + step: basicPracticeStep, + onChange: action('practice-step-changed') + // onDelete omitted + }, + parameters: { + docs: { + description: { + story: 'Practice step editor without delete functionality (onDelete prop omitted).' + } + } + } +} \ No newline at end of file diff --git a/apps/web/src/components/tutorial/PracticeStepEditor.tsx b/apps/web/src/components/tutorial/PracticeStepEditor.tsx new file mode 100644 index 00000000..3d7e74d1 --- /dev/null +++ b/apps/web/src/components/tutorial/PracticeStepEditor.tsx @@ -0,0 +1,506 @@ +'use client' + +import { useState, useCallback } from 'react' +import { css } from '../../styled-system/css' +import { vstack, hstack } from '../../styled-system/patterns' +import { PracticeStep, SkillSet, createBasicSkillSet, createEmptySkillSet } from '../../types/tutorial' +import { SkillSelector } from './SkillSelector' + +interface PracticeStepEditorProps { + step: PracticeStep + onChange: (step: PracticeStep) => void + onDelete?: () => void + className?: string +} + +export function PracticeStepEditor({ + step, + onChange, + onDelete, + className +}: PracticeStepEditorProps) { + const [showAdvanced, setShowAdvanced] = useState(false) + + const updateStep = useCallback((updates: Partial) => { + onChange({ ...step, ...updates }) + }, [step, onChange]) + + const updateRequiredSkills = useCallback((skills: SkillSet) => { + updateStep({ requiredSkills: skills }) + }, [updateStep]) + + const updateTargetSkills = useCallback((skills: Partial) => { + updateStep({ targetSkills: skills }) + }, [updateStep]) + + const updateForbiddenSkills = useCallback((skills: Partial) => { + updateStep({ forbiddenSkills: skills }) + }, [updateStep]) + + // Convert partial skill sets to full skill sets for the selector + const targetSkillsForSelector: SkillSet = { + basic: { + directAddition: step.targetSkills?.basic?.directAddition || false, + heavenBead: step.targetSkills?.basic?.heavenBead || false, + simpleCombinations: step.targetSkills?.basic?.simpleCombinations || false + }, + fiveComplements: { + "4=5-1": step.targetSkills?.fiveComplements?.["4=5-1"] || false, + "3=5-2": step.targetSkills?.fiveComplements?.["3=5-2"] || false, + "2=5-3": step.targetSkills?.fiveComplements?.["2=5-3"] || false, + "1=5-4": step.targetSkills?.fiveComplements?.["1=5-4"] || false + }, + tenComplements: { + "9=10-1": step.targetSkills?.tenComplements?.["9=10-1"] || false, + "8=10-2": step.targetSkills?.tenComplements?.["8=10-2"] || false, + "7=10-3": step.targetSkills?.tenComplements?.["7=10-3"] || false, + "6=10-4": step.targetSkills?.tenComplements?.["6=10-4"] || false, + "5=10-5": step.targetSkills?.tenComplements?.["5=10-5"] || false, + "4=10-6": step.targetSkills?.tenComplements?.["4=10-6"] || false, + "3=10-7": step.targetSkills?.tenComplements?.["3=10-7"] || false, + "2=10-8": step.targetSkills?.tenComplements?.["2=10-8"] || false, + "1=10-9": step.targetSkills?.tenComplements?.["1=10-9"] || false + } + } + + const presetConfigurations = [ + { + name: 'Basic Addition (1-4)', + skills: createBasicSkillSet() + }, + { + name: 'With Heaven Bead', + skills: { + ...createBasicSkillSet(), + basic: { ...createBasicSkillSet().basic, heavenBead: true, simpleCombinations: true } + } + }, + { + name: 'First Five Complement (4=5-1)', + skills: { + ...createBasicSkillSet(), + basic: { directAddition: true, heavenBead: true, simpleCombinations: true }, + fiveComplements: { ...createEmptySkillSet().fiveComplements, "4=5-1": true } + } + }, + { + name: 'All Five Complements', + skills: { + ...createBasicSkillSet(), + basic: { directAddition: true, heavenBead: true, simpleCombinations: true }, + fiveComplements: { "4=5-1": true, "3=5-2": true, "2=5-3": true, "1=5-4": true } + } + } + ] + + return ( +
+
+ {/* Header */} +
+

+ Practice Step Editor +

+ {onDelete && ( + + )} +
+ + {/* Basic Information */} +
+
+ + updateStep({ title: e.target.value })} + className={css({ + w: 'full', + px: 3, + py: 2, + border: '1px solid', + borderColor: 'gray.300', + rounded: 'md', + fontSize: 'sm' + })} + placeholder="e.g., Practice: Basic Addition" + /> +
+ +
+ +