soroban-abacus-flashcards/apps/web/.claude/procedures/FLOWCHART_MODIFICATIONS.md

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 with inputLabels: ["First", "Second"] and orderMatters: 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:

  1. enumerateAllPaths() walks all possible paths through the flowchart
  2. Each path collects pathLabel values from decision nodes
  3. Unique combinations of decisions define grid rows/columns
  4. The gridLabel values become human-readable headers

What affects grid stability:

  • Decision nodes with pathLabel options → YES, defines grid structure
  • Checkpoint nodes (normal) → NO, just increments step count
  • Checkpoint nodes with skipIf and excludeSkipFromPaths: false → YES, creates path branches
  • Checkpoint nodes with skipIf and excludeSkipFromPaths: 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

  1. Check if any checkpoint has skipIf without excludeSkipFromPaths: true
  2. Increase example generation count (in generateExamplesForStructure, currently 1050)
  3. Verify decision nodes have consistent pathLabel values

Checkpoint Not Appearing

  1. Check the skipIf condition - is it evaluating correctly?
  2. Verify edges connect to the node
  3. Check Mermaid content has the node defined

Wrong Expected Value

  1. Check variable initialization expressions
  2. Use the DEBUG panel to see computed values
  3. 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 variables section
  • Added node definition in JSON nodes section
  • Updated JSON edges to wire up the node
  • Added Mermaid content for the node (check index.ts first!)
  • Added node to correct phase subgraph in Mermaid
  • If using skipIf, decided on excludeSkipFromPaths value
  • Tested with problems that exercise the new path
  • Verified grid dimensions are stable (roll dice multiple times)
  • Run npm run pre-commit to verify no type errors