feat: apply skill-specific scaffolding and fix mini card heights

**Issue 1: Too much scaffolding on mastery skills**
- Mastery skills have `recommendedScaffolding` defined but weren't being applied
- "Five-digit mastery" should have zero scaffolding (no carry boxes, no answer boxes)

**Issue 2: Variable card heights in mixed mode**
- Mini skill cards used `minHeight` which allowed them to grow
- Description text of varying lengths caused buttons to jump around

**Fix 1: Auto-apply recommendedScaffolding in mastery mode**
- validation.ts: Query skill by ID and apply its recommendedScaffolding
- Single operator: Use skill's exact scaffolding rules
- Mixed mode: Merge both skills, taking least scaffolding (most restrictive)
- 5d-mastery skills now correctly show:
  - carryBoxes: 'never'
  - answerBoxes: 'never'
  - placeValueColors: 'when3PlusDigits' (shown for 5-digit, hidden otherwise)
  - tenFrames: 'never'

**Fix 2: Enforce fixed height for mini cards**
- Changed `minHeight` → `height: '5.5rem'`
- Added `overflow: 'hidden'` to container
- Added `flexShrink: 0` to header and title
- Description truncates with ellipsis after 2 lines (`WebkitLineClamp: 2`)
- Buttons now stay perfectly aligned

Files changed:
- `validation.ts`: Apply recommendedScaffolding from skills in mastery mode
- `MasteryModePanel.tsx`: Fixed height (5.5rem) with 2-line description clamp

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Thomas Hallock 2025-11-10 14:18:05 -06:00
parent 4a5294353e
commit ee90182ff2
2 changed files with 97 additions and 5 deletions

View File

@ -287,9 +287,10 @@ export function MasteryModePanel({ formState, onChange, isDark = false }: Master
border: '1px solid',
borderColor: isDark ? 'gray.600' : 'gray.300',
backgroundColor: isDark ? 'gray.600' : 'white',
minHeight: '5.5rem',
height: '5.5rem',
display: 'flex',
flexDirection: 'column',
overflow: 'hidden',
})}
>
<div
@ -300,11 +301,19 @@ export function MasteryModePanel({ formState, onChange, isDark = false }: Master
marginBottom: '0.25rem',
textTransform: 'uppercase',
letterSpacing: '0.025em',
flexShrink: 0,
})}
>
Addition
</div>
<div className={css({ display: 'flex', alignItems: 'center', gap: '0.375rem' })}>
<div
className={css({
display: 'flex',
alignItems: 'center',
gap: '0.375rem',
flexShrink: 0,
})}
>
<h4
className={css({
fontSize: '0.8125rem',
@ -333,6 +342,11 @@ export function MasteryModePanel({ formState, onChange, isDark = false }: Master
color: isDark ? 'gray.400' : 'gray.600',
marginTop: '0.25rem',
lineHeight: '1.3',
overflow: 'hidden',
textOverflow: 'ellipsis',
display: '-webkit-box',
WebkitLineClamp: 2,
WebkitBoxOrient: 'vertical',
})}
>
{currentAdditionSkill.description}
@ -432,9 +446,10 @@ export function MasteryModePanel({ formState, onChange, isDark = false }: Master
border: '1px solid',
borderColor: isDark ? 'gray.600' : 'gray.300',
backgroundColor: isDark ? 'gray.600' : 'white',
minHeight: '5.5rem',
height: '5.5rem',
display: 'flex',
flexDirection: 'column',
overflow: 'hidden',
})}
>
<div
@ -445,11 +460,19 @@ export function MasteryModePanel({ formState, onChange, isDark = false }: Master
marginBottom: '0.25rem',
textTransform: 'uppercase',
letterSpacing: '0.025em',
flexShrink: 0,
})}
>
Subtraction
</div>
<div className={css({ display: 'flex', alignItems: 'center', gap: '0.375rem' })}>
<div
className={css({
display: 'flex',
alignItems: 'center',
gap: '0.375rem',
flexShrink: 0,
})}
>
<h4
className={css({
fontSize: '0.8125rem',
@ -478,6 +501,11 @@ export function MasteryModePanel({ formState, onChange, isDark = false }: Master
color: isDark ? 'gray.400' : 'gray.600',
marginTop: '0.25rem',
lineHeight: '1.3',
overflow: 'hidden',
textOverflow: 'ellipsis',
display: '-webkit-box',
WebkitLineClamp: 2,
WebkitBoxOrient: 'vertical',
})}
>
{currentSubtractionSkill.description}

View File

@ -2,6 +2,7 @@
import type { WorksheetFormState, WorksheetConfig, ValidationResult } from './types'
import type { DisplayRules } from './displayRules'
import { getSkillById } from './skills'
/**
* Get current date formatted as "Month Day, Year"
@ -150,7 +151,9 @@ export function validateWorksheetConfig(formState: WorksheetFormState): Validati
if (mode === 'smart' || mode === 'mastery') {
// Smart & Mastery modes: Use displayRules for conditional scaffolding
const displayRules: DisplayRules = {
// Default display rules
let baseDisplayRules: DisplayRules = {
carryBoxes: 'whenRegrouping',
answerBoxes: 'always',
placeValueColors: 'always',
@ -159,6 +162,67 @@ export function validateWorksheetConfig(formState: WorksheetFormState): Validati
cellBorders: 'always',
borrowNotation: 'whenRegrouping', // Subtraction: show when borrowing
borrowingHints: 'never', // Subtraction: no hints by default
}
// Mastery mode: Apply recommendedScaffolding from current skill(s)
if (mode === 'mastery') {
const operator = formState.operator ?? 'addition'
if (operator === 'mixed') {
// Mixed mode: Merge scaffolding from both skills
const addSkillId = formState.currentAdditionSkillId
const subSkillId = formState.currentSubtractionSkillId
if (addSkillId && subSkillId) {
const addSkill = getSkillById(addSkillId as any)
const subSkill = getSkillById(subSkillId as any)
if (addSkill?.recommendedScaffolding && subSkill?.recommendedScaffolding) {
// Use the LEAST scaffolding from both (most restrictive)
// This ensures mastery-level problems have minimal scaffolding
baseDisplayRules = {
// Take 'never' if either skill recommends it
carryBoxes:
addSkill.recommendedScaffolding.carryBoxes === 'never' ||
subSkill.recommendedScaffolding.carryBoxes === 'never'
? 'never'
: 'whenRegrouping',
answerBoxes:
addSkill.recommendedScaffolding.answerBoxes === 'never' ||
subSkill.recommendedScaffolding.answerBoxes === 'never'
? 'never'
: 'always',
placeValueColors:
addSkill.recommendedScaffolding.placeValueColors === 'never' ||
subSkill.recommendedScaffolding.placeValueColors === 'never'
? 'never'
: addSkill.recommendedScaffolding.placeValueColors,
tenFrames: 'never', // Always off for mastery
problemNumbers: 'always',
cellBorders: 'always',
borrowNotation: subSkill.recommendedScaffolding.borrowNotation,
borrowingHints: 'never',
}
}
}
} else {
// Single operator: Use its recommendedScaffolding
const skillId =
operator === 'addition'
? formState.currentAdditionSkillId
: formState.currentSubtractionSkillId
if (skillId) {
const skill = getSkillById(skillId as any)
if (skill?.recommendedScaffolding) {
baseDisplayRules = { ...skill.recommendedScaffolding }
}
}
}
}
const displayRules: DisplayRules = {
...baseDisplayRules,
...((formState.displayRules as any) ?? {}), // Override with provided rules if any
}