This guide covers the most common failure modes in AlgoFlow and how to fix them quickly. Each section maps a symptom to a root cause and provides a concrete debug pattern you can run immediately.
Prerequisites: Read Architecture (data flow, core pattern) and Glossary (key terms) first. The sections below assume familiarity with the registry, tracker abstraction, and
ExecutionStepshape.
- Quick Diagnosis Table
- Step Generation Failures
- Line Mapping Issues
- Visualizer Rendering Problems
- ?fn Import Pipeline
- Registry & Self-Registration
- Tree-Specific Debugging
- E2E Test Failures
- See Also
| Symptom | Likely Cause | Section |
|---|---|---|
| Algorithm not in UI | Missing import in src/algorithms/index.ts |
Registry & Self-Registration |
| Wrong line highlighted | @step: key mismatch between source and tracker |
Line Mapping Issues |
| Blank visualizer | VisualState.kind doesn't match any visualizer |
Visualizer Rendering Problems |
| Step count is 0 | Tracker not calling pushStep() |
Step Generation Failures |
?fn import error |
File not in sources/ directory or not .ts |
?fn Import Pipeline |
| E2E test timeout | webServer failed to start or wrong port |
E2E Test Failures |
generateSteps() returns an empty array (or fewer steps than expected) when the tracker's pushStep() is never called, called with wrong arguments, or when the algorithm logic exits early.
Common causes:
- Tracker method is never reached — the algorithm logic has a guard clause or early return that prevents execution
complete()is not called at the end — the final step (marking the algorithm as done) is missing- Wrong tracker class for the category — for example, using
SortingTrackerinside a graph algorithm
Debug pattern: In a unit test, call generateSteps() with a known minimal input and log the step count and types:
const steps = generateSteps(knownInput);
console.log(steps.length);
console.log(steps.map((step) => step.type));If the array is empty, add a console.log immediately before the first pushStep() call in the tracker to confirm execution reaches that point.
Lines in the code panel are highlighted based on a LineMap built from @step: marker comments in each source file. A mismatch between the marker key and the step type causes no lines (or the wrong lines) to highlight.
Common causes:
- The step key in a source file (
// @step:compare) doesn't match thetypeorlineMapKeypassed to the tracker call — even a small typo (e.g.,comparee) silently produces no highlight - Language files use different step keys — keys must be identical across TypeScript, Python, Java, Rust, C++, and Go source files
- A
@step:marker is missing entirely for a step type — that step will produce no highlighted lines
Debug pattern: Call buildLineMapFromSources(algorithmId) in a test and inspect the output to verify all expected keys are present with correct line numbers for every language:
const lineMap = buildLineMapFromSources("bubble-sort");
console.log(JSON.stringify(lineMap, null, 2));Cross-reference each key in the map against the step type values produced by generateSteps().
The visualization panel renders nothing (blank panel) when the VisualState.kind produced by the tracker does not match any registered visualizer.
Valid kind values — see the full VisualState discriminated union in Glossary — VisualState. Common kinds: array, graph, grid, dp-table, tree, linked-list, heap, stack-queue, hash-map, string, matrix, set.
Common causes:
- Returning a
kindthat is misspelled or not in the union (TypeScript strict mode should catch this at compile time, but a cast can bypass it) - Copying a tracker from a different category and forgetting to change the
kindfield - The visualizer switch/dispatch has not been updated to handle a newly added
kind
Debug pattern: Log the visualState.kind of each step:
const steps = generateSteps(knownInput);
steps.forEach((step) => console.log(step.visualState.kind));The ?fn query suffix is handled by the custom Vite plugin (vite-plugin-fn-import.ts). It allows algorithm source files to be imported as executable JavaScript functions while stripping visualization markers at build time.
How it works:
- Intercepts any import ending in
?fn - Reads the
.tssource file from disk - Strips all
// @step:markers via regex so the output is clean code - Transpiles TypeScript to JavaScript via OxC
- Auto-exports all top-level
functiondeclarations
Key constraints:
- Only works for
.tsfiles —.pyand.javafiles are not transpilable by this plugin - The file must live inside a
sources/directory within the algorithm directory - If the file cannot be read (wrong path, wrong extension), the plugin silently returns
null, which manifests as a module-not-found error at runtime
Debug pattern: If a ?fn import fails, verify:
- The imported path points to a
.tsfile (not.js,.py, or.java) - That file lives under a
sources/subdirectory of the algorithm - The file contains at least one top-level
functiondeclaration (arrow functions assigned toconstare not auto-exported)
Each algorithm registers itself by calling registry.register(definition) when its index.ts module is imported. If the module is never imported, the algorithm silently never appears in the UI.
How it works:
src/algorithms/index.tsimports each algorithm'sindex.ts(barrel imports)- The import triggers module evaluation, which calls
registry.register(definition) - The registry stores the definition and makes it available to all UI components
Common issues:
- Missing import in
src/algorithms/index.ts— most common cause of an algorithm not appearing in the dropdown; add the import to fix it - Duplicate
meta.id—registry.register()throws if an algorithm with the same ID is already registered; check for copy-paste errors when adding a new algorithm - Wrong category string — the algorithm is registered but appears under the wrong dropdown section
Debug pattern: Check src/algorithms/index.ts for the import. Then confirm the register call exists in the algorithm's own index.ts:
grep -r "registry.register" src/algorithms/Verify the meta.id is unique across all algorithm definitions.
Tree algorithms use the tree VisualState kind and have several fields that require extra attention:
Some tree algorithms (e.g., LCA, symmetric tree check) operate on two trees simultaneously. These algorithms populate a secondaryTree field alongside the primary nodes and edges arrays. If the secondary tree renders blank, verify that secondaryTree is set on every step — omitting it on even one step causes the secondary panel to disappear mid-playback.
Each TreeNode carries a childrenIds array listing the IDs of its children. Binary tree algorithms always produce exactly 0, 1, or 2 entries. N-ary algorithms (tries, segment trees, expression trees) may produce many. If a child node renders disconnected from its parent, check that the parent's childrenIds includes the child's ID and that the child appears in the nodes array.
Debug pattern:
const steps = generateSteps(knownInput);
const step = steps[0];
if (step.visualState.kind === "tree") {
step.visualState.nodes.forEach((node) => {
console.log(node.id, "->", node.childrenIds);
});
}Segment tree algorithms expose a queryRange field [left, right] on the tree VisualState to indicate the active query interval. If highlighted nodes do not match the expected range, confirm that queryRange is updated correctly each time the active segment changes.
The E2E suite uses @playwright/test. Spec files live in e2e/specs/, config at e2e/playwright.config.ts. The webServer block auto-starts Vite on port 5174 so no manual dev server is needed.
How the suite runs:
npm run e2e— headless mode (CI / automated)npm run e2e:headed— opens a visible browser for interactive debuggingnpm run e2e:debug— opens the Playwright inspector for step-through debugging- The
session-end-e2e-check.shStop hook runs the suite automatically whenever.tsx,.css,.html, ore2e/specs/files are modified
Common failures and fixes:
| Failure | Cause | Fix |
|---|---|---|
| Element not found / selector error | A component's DOM structure or ARIA label changed | Update the selector in the relevant spec file or shared helper in e2e/helpers/ to match the new structure |
| Timeout waiting for element | Slow step generation or animation blocking the assertion | Increase the wait timeout for that assertion, or check that generateSteps() completes without hanging |
| Console error detected | Runtime JS error on the page (the suite monitors for errors, filtering ResizeObserver and favicon noise) | Open the browser console with npm run e2e:headed and fix the underlying error |
| New algorithm not tested | Algorithm added to registry but category spec file doesn't pick it up | Per-category spec files auto-discover from the registry — verify src/algorithms/index.ts imports the algorithm |
| Wrong port / connection refused | webServer config failed to start Vite |
Check port 5174 is free; re-run npm run e2e — webServer handles startup automatically |
Debug tips:
- Run
npm run e2e:headedto watch every step execute in a real browser - Run
npm run e2e:debugto pause and inspect the DOM at any assertion - Confirm Node version is 22:
node --version - Clear Playwright cache if selectors behave unexpectedly:
npx playwright install chromium
For additional common pitfalls, see Common Pitfalls & Troubleshooting.
- Contributing — branch workflow, algorithm walkthrough, common pitfalls & troubleshooting
- Architecture — core registry-driven pattern, tracker abstraction, data flow
- Glossary — definitions for
ExecutionStep,VisualState,LineMap, tracker, and other key terms

