feat(worksheets): restore mastery progression UI with 3-way mode selector
Restore the mastery progression worksheet mode that was previously working but got disconnected during recent development. Changes: - Add 'mastery' mode option to ModeSelector (Smart/Manual/Mastery Progression) - Wire ProgressionModePanel into ConfigPanel's mode selection logic - Add currentStepId field to WorksheetFormState for tracking progression step - Mastery mode displays 6-step scaffolding fade progression slider The mastery progression system uses a 1D slider mapped to a 6-step path through 3D difficulty space (digit count × regrouping complexity × scaffolding). Scaffolding cycles back when moving to higher digit counts, following a pedagogically sound pattern. Components progressionPath.ts and ProgressionModePanel.tsx already existed (dated Nov 9) but were not integrated into the UI flow. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
0d66c54991
commit
26a08859d7
|
|
@ -1,44 +1,40 @@
|
||||||
"use client";
|
'use client'
|
||||||
|
|
||||||
import { stack } from "../../../../../../styled-system/patterns";
|
import { stack } from '../../../../../../styled-system/patterns'
|
||||||
import type { WorksheetFormState } from "../types";
|
import type { WorksheetFormState } from '../types'
|
||||||
import { defaultAdditionConfig } from "@/app/create/worksheets/config-schemas";
|
import { defaultAdditionConfig } from '@/app/create/worksheets/config-schemas'
|
||||||
import { ModeSelector } from "./ModeSelector";
|
import { ModeSelector } from './ModeSelector'
|
||||||
import { StudentNameInput } from "./config-panel/StudentNameInput";
|
import { StudentNameInput } from './config-panel/StudentNameInput'
|
||||||
import { DigitRangeSection } from "./config-panel/DigitRangeSection";
|
import { DigitRangeSection } from './config-panel/DigitRangeSection'
|
||||||
import { OperatorSection } from "./config-panel/OperatorSection";
|
import { OperatorSection } from './config-panel/OperatorSection'
|
||||||
import { ProgressiveDifficultyToggle } from "./config-panel/ProgressiveDifficultyToggle";
|
import { ProgressiveDifficultyToggle } from './config-panel/ProgressiveDifficultyToggle'
|
||||||
import { SmartModeControls } from "./config-panel/SmartModeControls";
|
import { SmartModeControls } from './config-panel/SmartModeControls'
|
||||||
import { ManualModeControls } from "./config-panel/ManualModeControls";
|
import { ManualModeControls } from './config-panel/ManualModeControls'
|
||||||
|
import { ProgressionModePanel } from './config-panel/ProgressionModePanel'
|
||||||
|
|
||||||
interface ConfigPanelProps {
|
interface ConfigPanelProps {
|
||||||
formState: WorksheetFormState;
|
formState: WorksheetFormState
|
||||||
onChange: (updates: Partial<WorksheetFormState>) => void;
|
onChange: (updates: Partial<WorksheetFormState>) => void
|
||||||
isDark?: boolean;
|
isDark?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ConfigPanel({
|
export function ConfigPanel({ formState, onChange, isDark = false }: ConfigPanelProps) {
|
||||||
formState,
|
|
||||||
onChange,
|
|
||||||
isDark = false,
|
|
||||||
}: ConfigPanelProps) {
|
|
||||||
// Handler for mode switching
|
// Handler for mode switching
|
||||||
const handleModeChange = (newMode: "smart" | "manual") => {
|
const handleModeChange = (newMode: 'smart' | 'manual' | 'mastery') => {
|
||||||
if (formState.mode === newMode) {
|
if (formState.mode === newMode) {
|
||||||
return; // No change needed
|
return // No change needed
|
||||||
}
|
}
|
||||||
|
|
||||||
if (newMode === "smart") {
|
if (newMode === 'smart') {
|
||||||
// Switching to Smart mode
|
// Switching to Smart mode
|
||||||
// Use current displayRules if available, otherwise default to earlyLearner
|
// Use current displayRules if available, otherwise default to earlyLearner
|
||||||
const displayRules =
|
const displayRules = formState.displayRules ?? defaultAdditionConfig.displayRules
|
||||||
formState.displayRules ?? defaultAdditionConfig.displayRules;
|
|
||||||
onChange({
|
onChange({
|
||||||
mode: "smart",
|
mode: 'smart',
|
||||||
displayRules,
|
displayRules,
|
||||||
difficultyProfile: "earlyLearner",
|
difficultyProfile: 'earlyLearner',
|
||||||
} as unknown as Partial<WorksheetFormState>);
|
} as unknown as Partial<WorksheetFormState>)
|
||||||
} else {
|
} else if (newMode === 'manual') {
|
||||||
// Switching to Manual mode
|
// Switching to Manual mode
|
||||||
// Convert current displayRules to boolean flags if available
|
// Convert current displayRules to boolean flags if available
|
||||||
let booleanFlags = {
|
let booleanFlags = {
|
||||||
|
|
@ -49,32 +45,38 @@ export function ConfigPanel({
|
||||||
showProblemNumbers: true,
|
showProblemNumbers: true,
|
||||||
showCellBorder: true,
|
showCellBorder: true,
|
||||||
showTenFramesForAll: false,
|
showTenFramesForAll: false,
|
||||||
};
|
}
|
||||||
|
|
||||||
if (formState.displayRules) {
|
if (formState.displayRules) {
|
||||||
// Convert 'always' to true, everything else to false
|
// Convert 'always' to true, everything else to false
|
||||||
booleanFlags = {
|
booleanFlags = {
|
||||||
showCarryBoxes: formState.displayRules.carryBoxes === "always",
|
showCarryBoxes: formState.displayRules.carryBoxes === 'always',
|
||||||
showAnswerBoxes: formState.displayRules.answerBoxes === "always",
|
showAnswerBoxes: formState.displayRules.answerBoxes === 'always',
|
||||||
showPlaceValueColors:
|
showPlaceValueColors: formState.displayRules.placeValueColors === 'always',
|
||||||
formState.displayRules.placeValueColors === "always",
|
showTenFrames: formState.displayRules.tenFrames === 'always',
|
||||||
showTenFrames: formState.displayRules.tenFrames === "always",
|
showProblemNumbers: formState.displayRules.problemNumbers === 'always',
|
||||||
showProblemNumbers:
|
showCellBorder: formState.displayRules.cellBorders === 'always',
|
||||||
formState.displayRules.problemNumbers === "always",
|
|
||||||
showCellBorder: formState.displayRules.cellBorders === "always",
|
|
||||||
showTenFramesForAll: false,
|
showTenFramesForAll: false,
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onChange({
|
onChange({
|
||||||
mode: "manual",
|
mode: 'manual',
|
||||||
...booleanFlags,
|
...booleanFlags,
|
||||||
} as unknown as Partial<WorksheetFormState>);
|
} as unknown as Partial<WorksheetFormState>)
|
||||||
|
} else {
|
||||||
|
// Switching to Mastery mode
|
||||||
|
// Mastery mode uses Smart mode under the hood with skill-based configuration
|
||||||
|
const displayRules = formState.displayRules ?? defaultAdditionConfig.displayRules
|
||||||
|
onChange({
|
||||||
|
mode: 'mastery',
|
||||||
|
displayRules,
|
||||||
|
} as unknown as Partial<WorksheetFormState>)
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div data-component="config-panel" className={stack({ gap: "3" })}>
|
<div data-component="config-panel" className={stack({ gap: '3' })}>
|
||||||
{/* Student Name */}
|
{/* Student Name */}
|
||||||
<StudentNameInput
|
<StudentNameInput
|
||||||
value={formState.name}
|
value={formState.name}
|
||||||
|
|
@ -98,7 +100,7 @@ export function ConfigPanel({
|
||||||
|
|
||||||
{/* Mode Selector */}
|
{/* Mode Selector */}
|
||||||
<ModeSelector
|
<ModeSelector
|
||||||
currentMode={formState.mode ?? "smart"}
|
currentMode={formState.mode ?? 'smart'}
|
||||||
onChange={handleModeChange}
|
onChange={handleModeChange}
|
||||||
isDark={isDark}
|
isDark={isDark}
|
||||||
/>
|
/>
|
||||||
|
|
@ -111,22 +113,19 @@ export function ConfigPanel({
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Smart Mode Controls */}
|
{/* Smart Mode Controls */}
|
||||||
{(!formState.mode || formState.mode === "smart") && (
|
{(!formState.mode || formState.mode === 'smart') && (
|
||||||
<SmartModeControls
|
<SmartModeControls formState={formState} onChange={onChange} isDark={isDark} />
|
||||||
formState={formState}
|
|
||||||
onChange={onChange}
|
|
||||||
isDark={isDark}
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Manual Mode Controls */}
|
{/* Manual Mode Controls */}
|
||||||
{formState.mode === "manual" && (
|
{formState.mode === 'manual' && (
|
||||||
<ManualModeControls
|
<ManualModeControls formState={formState} onChange={onChange} isDark={isDark} />
|
||||||
formState={formState}
|
)}
|
||||||
onChange={onChange}
|
|
||||||
isDark={isDark}
|
{/* Mastery Mode Controls */}
|
||||||
/>
|
{formState.mode === 'mastery' && (
|
||||||
|
<ProgressionModePanel formState={formState} onChange={onChange} isDark={isDark} />
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,42 +1,38 @@
|
||||||
"use client";
|
'use client'
|
||||||
|
|
||||||
import { css } from "../../../../../../styled-system/css";
|
import { css } from '../../../../../../styled-system/css'
|
||||||
|
|
||||||
interface ModeSelectorProps {
|
interface ModeSelectorProps {
|
||||||
currentMode: "smart" | "manual";
|
currentMode: 'smart' | 'manual' | 'mastery'
|
||||||
onChange: (mode: "smart" | "manual") => void;
|
onChange: (mode: 'smart' | 'manual' | 'mastery') => void
|
||||||
isDark?: boolean;
|
isDark?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Mode selector for worksheet generation
|
* Mode selector for worksheet generation
|
||||||
* Allows switching between Smart Difficulty and Manual Control modes
|
* Allows switching between Smart Difficulty, Manual Control, and Mastery Progression modes
|
||||||
*/
|
*/
|
||||||
export function ModeSelector({
|
export function ModeSelector({ currentMode, onChange, isDark = false }: ModeSelectorProps) {
|
||||||
currentMode,
|
|
||||||
onChange,
|
|
||||||
isDark = false,
|
|
||||||
}: ModeSelectorProps) {
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
data-component="mode-selector"
|
data-component="mode-selector"
|
||||||
className={css({
|
className={css({
|
||||||
marginBottom: "1.5rem",
|
marginBottom: '1.5rem',
|
||||||
padding: "1rem",
|
padding: '1rem',
|
||||||
backgroundColor: isDark ? "gray.700" : "gray.50",
|
backgroundColor: isDark ? 'gray.700' : 'gray.50',
|
||||||
borderRadius: "8px",
|
borderRadius: '8px',
|
||||||
border: "1px solid",
|
border: '1px solid',
|
||||||
borderColor: isDark ? "gray.600" : "gray.200",
|
borderColor: isDark ? 'gray.600' : 'gray.200',
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<h3
|
<h3
|
||||||
className={css({
|
className={css({
|
||||||
fontSize: "0.875rem",
|
fontSize: '0.875rem',
|
||||||
fontWeight: "600",
|
fontWeight: '600',
|
||||||
color: isDark ? "gray.200" : "gray.700",
|
color: isDark ? 'gray.200' : 'gray.700',
|
||||||
marginBottom: "0.75rem",
|
marginBottom: '0.75rem',
|
||||||
textTransform: "uppercase",
|
textTransform: 'uppercase',
|
||||||
letterSpacing: "0.05em",
|
letterSpacing: '0.05em',
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
Worksheet Mode
|
Worksheet Mode
|
||||||
|
|
@ -45,67 +41,52 @@ export function ModeSelector({
|
||||||
<div
|
<div
|
||||||
data-element="mode-buttons"
|
data-element="mode-buttons"
|
||||||
className={css({
|
className={css({
|
||||||
display: "flex",
|
display: 'grid',
|
||||||
gap: "0.75rem",
|
gridTemplateColumns: 'repeat(3, 1fr)',
|
||||||
|
gap: '0.75rem',
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
{/* Smart Difficulty Mode Button */}
|
{/* Smart Difficulty Mode Button */}
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
data-action="select-smart-mode"
|
data-action="select-smart-mode"
|
||||||
data-selected={currentMode === "smart"}
|
data-selected={currentMode === 'smart'}
|
||||||
onClick={() => onChange("smart")}
|
onClick={() => onChange('smart')}
|
||||||
className={css({
|
className={css({
|
||||||
flex: 1,
|
padding: '1rem',
|
||||||
padding: "1rem",
|
borderRadius: '6px',
|
||||||
borderRadius: "6px",
|
border: '2px solid',
|
||||||
border: "2px solid",
|
borderColor: currentMode === 'smart' ? 'blue.500' : isDark ? 'gray.500' : 'gray.300',
|
||||||
borderColor:
|
backgroundColor: currentMode === 'smart' ? 'blue.50' : isDark ? 'gray.600' : 'white',
|
||||||
currentMode === "smart"
|
cursor: 'pointer',
|
||||||
? "blue.500"
|
transition: 'all 0.2s',
|
||||||
: isDark
|
textAlign: 'left',
|
||||||
? "gray.500"
|
|
||||||
: "gray.300",
|
|
||||||
backgroundColor:
|
|
||||||
currentMode === "smart"
|
|
||||||
? "blue.50"
|
|
||||||
: isDark
|
|
||||||
? "gray.600"
|
|
||||||
: "white",
|
|
||||||
cursor: "pointer",
|
|
||||||
transition: "all 0.2s",
|
|
||||||
textAlign: "left",
|
|
||||||
_hover: {
|
_hover: {
|
||||||
borderColor: "blue.400",
|
borderColor: 'blue.400',
|
||||||
backgroundColor: "blue.50",
|
backgroundColor: 'blue.50',
|
||||||
},
|
},
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className={css({
|
className={css({
|
||||||
display: "flex",
|
display: 'flex',
|
||||||
alignItems: "center",
|
alignItems: 'center',
|
||||||
gap: "0.5rem",
|
gap: '0.5rem',
|
||||||
marginBottom: "0.5rem",
|
marginBottom: '0.5rem',
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
className={css({
|
className={css({
|
||||||
fontSize: "1.25rem",
|
fontSize: '1.25rem',
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
🎯
|
🎯
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
className={css({
|
className={css({
|
||||||
fontSize: "0.875rem",
|
fontSize: '0.875rem',
|
||||||
fontWeight: "600",
|
fontWeight: '600',
|
||||||
color:
|
color: currentMode === 'smart' ? 'blue.700' : isDark ? 'gray.200' : 'gray.700',
|
||||||
currentMode === "smart"
|
|
||||||
? "blue.700"
|
|
||||||
: isDark
|
|
||||||
? "gray.200"
|
|
||||||
: "gray.700",
|
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
Smart Difficulty
|
Smart Difficulty
|
||||||
|
|
@ -113,18 +94,12 @@ export function ModeSelector({
|
||||||
</div>
|
</div>
|
||||||
<p
|
<p
|
||||||
className={css({
|
className={css({
|
||||||
fontSize: "0.75rem",
|
fontSize: '0.75rem',
|
||||||
color:
|
color: currentMode === 'smart' ? 'blue.600' : isDark ? 'gray.400' : 'gray.600',
|
||||||
currentMode === "smart"
|
lineHeight: '1.4',
|
||||||
? "blue.600"
|
|
||||||
: isDark
|
|
||||||
? "gray.400"
|
|
||||||
: "gray.600",
|
|
||||||
lineHeight: "1.4",
|
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
Research-backed progressive difficulty with adaptive scaffolding per
|
Research-backed progressive difficulty with adaptive scaffolding per problem
|
||||||
problem
|
|
||||||
</p>
|
</p>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
|
@ -132,59 +107,43 @@ export function ModeSelector({
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
data-action="select-manual-mode"
|
data-action="select-manual-mode"
|
||||||
data-selected={currentMode === "manual"}
|
data-selected={currentMode === 'manual'}
|
||||||
onClick={() => onChange("manual")}
|
onClick={() => onChange('manual')}
|
||||||
className={css({
|
className={css({
|
||||||
flex: 1,
|
padding: '1rem',
|
||||||
padding: "1rem",
|
borderRadius: '6px',
|
||||||
borderRadius: "6px",
|
border: '2px solid',
|
||||||
border: "2px solid",
|
borderColor: currentMode === 'manual' ? 'blue.500' : isDark ? 'gray.500' : 'gray.300',
|
||||||
borderColor:
|
backgroundColor: currentMode === 'manual' ? 'blue.50' : isDark ? 'gray.600' : 'white',
|
||||||
currentMode === "manual"
|
cursor: 'pointer',
|
||||||
? "blue.500"
|
transition: 'all 0.2s',
|
||||||
: isDark
|
textAlign: 'left',
|
||||||
? "gray.500"
|
|
||||||
: "gray.300",
|
|
||||||
backgroundColor:
|
|
||||||
currentMode === "manual"
|
|
||||||
? "blue.50"
|
|
||||||
: isDark
|
|
||||||
? "gray.600"
|
|
||||||
: "white",
|
|
||||||
cursor: "pointer",
|
|
||||||
transition: "all 0.2s",
|
|
||||||
textAlign: "left",
|
|
||||||
_hover: {
|
_hover: {
|
||||||
borderColor: "blue.400",
|
borderColor: 'blue.400',
|
||||||
backgroundColor: "blue.50",
|
backgroundColor: 'blue.50',
|
||||||
},
|
},
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className={css({
|
className={css({
|
||||||
display: "flex",
|
display: 'flex',
|
||||||
alignItems: "center",
|
alignItems: 'center',
|
||||||
gap: "0.5rem",
|
gap: '0.5rem',
|
||||||
marginBottom: "0.5rem",
|
marginBottom: '0.5rem',
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
className={css({
|
className={css({
|
||||||
fontSize: "1.25rem",
|
fontSize: '1.25rem',
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
🎛️
|
🎛️
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
className={css({
|
className={css({
|
||||||
fontSize: "0.875rem",
|
fontSize: '0.875rem',
|
||||||
fontWeight: "600",
|
fontWeight: '600',
|
||||||
color:
|
color: currentMode === 'manual' ? 'blue.700' : isDark ? 'gray.200' : 'gray.700',
|
||||||
currentMode === "manual"
|
|
||||||
? "blue.700"
|
|
||||||
: isDark
|
|
||||||
? "gray.200"
|
|
||||||
: "gray.700",
|
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
Manual Control
|
Manual Control
|
||||||
|
|
@ -192,21 +151,72 @@ export function ModeSelector({
|
||||||
</div>
|
</div>
|
||||||
<p
|
<p
|
||||||
className={css({
|
className={css({
|
||||||
fontSize: "0.75rem",
|
fontSize: '0.75rem',
|
||||||
color:
|
color: currentMode === 'manual' ? 'blue.600' : isDark ? 'gray.400' : 'gray.600',
|
||||||
currentMode === "manual"
|
lineHeight: '1.4',
|
||||||
? "blue.600"
|
|
||||||
: isDark
|
|
||||||
? "gray.400"
|
|
||||||
: "gray.600",
|
|
||||||
lineHeight: "1.4",
|
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
Full control over display options with uniform scaffolding across
|
Full control over display options with uniform scaffolding across all problems
|
||||||
all problems
|
</p>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{/* Mastery Progression Mode Button */}
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
data-action="select-mastery-mode"
|
||||||
|
data-selected={currentMode === 'mastery'}
|
||||||
|
onClick={() => onChange('mastery')}
|
||||||
|
className={css({
|
||||||
|
padding: '1rem',
|
||||||
|
borderRadius: '6px',
|
||||||
|
border: '2px solid',
|
||||||
|
borderColor: currentMode === 'mastery' ? 'blue.500' : isDark ? 'gray.500' : 'gray.300',
|
||||||
|
backgroundColor: currentMode === 'mastery' ? 'blue.50' : isDark ? 'gray.600' : 'white',
|
||||||
|
cursor: 'pointer',
|
||||||
|
transition: 'all 0.2s',
|
||||||
|
textAlign: 'left',
|
||||||
|
_hover: {
|
||||||
|
borderColor: 'blue.400',
|
||||||
|
backgroundColor: 'blue.50',
|
||||||
|
},
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={css({
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: '0.5rem',
|
||||||
|
marginBottom: '0.5rem',
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
className={css({
|
||||||
|
fontSize: '1.25rem',
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
🎓
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
className={css({
|
||||||
|
fontSize: '0.875rem',
|
||||||
|
fontWeight: '600',
|
||||||
|
color: currentMode === 'mastery' ? 'blue.700' : isDark ? 'gray.200' : 'gray.700',
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
Mastery Progression
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<p
|
||||||
|
className={css({
|
||||||
|
fontSize: '0.75rem',
|
||||||
|
color: currentMode === 'mastery' ? 'blue.600' : isDark ? 'gray.400' : 'gray.600',
|
||||||
|
lineHeight: '1.4',
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
Skill-based progression with automatic review mixing for pedagogical practice
|
||||||
</p>
|
</p>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import type {
|
||||||
AdditionConfigV4,
|
AdditionConfigV4,
|
||||||
AdditionConfigV4Smart,
|
AdditionConfigV4Smart,
|
||||||
AdditionConfigV4Manual,
|
AdditionConfigV4Manual,
|
||||||
} from "../config-schemas";
|
} from '../config-schemas'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Complete, validated configuration for worksheet generation
|
* Complete, validated configuration for worksheet generation
|
||||||
|
|
@ -18,25 +18,25 @@ import type {
|
||||||
*/
|
*/
|
||||||
export type WorksheetConfig = AdditionConfigV4 & {
|
export type WorksheetConfig = AdditionConfigV4 & {
|
||||||
// Problem set - DERIVED state
|
// Problem set - DERIVED state
|
||||||
total: number; // total = problemsPerPage * pages
|
total: number // total = problemsPerPage * pages
|
||||||
rows: number; // rows = (problemsPerPage / cols) * pages
|
rows: number // rows = (problemsPerPage / cols) * pages
|
||||||
|
|
||||||
// Personalization
|
// Personalization
|
||||||
date: string;
|
date: string
|
||||||
seed: number;
|
seed: number
|
||||||
|
|
||||||
// Layout
|
// Layout
|
||||||
page: {
|
page: {
|
||||||
wIn: number;
|
wIn: number
|
||||||
hIn: number;
|
hIn: number
|
||||||
};
|
}
|
||||||
margins: {
|
margins: {
|
||||||
left: number;
|
left: number
|
||||||
right: number;
|
right: number
|
||||||
top: number;
|
top: number
|
||||||
bottom: number;
|
bottom: number
|
||||||
};
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Partial form state - user may be editing, fields optional
|
* Partial form state - user may be editing, fields optional
|
||||||
|
|
@ -52,55 +52,56 @@ export type WorksheetConfig = AdditionConfigV4 & {
|
||||||
* This type is intentionally permissive during form editing to allow fields from
|
* This type is intentionally permissive during form editing to allow fields from
|
||||||
* both modes to exist temporarily. Validation will enforce mode consistency.
|
* both modes to exist temporarily. Validation will enforce mode consistency.
|
||||||
*/
|
*/
|
||||||
export type WorksheetFormState = Partial<
|
export type WorksheetFormState = Partial<Omit<AdditionConfigV4Smart, 'version'>> &
|
||||||
Omit<AdditionConfigV4Smart, "version">
|
Partial<Omit<AdditionConfigV4Manual, 'version'>> & {
|
||||||
> &
|
|
||||||
Partial<Omit<AdditionConfigV4Manual, "version">> & {
|
|
||||||
// DERIVED state (calculated from primary state)
|
// DERIVED state (calculated from primary state)
|
||||||
rows?: number;
|
rows?: number
|
||||||
total?: number;
|
total?: number
|
||||||
date?: string;
|
date?: string
|
||||||
seed?: number;
|
seed?: number
|
||||||
};
|
|
||||||
|
// Mastery progression mode
|
||||||
|
currentStepId?: string // Current step in progression path
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Worksheet operator type
|
* Worksheet operator type
|
||||||
*/
|
*/
|
||||||
export type WorksheetOperator = "addition" | "subtraction" | "mixed";
|
export type WorksheetOperator = 'addition' | 'subtraction' | 'mixed'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A single addition problem
|
* A single addition problem
|
||||||
*/
|
*/
|
||||||
export interface AdditionProblem {
|
export interface AdditionProblem {
|
||||||
a: number;
|
a: number
|
||||||
b: number;
|
b: number
|
||||||
operator: "+";
|
operator: '+'
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A single subtraction problem
|
* A single subtraction problem
|
||||||
*/
|
*/
|
||||||
export interface SubtractionProblem {
|
export interface SubtractionProblem {
|
||||||
minuend: number;
|
minuend: number
|
||||||
subtrahend: number;
|
subtrahend: number
|
||||||
operator: "−"; // Proper minus sign (U+2212)
|
operator: '−' // Proper minus sign (U+2212)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Unified problem type (addition or subtraction)
|
* Unified problem type (addition or subtraction)
|
||||||
*/
|
*/
|
||||||
export type WorksheetProblem = AdditionProblem | SubtractionProblem;
|
export type WorksheetProblem = AdditionProblem | SubtractionProblem
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validation result
|
* Validation result
|
||||||
*/
|
*/
|
||||||
export interface ValidationResult {
|
export interface ValidationResult {
|
||||||
isValid: boolean;
|
isValid: boolean
|
||||||
config?: WorksheetConfig;
|
config?: WorksheetConfig
|
||||||
errors?: string[];
|
errors?: string[]
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Problem category for difficulty control
|
* Problem category for difficulty control
|
||||||
*/
|
*/
|
||||||
export type ProblemCategory = "non" | "onesOnly" | "both";
|
export type ProblemCategory = 'non' | 'onesOnly' | 'both'
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue