An interactive, clean-room TypeScript web demo that makes a small spreadsheet-like calculation inspectable: edit inputs, evaluate formula dependencies, explore the graph, trace a selected result, and import or export the workbook as JSON.
Clean-room portfolio demonstration. Everything in this repository is independently implemented with synthetic, generic data. It contains no proprietary code, company data, customer data, domain rules, or formulas from any private project.
Requires Node.js 22+.
npm install
npm run devOpen the local URL printed by Vite (normally http://localhost:5173).
For checks:
npm run typecheck
npm test
npm run build- Edit the synthetic numeric inputs (
length,width, andunit_cost), then select Evaluate to normalize and recalculate the workbook. - Add or edit
formulaandresultnodes with a small, validated form. - Inspect the SVG dependency graph; selecting a node updates the provenance trace and canonical JSON panel.
- Switch between three synthetic presets: healthy calculation, missing dependency, and circular dependency.
- Import a local JSON workbook, export the current normalized workbook, copy JSON, or reset to the healthy demo.
- Adapt a generic node-export JSON shape with numeric ids,
name,type/isFormula,description, and spreadsheet-style cell references into a focused dependency graph. - See clear per-node errors for invalid inputs, invalid formulas, missing references, and circular dependencies.
Editable inputs / formula editor / JSON import
|
v
normalizeWorkbook (canonical JSON)
|
v
buildDependencyGraph -> cycle detection -> topological evaluation
|
v
results, SVG dependency graph, selected-node provenance trace
| Module | Responsibility |
|---|---|
src/workbook.ts |
Normalizes JSON-shaped input, validates ids/value types, and serializes/deserializes workbooks. |
src/formula.ts |
Tokenizes and safely evaluates the small arithmetic language without eval. |
src/graph.ts |
Builds graph directions, detects cycles, orders nodes, and returns values/errors. |
src/trace.ts |
Produces a readable depth-first source chain for any selected node. |
src/demo-presets.ts |
Creates fresh synthetic healthy, missing-reference, and circular examples. |
src/main.ts |
Owns explicit browser state and renders the interactive workspace. |
The UI deliberately has no graphing dependency: SVG blocks and connectors are rendered directly from buildDependencyGraph, which keeps the example easy to review in a portfolio.
The primary import format is the portfolio JSON schema below. For a common generic node-export shape, the importer also adapts numeric node ids to text ids, reads formula text after a Formula: marker in node metadata, and resolves dependencies from either named references or spreadsheet-style cell references such as $B$2 and Sheet!B2.
This compatibility path is graph-first: formulas outside the documented arithmetic subset remain visible in the graph and trace, but are reported as non-evaluable rather than being assigned an invented numeric result. Imported source data stays local; it is never added to this repository.
There are three generic node kinds:
| Kind | Required fields | Purpose |
|---|---|---|
input |
id, kind, value |
Numeric source value. |
formula |
id, kind, formula |
Intermediate calculation. |
result |
id, kind, formula |
Calculation presented as an outcome. |
{
"nodes": [
{ "id": "length", "kind": "input", "label": "Length", "value": 12 },
{ "id": "width", "kind": "input", "label": "Width", "value": 10 },
{ "id": "total_area", "kind": "formula", "label": "Total Area", "formula": "=length * width" },
{ "id": "unit_cost", "kind": "input", "label": "Unit Cost", "value": 50 },
{ "id": "estimated_cost", "kind": "result", "label": "Estimated Cost", "formula": "=total_area * unit_cost" }
]
}Normalization derives each formula node's dependencies from its formula. The healthy demo evaluates to estimated_cost = 6000 and its trace is:
estimated_cost = total_area * unit_cost
<- total_area = length * width
<- length = 12
<- width = 10
<- unit_cost = 50
The Missing dependency preset contains:
=base_rate * missing_multiplier
It yields Missing dependency "missing_multiplier". on that result node. The Circular dependency preset has first_step -> second_step -> first_step; the evaluator reports the cycle and prevents dependent results from being calculated.
The intentionally small formula language supports numeric literals, node references, +, -, *, /, unary signs, and parentheses. Node ids can be any non-empty text value (for example a UUID). References that are ordinary identifiers can be written directly (=length * width); ids with punctuation must be bracketed (=[input-uuid-1] * 2). Formulas may begin with =. It does not support arbitrary JavaScript, spreadsheet functions, ranges, strings, macros, or external data.
The Node test suite covers core behavior and demo states: normalization, dependency extraction, evaluation order, invalid syntax, missing dependencies, cycles, trace output, JSON round trips, all presets, edited-input normalization, and intermediate-node tracing.