8.8 KiB
Flowchart Walker Modification Skill
This document captures patterns and lessons learned from modifying the flowchart walker system, specifically from the fraction addition/subtraction sprint where we:
- Split a single "calculate" step into separate numerator, denominator, and whole number checkpoints
- Added conditional skipping for optional steps
- Fixed grid dimension stability issues
Architecture Overview
Each flowchart has two parts that must stay in sync:
| Part | Location | Purpose |
|---|---|---|
| JSON definition | definitions/*.flow.json |
Node types, validation logic, variables, constraints |
| Mermaid content | definitions/index.ts (embedded) or *.mmd |
Visual presentation, node text, phases |
Critical: Many flowcharts embed Mermaid content in definitions/index.ts as constants (e.g., FRACTION_MERMAID, LINEAR_EQUATIONS_MERMAID). Always check there first before looking for .mmd files.
Adding a New Checkpoint Node
Step 1: Add Variables (if needed)
In the JSON definition's variables section, add computed values the checkpoint will use:
"variables": {
"existingVar": { "init": "someExpression" },
"newVar": { "init": "leftNum + rightNum" },
"conditionalVar": { "init": "condition ? valueIfTrue : valueIfFalse" }
}
Expression syntax: Uses JavaScript-like expressions with access to:
- Problem input fields (e.g.,
leftNum,rightDenom,op) - Other computed variables
- Built-in functions:
gcd(),lcm(),abs(),floor(),ceil(),min(),max()
Step 2: Add the Checkpoint Node
In the JSON definition's nodes section:
"NEW_CHECKPOINT": {
"type": "checkpoint",
"prompt": "What's the answer?",
"inputType": "number",
"expected": "variableName",
"workingProblemUpdate": {
"result": "expression for new working problem display",
"label": "Description of what changed"
}
}
Checkpoint input types:
"number"- Single numeric input"text"- Text input"two-numbers"- Two inputs withinputLabels: ["First", "Second"]andorderMatters: boolean
Step 3: Update Edges
In the JSON definition's edges section, wire up the new node:
"edges": {
"PREVIOUS_NODE": ["NEW_CHECKPOINT"],
"NEW_CHECKPOINT": ["NEXT_NODE"]
}
Step 4: Add Mermaid Content
Find where the Mermaid content lives (usually definitions/index.ts) and add the node:
NEW_CHECKPOINT["<b>🔢 CHECKPOINT TITLE</b>
───────────
Instructions for the user
go here"]
Mermaid node format:
[" ... "]for rounded rectangle (checkpoint/instruction){" ... "}for diamond (decision)- First line in
<b>...</b>becomes the title - Subsequent lines become body text
Step 5: Add to Phase (if applicable)
In the Mermaid content, ensure the node is in the correct subgraph:
subgraph phase3["3. 🎯 DO THE MATH!"]
EXISTING_NODE
NEW_CHECKPOINT
end
Adding Conditional Skip (skipIf)
For checkpoints that should be skipped under certain conditions:
"OPTIONAL_CHECKPOINT": {
"type": "checkpoint",
"prompt": "What's the whole number?",
"inputType": "number",
"expected": "resultWhole",
"skipIf": "!hasWholeNumbers",
"skipTo": "NEXT_NODE_WHEN_SKIPPED",
"excludeSkipFromPaths": true
}
The excludeSkipFromPaths Flag
Critical for grid stability: By default, a checkpoint with skipIf creates TWO paths in path enumeration (skip path and non-skip path), which affects example grid dimensions.
| Flag Value | Behavior | Use When |
|---|---|---|
false (default) |
skipIf creates branching paths | The skip represents a structural difference in problem types |
true |
skipIf is runtime-only, no path branching | The skip is an optional step that doesn't define problem categories |
Example: CALC_WHOLE uses excludeSkipFromPaths: true because whether a problem has whole numbers is incidental - it doesn't define a fundamentally different problem type for the grid.
Understanding Path Enumeration and Grid Dimensions
The example picker grid dimensions come from decision nodes with pathLabel options:
"DECISION_NODE": {
"type": "decision",
"correctAnswer": "someCondition",
"options": [
{
"label": "YES ✓",
"value": "yes",
"next": "PATH_A",
"pathLabel": "Same", // Used for path concatenation
"gridLabel": "Same denominators" // Used for grid headers
},
{
"label": "NO",
"value": "no",
"next": "PATH_B",
"pathLabel": "Diff",
"gridLabel": "Different denominators"
}
]
}
How grid dimensions are determined:
enumerateAllPaths()walks all possible paths through the flowchart- Each path collects
pathLabelvalues from decision nodes - Unique combinations of decisions define grid rows/columns
- The
gridLabelvalues become human-readable headers
What affects grid stability:
- Decision nodes with
pathLabeloptions → YES, defines grid structure - Checkpoint nodes (normal) → NO, just increments step count
- Checkpoint nodes with
skipIfandexcludeSkipFromPaths: false→ YES, creates path branches - Checkpoint nodes with
skipIfandexcludeSkipFromPaths: true→ NO, runtime only
Common Patterns
Pattern: Breaking One Step into Multiple Inputs
Before: Single "I did it" instruction node After: Multiple checkpoint nodes for each input
// Before
"CALCULATE": {
"type": "instruction",
"advance": "tap"
}
// After
"CALC_NUMERATOR": {
"type": "checkpoint",
"prompt": "What's the numerator?",
"inputType": "number",
"expected": "rawResultNum"
},
"CALC_DENOMINATOR": {
"type": "checkpoint",
"prompt": "What's the denominator?",
"inputType": "number",
"expected": "lcd",
"workingProblemUpdate": {
"result": "rawResultNum + '/' + lcd",
"label": "Calculated fraction"
}
}
Pattern: Optional Step Based on Problem Values
"OPTIONAL_STEP": {
"type": "checkpoint",
"prompt": "...",
"inputType": "number",
"expected": "someVar",
"skipIf": "!conditionForShowing",
"skipTo": "NEXT_AFTER_SKIP",
"excludeSkipFromPaths": true // Don't affect grid dimensions
}
Pattern: Working Problem Evolution
Track how the problem transforms through the solution:
"workingProblem": {
"initial": "(leftWhole > 0 ? (leftWhole + ' ') : '') + leftNum + '/' + leftDenom + ' ' + op + ' ' + ..."
}
Then update it at key checkpoints:
"workingProblemUpdate": {
"result": "newExpression",
"label": "What operation was performed"
}
Debugging Tips
Grid Dimensions Unstable
- Check if any checkpoint has
skipIfwithoutexcludeSkipFromPaths: true - Increase example generation count (in
generateExamplesForStructure, currently 1050) - Verify decision nodes have consistent
pathLabelvalues
Checkpoint Not Appearing
- Check the
skipIfcondition - is it evaluating correctly? - Verify edges connect to the node
- Check Mermaid content has the node defined
Wrong Expected Value
- Check variable initialization expressions
- Use the DEBUG panel to see computed values
- Test with known problem values
Files Reference
| File | Purpose |
|---|---|
src/lib/flowcharts/schema.ts |
TypeScript types for all node types |
src/lib/flowcharts/loader.ts |
Merges JSON + Mermaid, path enumeration |
src/lib/flowcharts/evaluator.ts |
Expression evaluation engine |
src/lib/flowcharts/definitions/index.ts |
Registry + embedded Mermaid content |
src/lib/flowcharts/definitions/*.flow.json |
JSON behavior definitions |
src/components/flowchart/FlowchartWalker.tsx |
Main UI component |
Checklist for Flowchart Modifications
- Added necessary variables in JSON
variablessection - Added node definition in JSON
nodessection - Updated JSON
edgesto wire up the node - Added Mermaid content for the node (check
index.tsfirst!) - Added node to correct phase subgraph in Mermaid
- If using
skipIf, decided onexcludeSkipFromPathsvalue - Tested with problems that exercise the new path
- Verified grid dimensions are stable (roll dice multiple times)
- Run
npm run pre-committo verify no type errors