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 type { WorksheetFormState } from "../types";
|
||||
import { defaultAdditionConfig } from "@/app/create/worksheets/config-schemas";
|
||||
import { ModeSelector } from "./ModeSelector";
|
||||
import { StudentNameInput } from "./config-panel/StudentNameInput";
|
||||
import { DigitRangeSection } from "./config-panel/DigitRangeSection";
|
||||
import { OperatorSection } from "./config-panel/OperatorSection";
|
||||
import { ProgressiveDifficultyToggle } from "./config-panel/ProgressiveDifficultyToggle";
|
||||
import { SmartModeControls } from "./config-panel/SmartModeControls";
|
||||
import { ManualModeControls } from "./config-panel/ManualModeControls";
|
||||
import { stack } from '../../../../../../styled-system/patterns'
|
||||
import type { WorksheetFormState } from '../types'
|
||||
import { defaultAdditionConfig } from '@/app/create/worksheets/config-schemas'
|
||||
import { ModeSelector } from './ModeSelector'
|
||||
import { StudentNameInput } from './config-panel/StudentNameInput'
|
||||
import { DigitRangeSection } from './config-panel/DigitRangeSection'
|
||||
import { OperatorSection } from './config-panel/OperatorSection'
|
||||
import { ProgressiveDifficultyToggle } from './config-panel/ProgressiveDifficultyToggle'
|
||||
import { SmartModeControls } from './config-panel/SmartModeControls'
|
||||
import { ManualModeControls } from './config-panel/ManualModeControls'
|
||||
import { ProgressionModePanel } from './config-panel/ProgressionModePanel'
|
||||
|
||||
interface ConfigPanelProps {
|
||||
formState: WorksheetFormState;
|
||||
onChange: (updates: Partial<WorksheetFormState>) => void;
|
||||
isDark?: boolean;
|
||||
formState: WorksheetFormState
|
||||
onChange: (updates: Partial<WorksheetFormState>) => void
|
||||
isDark?: boolean
|
||||
}
|
||||
|
||||
export function ConfigPanel({
|
||||
formState,
|
||||
onChange,
|
||||
isDark = false,
|
||||
}: ConfigPanelProps) {
|
||||
export function ConfigPanel({ formState, onChange, isDark = false }: ConfigPanelProps) {
|
||||
// Handler for mode switching
|
||||
const handleModeChange = (newMode: "smart" | "manual") => {
|
||||
const handleModeChange = (newMode: 'smart' | 'manual' | 'mastery') => {
|
||||
if (formState.mode === newMode) {
|
||||
return; // No change needed
|
||||
return // No change needed
|
||||
}
|
||||
|
||||
if (newMode === "smart") {
|
||||
if (newMode === 'smart') {
|
||||
// Switching to Smart mode
|
||||
// Use current displayRules if available, otherwise default to earlyLearner
|
||||
const displayRules =
|
||||
formState.displayRules ?? defaultAdditionConfig.displayRules;
|
||||
const displayRules = formState.displayRules ?? defaultAdditionConfig.displayRules
|
||||
onChange({
|
||||
mode: "smart",
|
||||
mode: 'smart',
|
||||
displayRules,
|
||||
difficultyProfile: "earlyLearner",
|
||||
} as unknown as Partial<WorksheetFormState>);
|
||||
} else {
|
||||
difficultyProfile: 'earlyLearner',
|
||||
} as unknown as Partial<WorksheetFormState>)
|
||||
} else if (newMode === 'manual') {
|
||||
// Switching to Manual mode
|
||||
// Convert current displayRules to boolean flags if available
|
||||
let booleanFlags = {
|
||||
|
|
@ -49,32 +45,38 @@ export function ConfigPanel({
|
|||
showProblemNumbers: true,
|
||||
showCellBorder: true,
|
||||
showTenFramesForAll: false,
|
||||
};
|
||||
}
|
||||
|
||||
if (formState.displayRules) {
|
||||
// Convert 'always' to true, everything else to false
|
||||
booleanFlags = {
|
||||
showCarryBoxes: formState.displayRules.carryBoxes === "always",
|
||||
showAnswerBoxes: formState.displayRules.answerBoxes === "always",
|
||||
showPlaceValueColors:
|
||||
formState.displayRules.placeValueColors === "always",
|
||||
showTenFrames: formState.displayRules.tenFrames === "always",
|
||||
showProblemNumbers:
|
||||
formState.displayRules.problemNumbers === "always",
|
||||
showCellBorder: formState.displayRules.cellBorders === "always",
|
||||
showCarryBoxes: formState.displayRules.carryBoxes === 'always',
|
||||
showAnswerBoxes: formState.displayRules.answerBoxes === 'always',
|
||||
showPlaceValueColors: formState.displayRules.placeValueColors === 'always',
|
||||
showTenFrames: formState.displayRules.tenFrames === 'always',
|
||||
showProblemNumbers: formState.displayRules.problemNumbers === 'always',
|
||||
showCellBorder: formState.displayRules.cellBorders === 'always',
|
||||
showTenFramesForAll: false,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
onChange({
|
||||
mode: "manual",
|
||||
mode: 'manual',
|
||||
...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 (
|
||||
<div data-component="config-panel" className={stack({ gap: "3" })}>
|
||||
<div data-component="config-panel" className={stack({ gap: '3' })}>
|
||||
{/* Student Name */}
|
||||
<StudentNameInput
|
||||
value={formState.name}
|
||||
|
|
@ -98,7 +100,7 @@ export function ConfigPanel({
|
|||
|
||||
{/* Mode Selector */}
|
||||
<ModeSelector
|
||||
currentMode={formState.mode ?? "smart"}
|
||||
currentMode={formState.mode ?? 'smart'}
|
||||
onChange={handleModeChange}
|
||||
isDark={isDark}
|
||||
/>
|
||||
|
|
@ -111,22 +113,19 @@ export function ConfigPanel({
|
|||
/>
|
||||
|
||||
{/* Smart Mode Controls */}
|
||||
{(!formState.mode || formState.mode === "smart") && (
|
||||
<SmartModeControls
|
||||
formState={formState}
|
||||
onChange={onChange}
|
||||
isDark={isDark}
|
||||
/>
|
||||
{(!formState.mode || formState.mode === 'smart') && (
|
||||
<SmartModeControls formState={formState} onChange={onChange} isDark={isDark} />
|
||||
)}
|
||||
|
||||
{/* Manual Mode Controls */}
|
||||
{formState.mode === "manual" && (
|
||||
<ManualModeControls
|
||||
formState={formState}
|
||||
onChange={onChange}
|
||||
isDark={isDark}
|
||||
/>
|
||||
{formState.mode === 'manual' && (
|
||||
<ManualModeControls formState={formState} onChange={onChange} isDark={isDark} />
|
||||
)}
|
||||
|
||||
{/* Mastery Mode Controls */}
|
||||
{formState.mode === 'mastery' && (
|
||||
<ProgressionModePanel formState={formState} onChange={onChange} isDark={isDark} />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,42 +1,38 @@
|
|||
"use client";
|
||||
'use client'
|
||||
|
||||
import { css } from "../../../../../../styled-system/css";
|
||||
import { css } from '../../../../../../styled-system/css'
|
||||
|
||||
interface ModeSelectorProps {
|
||||
currentMode: "smart" | "manual";
|
||||
onChange: (mode: "smart" | "manual") => void;
|
||||
isDark?: boolean;
|
||||
currentMode: 'smart' | 'manual' | 'mastery'
|
||||
onChange: (mode: 'smart' | 'manual' | 'mastery') => void
|
||||
isDark?: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* 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({
|
||||
currentMode,
|
||||
onChange,
|
||||
isDark = false,
|
||||
}: ModeSelectorProps) {
|
||||
export function ModeSelector({ currentMode, onChange, isDark = false }: ModeSelectorProps) {
|
||||
return (
|
||||
<div
|
||||
data-component="mode-selector"
|
||||
className={css({
|
||||
marginBottom: "1.5rem",
|
||||
padding: "1rem",
|
||||
backgroundColor: isDark ? "gray.700" : "gray.50",
|
||||
borderRadius: "8px",
|
||||
border: "1px solid",
|
||||
borderColor: isDark ? "gray.600" : "gray.200",
|
||||
marginBottom: '1.5rem',
|
||||
padding: '1rem',
|
||||
backgroundColor: isDark ? 'gray.700' : 'gray.50',
|
||||
borderRadius: '8px',
|
||||
border: '1px solid',
|
||||
borderColor: isDark ? 'gray.600' : 'gray.200',
|
||||
})}
|
||||
>
|
||||
<h3
|
||||
className={css({
|
||||
fontSize: "0.875rem",
|
||||
fontWeight: "600",
|
||||
color: isDark ? "gray.200" : "gray.700",
|
||||
marginBottom: "0.75rem",
|
||||
textTransform: "uppercase",
|
||||
letterSpacing: "0.05em",
|
||||
fontSize: '0.875rem',
|
||||
fontWeight: '600',
|
||||
color: isDark ? 'gray.200' : 'gray.700',
|
||||
marginBottom: '0.75rem',
|
||||
textTransform: 'uppercase',
|
||||
letterSpacing: '0.05em',
|
||||
})}
|
||||
>
|
||||
Worksheet Mode
|
||||
|
|
@ -45,67 +41,52 @@ export function ModeSelector({
|
|||
<div
|
||||
data-element="mode-buttons"
|
||||
className={css({
|
||||
display: "flex",
|
||||
gap: "0.75rem",
|
||||
display: 'grid',
|
||||
gridTemplateColumns: 'repeat(3, 1fr)',
|
||||
gap: '0.75rem',
|
||||
})}
|
||||
>
|
||||
{/* Smart Difficulty Mode Button */}
|
||||
<button
|
||||
type="button"
|
||||
data-action="select-smart-mode"
|
||||
data-selected={currentMode === "smart"}
|
||||
onClick={() => onChange("smart")}
|
||||
data-selected={currentMode === 'smart'}
|
||||
onClick={() => onChange('smart')}
|
||||
className={css({
|
||||
flex: 1,
|
||||
padding: "1rem",
|
||||
borderRadius: "6px",
|
||||
border: "2px solid",
|
||||
borderColor:
|
||||
currentMode === "smart"
|
||||
? "blue.500"
|
||||
: isDark
|
||||
? "gray.500"
|
||||
: "gray.300",
|
||||
backgroundColor:
|
||||
currentMode === "smart"
|
||||
? "blue.50"
|
||||
: isDark
|
||||
? "gray.600"
|
||||
: "white",
|
||||
cursor: "pointer",
|
||||
transition: "all 0.2s",
|
||||
textAlign: "left",
|
||||
padding: '1rem',
|
||||
borderRadius: '6px',
|
||||
border: '2px solid',
|
||||
borderColor: currentMode === 'smart' ? 'blue.500' : isDark ? 'gray.500' : 'gray.300',
|
||||
backgroundColor: currentMode === 'smart' ? 'blue.50' : isDark ? 'gray.600' : 'white',
|
||||
cursor: 'pointer',
|
||||
transition: 'all 0.2s',
|
||||
textAlign: 'left',
|
||||
_hover: {
|
||||
borderColor: "blue.400",
|
||||
backgroundColor: "blue.50",
|
||||
borderColor: 'blue.400',
|
||||
backgroundColor: 'blue.50',
|
||||
},
|
||||
})}
|
||||
>
|
||||
<div
|
||||
className={css({
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: "0.5rem",
|
||||
marginBottom: "0.5rem",
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '0.5rem',
|
||||
marginBottom: '0.5rem',
|
||||
})}
|
||||
>
|
||||
<span
|
||||
className={css({
|
||||
fontSize: "1.25rem",
|
||||
fontSize: '1.25rem',
|
||||
})}
|
||||
>
|
||||
🎯
|
||||
</span>
|
||||
<span
|
||||
className={css({
|
||||
fontSize: "0.875rem",
|
||||
fontWeight: "600",
|
||||
color:
|
||||
currentMode === "smart"
|
||||
? "blue.700"
|
||||
: isDark
|
||||
? "gray.200"
|
||||
: "gray.700",
|
||||
fontSize: '0.875rem',
|
||||
fontWeight: '600',
|
||||
color: currentMode === 'smart' ? 'blue.700' : isDark ? 'gray.200' : 'gray.700',
|
||||
})}
|
||||
>
|
||||
Smart Difficulty
|
||||
|
|
@ -113,18 +94,12 @@ export function ModeSelector({
|
|||
</div>
|
||||
<p
|
||||
className={css({
|
||||
fontSize: "0.75rem",
|
||||
color:
|
||||
currentMode === "smart"
|
||||
? "blue.600"
|
||||
: isDark
|
||||
? "gray.400"
|
||||
: "gray.600",
|
||||
lineHeight: "1.4",
|
||||
fontSize: '0.75rem',
|
||||
color: currentMode === 'smart' ? 'blue.600' : isDark ? 'gray.400' : 'gray.600',
|
||||
lineHeight: '1.4',
|
||||
})}
|
||||
>
|
||||
Research-backed progressive difficulty with adaptive scaffolding per
|
||||
problem
|
||||
Research-backed progressive difficulty with adaptive scaffolding per problem
|
||||
</p>
|
||||
</button>
|
||||
|
||||
|
|
@ -132,59 +107,43 @@ export function ModeSelector({
|
|||
<button
|
||||
type="button"
|
||||
data-action="select-manual-mode"
|
||||
data-selected={currentMode === "manual"}
|
||||
onClick={() => onChange("manual")}
|
||||
data-selected={currentMode === 'manual'}
|
||||
onClick={() => onChange('manual')}
|
||||
className={css({
|
||||
flex: 1,
|
||||
padding: "1rem",
|
||||
borderRadius: "6px",
|
||||
border: "2px solid",
|
||||
borderColor:
|
||||
currentMode === "manual"
|
||||
? "blue.500"
|
||||
: isDark
|
||||
? "gray.500"
|
||||
: "gray.300",
|
||||
backgroundColor:
|
||||
currentMode === "manual"
|
||||
? "blue.50"
|
||||
: isDark
|
||||
? "gray.600"
|
||||
: "white",
|
||||
cursor: "pointer",
|
||||
transition: "all 0.2s",
|
||||
textAlign: "left",
|
||||
padding: '1rem',
|
||||
borderRadius: '6px',
|
||||
border: '2px solid',
|
||||
borderColor: currentMode === 'manual' ? 'blue.500' : isDark ? 'gray.500' : 'gray.300',
|
||||
backgroundColor: currentMode === 'manual' ? 'blue.50' : isDark ? 'gray.600' : 'white',
|
||||
cursor: 'pointer',
|
||||
transition: 'all 0.2s',
|
||||
textAlign: 'left',
|
||||
_hover: {
|
||||
borderColor: "blue.400",
|
||||
backgroundColor: "blue.50",
|
||||
borderColor: 'blue.400',
|
||||
backgroundColor: 'blue.50',
|
||||
},
|
||||
})}
|
||||
>
|
||||
<div
|
||||
className={css({
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: "0.5rem",
|
||||
marginBottom: "0.5rem",
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '0.5rem',
|
||||
marginBottom: '0.5rem',
|
||||
})}
|
||||
>
|
||||
<span
|
||||
className={css({
|
||||
fontSize: "1.25rem",
|
||||
fontSize: '1.25rem',
|
||||
})}
|
||||
>
|
||||
🎛️
|
||||
</span>
|
||||
<span
|
||||
className={css({
|
||||
fontSize: "0.875rem",
|
||||
fontWeight: "600",
|
||||
color:
|
||||
currentMode === "manual"
|
||||
? "blue.700"
|
||||
: isDark
|
||||
? "gray.200"
|
||||
: "gray.700",
|
||||
fontSize: '0.875rem',
|
||||
fontWeight: '600',
|
||||
color: currentMode === 'manual' ? 'blue.700' : isDark ? 'gray.200' : 'gray.700',
|
||||
})}
|
||||
>
|
||||
Manual Control
|
||||
|
|
@ -192,21 +151,72 @@ export function ModeSelector({
|
|||
</div>
|
||||
<p
|
||||
className={css({
|
||||
fontSize: "0.75rem",
|
||||
color:
|
||||
currentMode === "manual"
|
||||
? "blue.600"
|
||||
: isDark
|
||||
? "gray.400"
|
||||
: "gray.600",
|
||||
lineHeight: "1.4",
|
||||
fontSize: '0.75rem',
|
||||
color: currentMode === 'manual' ? 'blue.600' : isDark ? 'gray.400' : 'gray.600',
|
||||
lineHeight: '1.4',
|
||||
})}
|
||||
>
|
||||
Full control over display options with uniform scaffolding across
|
||||
all problems
|
||||
Full control over display options with uniform scaffolding across 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>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import type {
|
|||
AdditionConfigV4,
|
||||
AdditionConfigV4Smart,
|
||||
AdditionConfigV4Manual,
|
||||
} from "../config-schemas";
|
||||
} from '../config-schemas'
|
||||
|
||||
/**
|
||||
* Complete, validated configuration for worksheet generation
|
||||
|
|
@ -18,25 +18,25 @@ import type {
|
|||
*/
|
||||
export type WorksheetConfig = AdditionConfigV4 & {
|
||||
// Problem set - DERIVED state
|
||||
total: number; // total = problemsPerPage * pages
|
||||
rows: number; // rows = (problemsPerPage / cols) * pages
|
||||
total: number // total = problemsPerPage * pages
|
||||
rows: number // rows = (problemsPerPage / cols) * pages
|
||||
|
||||
// Personalization
|
||||
date: string;
|
||||
seed: number;
|
||||
date: string
|
||||
seed: number
|
||||
|
||||
// Layout
|
||||
page: {
|
||||
wIn: number;
|
||||
hIn: number;
|
||||
};
|
||||
wIn: number
|
||||
hIn: number
|
||||
}
|
||||
margins: {
|
||||
left: number;
|
||||
right: number;
|
||||
top: number;
|
||||
bottom: number;
|
||||
};
|
||||
};
|
||||
left: number
|
||||
right: number
|
||||
top: number
|
||||
bottom: number
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* both modes to exist temporarily. Validation will enforce mode consistency.
|
||||
*/
|
||||
export type WorksheetFormState = Partial<
|
||||
Omit<AdditionConfigV4Smart, "version">
|
||||
> &
|
||||
Partial<Omit<AdditionConfigV4Manual, "version">> & {
|
||||
export type WorksheetFormState = Partial<Omit<AdditionConfigV4Smart, 'version'>> &
|
||||
Partial<Omit<AdditionConfigV4Manual, 'version'>> & {
|
||||
// DERIVED state (calculated from primary state)
|
||||
rows?: number;
|
||||
total?: number;
|
||||
date?: string;
|
||||
seed?: number;
|
||||
};
|
||||
rows?: number
|
||||
total?: number
|
||||
date?: string
|
||||
seed?: number
|
||||
|
||||
// Mastery progression mode
|
||||
currentStepId?: string // Current step in progression path
|
||||
}
|
||||
|
||||
/**
|
||||
* Worksheet operator type
|
||||
*/
|
||||
export type WorksheetOperator = "addition" | "subtraction" | "mixed";
|
||||
export type WorksheetOperator = 'addition' | 'subtraction' | 'mixed'
|
||||
|
||||
/**
|
||||
* A single addition problem
|
||||
*/
|
||||
export interface AdditionProblem {
|
||||
a: number;
|
||||
b: number;
|
||||
operator: "+";
|
||||
a: number
|
||||
b: number
|
||||
operator: '+'
|
||||
}
|
||||
|
||||
/**
|
||||
* A single subtraction problem
|
||||
*/
|
||||
export interface SubtractionProblem {
|
||||
minuend: number;
|
||||
subtrahend: number;
|
||||
operator: "−"; // Proper minus sign (U+2212)
|
||||
minuend: number
|
||||
subtrahend: number
|
||||
operator: '−' // Proper minus sign (U+2212)
|
||||
}
|
||||
|
||||
/**
|
||||
* Unified problem type (addition or subtraction)
|
||||
*/
|
||||
export type WorksheetProblem = AdditionProblem | SubtractionProblem;
|
||||
export type WorksheetProblem = AdditionProblem | SubtractionProblem
|
||||
|
||||
/**
|
||||
* Validation result
|
||||
*/
|
||||
export interface ValidationResult {
|
||||
isValid: boolean;
|
||||
config?: WorksheetConfig;
|
||||
errors?: string[];
|
||||
isValid: boolean
|
||||
config?: WorksheetConfig
|
||||
errors?: string[]
|
||||
}
|
||||
|
||||
/**
|
||||
* Problem category for difficulty control
|
||||
*/
|
||||
export type ProblemCategory = "non" | "onesOnly" | "both";
|
||||
export type ProblemCategory = 'non' | 'onesOnly' | 'both'
|
||||
|
|
|
|||
Loading…
Reference in New Issue