feat: enhance tooltips with combined provenance and pedagogical content
Implement sophisticated tooltip system that shows both source digit context and pedagogical reasoning for different operation types. Key improvements: - Direct operations: Enhanced provenance titles like "Add the tens digit — 2 tens (20)" - Complement operations: Combined provenance context + pedagogical why explanations - Enhanced chips with source digit information for all operation types - Fixed context integration to remove prop drilling (steps now from tutorial context) - Preserved mathematical accuracy while improving pedagogical clarity For five-complement (3 + 4 = 7), tooltips now show: - Source context: "From ones digit 4 of 4" - Pedagogical reasoning: "Adding 4 would need more lower beads than we have" - Strategic explanation: "Use the heaven bead instead: press it and lift some lower beads" This addresses the original problem where students couldn't understand that "20" came from the "2" in "25", while maintaining rich educational content for complement strategies. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -57,7 +57,7 @@ interface DecompositionWithReasonsProps {
|
||||
termPositions: Array<{ startIndex: number; endIndex: number }>
|
||||
segments?: PedagogicalSegment[]
|
||||
termReasons?: TermReason[]
|
||||
steps?: UnifiedStepData[]
|
||||
// NOTE: steps now comes from tutorial context, not props
|
||||
}
|
||||
|
||||
interface TermSpanProps {
|
||||
@@ -102,18 +102,27 @@ interface SegmentGroupProps {
|
||||
fullDecomposition: string
|
||||
termPositions: Array<{ startIndex: number; endIndex: number }>
|
||||
termReasons?: TermReason[]
|
||||
steps: UnifiedStepData[]
|
||||
children: React.ReactNode
|
||||
}
|
||||
|
||||
function SegmentGroup({ segment, fullDecomposition, steps, children }: SegmentGroupProps) {
|
||||
function SegmentGroup({ segment, fullDecomposition, children }: SegmentGroupProps) {
|
||||
const [tooltipOpen, setTooltipOpen] = useState(false)
|
||||
const { addActiveTerm, removeActiveTerm } = useContext(DecompositionContext)
|
||||
|
||||
// Get steps from tutorial context instead of props
|
||||
const { unifiedSteps: steps } = useTutorialContext()
|
||||
|
||||
// Calculate the original term that was expanded
|
||||
// digit * 10^place gives us the original value (e.g., digit=5, place=1 -> 50)
|
||||
const originalValue = (segment.digit * Math.pow(10, segment.place)).toString()
|
||||
|
||||
// Get provenance from the first step in this segment
|
||||
const firstStepIndex = segment.termIndices[0]
|
||||
const firstStep = steps[firstStepIndex]
|
||||
const provenance = firstStep?.provenance
|
||||
|
||||
|
||||
|
||||
const handleTooltipChange = (open: boolean) => {
|
||||
setTooltipOpen(open)
|
||||
// Activate/deactivate all terms in this segment
|
||||
@@ -132,6 +141,7 @@ function SegmentGroup({ segment, fullDecomposition, steps, children }: SegmentGr
|
||||
steps={steps}
|
||||
open={tooltipOpen}
|
||||
onOpenChange={handleTooltipChange}
|
||||
provenance={provenance} // NEW: Pass provenance data
|
||||
>
|
||||
<span
|
||||
className="segment-group"
|
||||
@@ -150,8 +160,7 @@ export function DecompositionWithReasons({
|
||||
fullDecomposition,
|
||||
termPositions,
|
||||
segments,
|
||||
termReasons,
|
||||
steps = []
|
||||
termReasons
|
||||
}: DecompositionWithReasonsProps) {
|
||||
const [activeTerms, setActiveTerms] = useState<Set<number>>(new Set())
|
||||
|
||||
@@ -269,7 +278,6 @@ export function DecompositionWithReasons({
|
||||
fullDecomposition={fullDecomposition}
|
||||
termPositions={termPositions}
|
||||
termReasons={termReasons}
|
||||
steps={steps}
|
||||
>
|
||||
{segmentElements}
|
||||
</SegmentGroup>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import React, { useState } from 'react'
|
||||
import * as Tooltip from '@radix-ui/react-tooltip'
|
||||
import type { PedagogicalRule, PedagogicalSegment, TermReason } from './DecompositionWithReasons'
|
||||
import type { UnifiedStepData } from '../../utils/unifiedStepGenerator'
|
||||
import type { UnifiedStepData, TermProvenance } from '../../utils/unifiedStepGenerator'
|
||||
|
||||
interface ReasonTooltipProps {
|
||||
children: React.ReactNode
|
||||
@@ -14,6 +14,7 @@ interface ReasonTooltipProps {
|
||||
steps?: UnifiedStepData[]
|
||||
open?: boolean
|
||||
onOpenChange?: (open: boolean) => void
|
||||
provenance?: TermProvenance // NEW: Provenance data for enhanced tooltips
|
||||
}
|
||||
|
||||
// Fallback utility for legacy support
|
||||
@@ -30,7 +31,8 @@ export function ReasonTooltip({
|
||||
originalValue,
|
||||
steps,
|
||||
open,
|
||||
onOpenChange
|
||||
onOpenChange,
|
||||
provenance
|
||||
}: ReasonTooltipProps) {
|
||||
const [showBeadDetails, setShowBeadDetails] = useState(false)
|
||||
const [showMath, setShowMath] = useState(false)
|
||||
@@ -42,9 +44,51 @@ export function ReasonTooltip({
|
||||
return <>{children}</>
|
||||
}
|
||||
|
||||
// Use readable format from segment
|
||||
// Use readable format from segment, enhanced with provenance
|
||||
const readable = segment?.readable
|
||||
|
||||
// Generate enhanced tooltip content using provenance
|
||||
const getEnhancedTooltipContent = () => {
|
||||
if (!provenance) return null
|
||||
|
||||
// For Direct operations, use the enhanced provenance title
|
||||
if (rule === 'Direct') {
|
||||
const title = `Add the ${provenance.rhsPlaceName} digit — ${provenance.rhsDigit} ${provenance.rhsPlaceName} (${provenance.rhsValue})`
|
||||
const subtitle = `From addend ${provenance.rhs}`
|
||||
|
||||
const enhancedChips = [
|
||||
{ label: 'Digit we\'re using', value: `${provenance.rhsDigit} (${provenance.rhsPlaceName})` },
|
||||
...(readable?.chips.find(chip => chip.label === 'This rod shows') ? [
|
||||
{ label: 'This rod shows', value: readable.chips.find(chip => chip.label === 'This rod shows')!.value }
|
||||
] : []),
|
||||
{ label: 'So we add here', value: `+${provenance.rhsDigit} ${provenance.rhsPlaceName} → ${provenance.rhsValue}` }
|
||||
]
|
||||
|
||||
return { title, subtitle, chips: enhancedChips }
|
||||
}
|
||||
|
||||
// For complement operations, enhance the existing readable content with provenance context
|
||||
if (readable) {
|
||||
// Keep the readable title but add provenance context to subtitle
|
||||
const title = readable.title
|
||||
const subtitle = `${readable.subtitle || ''} • From ${provenance.rhsPlaceName} digit ${provenance.rhsDigit} of ${provenance.rhs}`.trim()
|
||||
|
||||
// Enhance the chips by adding provenance context at the beginning
|
||||
const enhancedChips = [
|
||||
{ label: 'Source digit', value: `${provenance.rhsDigit} from ${provenance.rhs} (${provenance.rhsPlaceName} place)` },
|
||||
...readable.chips
|
||||
]
|
||||
|
||||
return { title, subtitle, chips: enhancedChips }
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
const enhancedContent = getEnhancedTooltipContent()
|
||||
|
||||
|
||||
|
||||
const getRuleInfo = (rule: PedagogicalRule) => {
|
||||
switch (rule) {
|
||||
case 'Direct':
|
||||
@@ -126,18 +170,20 @@ export function ReasonTooltip({
|
||||
<div className="reason-tooltip__header">
|
||||
<span className="reason-tooltip__emoji">{ruleInfo.emoji}</span>
|
||||
<div className="reason-tooltip__title">
|
||||
<h4 className="reason-tooltip__name">{readable?.title || ruleInfo.name}</h4>
|
||||
<h4 className="reason-tooltip__name">
|
||||
{enhancedContent?.title || readable?.title || ruleInfo.name}
|
||||
</h4>
|
||||
<p id={`${tooltipId}-description`} className="reason-tooltip__description">
|
||||
{readable?.subtitle || ruleInfo.description}
|
||||
{enhancedContent?.subtitle || readable?.subtitle || ruleInfo.description}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Context chips using readable format */}
|
||||
{readable && readable.chips.length > 0 && (
|
||||
{/* Context chips using enhanced or readable format */}
|
||||
{(enhancedContent?.chips || readable?.chips) && (
|
||||
<div className="reason-tooltip__context">
|
||||
<div className="reason-tooltip__chips">
|
||||
{readable.chips.map((chip, index) => (
|
||||
{(enhancedContent?.chips || readable?.chips || []).map((chip, index) => (
|
||||
<span key={index} className="reason-tooltip__chip">
|
||||
{chip.label}: {chip.value}
|
||||
</span>
|
||||
@@ -146,15 +192,35 @@ export function ReasonTooltip({
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Why this step using readable format */}
|
||||
{readable && readable.why.length > 0 && (
|
||||
{/* Why this step using enhanced provenance or readable format */}
|
||||
{(provenance || (readable && readable.why.length > 0)) && (
|
||||
<div className="reason-tooltip__reasoning">
|
||||
<h5 className="reason-tooltip__section-title">Why this step</h5>
|
||||
{readable.why.map((why, index) => (
|
||||
{/* Show provenance explanation for Direct rules */}
|
||||
{provenance && rule === 'Direct' && (
|
||||
<>
|
||||
<p className="reason-tooltip__explanation-text">
|
||||
• We're adding the <strong>{provenance.rhsPlaceName} digit</strong> of <strong>{provenance.rhs}</strong> → <strong>{provenance.rhsDigit} {provenance.rhsPlaceName}</strong>.
|
||||
</p>
|
||||
{readable?.chips.find(chip => chip.label === 'This rod shows') && (
|
||||
<p className="reason-tooltip__explanation-text">
|
||||
• {readable.chips.find(chip => chip.label === 'This rod shows')?.label} <strong>{readable.chips.find(chip => chip.label === 'This rod shows')?.value}</strong>; adding <strong>{provenance.rhsDigit}</strong> fits, so no carry.
|
||||
</p>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{/* Show readable why explanations for complement rules (and optionally Direct if available) */}
|
||||
{readable && readable.why.length > 0 && readable.why.map((why, index) => (
|
||||
<p key={index} className="reason-tooltip__explanation-text">
|
||||
• {why}
|
||||
</p>
|
||||
))}
|
||||
{/* For complement rules with provenance, add additional context about source digit */}
|
||||
{provenance && rule !== 'Direct' && (
|
||||
<p className="reason-tooltip__explanation-text">
|
||||
• This expansion processes the <strong>{provenance.rhsPlaceName} digit {provenance.rhsDigit}</strong> from the addend <strong>{provenance.rhs}</strong>.
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user