6 — What may import what
Written: 2026-08-15 · Established by: extracting every cross-directory from '../…' import
across src/ outside src/__tests__/ at commit 6052e10, then reading each edge that ran against
the layering.
This describes the dependency direction that exists, not one that is enforced. No lint rule
constrains imports between directories — eslint.config.js restricts only node:* builtins. So this
diagram is a measurement, and the four edges called out below are the ones the measurement found
going the wrong way.
Three layers, and one dotted arrow that should not be there.
The solid arrows are the layering: React reads the store, the store drives the engine, and the engine reaches neither back. The dotted pair is the exception, and it is the whole finding — four engine-layer directories reach up into the render layer for geometry that was never render-specific.
| Directory | Depends on | Notes |
|---|---|---|
src/parser |
src/types |
The bottom of the graph |
src/types |
src/parser |
Type-only; see below |
src/checks |
src/parser, src/transforms, src/utils, src/types |
|
src/transforms |
src/parser, src/utils, src/types |
Pure core; its dialogs are React |
src/sim |
src/parser, src/utils, src/types, src/viewer |
|
src/clearance |
src/parser, src/sim, src/utils, src/viewer, src/types |
Engine layer, and stateless by construction — the fixture-clearance check (issue 35). src/sim is placedTriangles and stockBodies, the placement and role helpers it shares with the workpiece rather than any part of the field |
src/utils |
src/parser, src/types, src/viewer |
|
src/workers |
src/sim, src/parser, src/utils, src/types |
|
src/corpus |
src/parser, src/utils, src/types |
|
src/store |
src/parser, src/sim, src/transforms, src/utils, src/viewer, src/scenarios |
|
src/viewer |
src/store, src/sim, src/transforms, src/parser, src/utils |
|
src/scenarios |
src/store, src/types, src/utils, src/parser |
Same shape as store/viewer: the bake types and fetch helpers are pure and store/scenario-slice.ts imports them back, while ScenarioBar.tsx reads the store. src/parser is Units, type-only (issue #408) |
src/sidebar |
src/store and most of the rest |
The widest importer |
src/codemirror |
src/sidebar, src/types |
One symbol; see below |
src/export |
src/store, src/transforms, src/codemirror |
|
src/playback |
src/store, src/viewer, src/parser |
|
src/commands |
src/viewer, src/utils |
|
src/compare |
src/store, src/export, src/transforms |
|
src/bug-report |
src/store, src/parser, src/utils |
|
src/control-profile |
src/store, src/parser, src/components, src/utils, src/types |
Added by #1108, not by the extraction this file was written from — see the note below |
src/about |
src/components, src/utils |
No store; a static credits dialog (#322) |
src/components |
src/utils |
Leaf |
src/editions |
nothing | Leaf; not yet imported by the app — see below |
src/dev |
src/store, src/sim, src/viewer, src/scenarios, src/bench |
Dev-only; src/bench through a dynamic import inside the DEV block, so no edition ships it |
src/bench |
src/store, src/scenarios, src/viewer, src/components, src/utils, src/editions |
The in-app benchmark runner and its dialog (issue #2104). It drives the store through the same actions a click does — opens a scenario, runs the simulation, drives playback — reaches the pipeline only through src/viewer/picture-tally.ts's sample subscription and the drawn batch through src/viewer/picture-stats.ts, and asks src/editions which build it is measuring. Present in the verification edition alone, behind ScenarioBar.tsx's edition-gated dynamic import. src/scenarios imports its BenchmarkSpec type back, type-only, the way src/types and src/parser already do |
src/track |
src/store, src/utils, src/types |
The program on one time axis under the backplot (issue #2226). track-model.ts is pure over utils/cycle-time and utils/playback; Track.tsx reads the store directly, because it owns selection and the seek — the carve-out .claude/rules/ui.md names |
src/harvest |
nothing | Offline; see below |
src/reference |
— | Leaf; test/tooling-only, see below |
src/__tests__ |
everything | pure, dom, scene, scripts |
Four rows this file did not measure
src/clearance, src/control-profile, src/bench and src/track were added by
#1294,
#1108,
#2104 and
#2226, each of which created its directory.
Their dependencies are read off those changes' own imports rather than off the 6052e10 extraction
in the header, so the four rows are accurate for their directories and say nothing about whether the
rest of the table has moved since.
The rate is the finding, not the rows. pure/architecture-diagrams.test.ts fails a
top-level directory under src/ that no diagram names, so every new subsystem arrives as a red gate
that the smallest honest fix turns green — a hand-written row. That is the right response to the
guard and the wrong shape for the table, because the rows accumulate faster than anyone re-runs the
extraction. Refreshing the whole set is
#1302.
The four edges that run against the layering
src/sim, src/utils, src/store and src/transforms all import src/viewer/utils.ts. Nine
files outside src/viewer/ import it, and between them they take ten symbols: arcGeometry,
arcLength, arcSegmentCount, arcExtremePoints, chordTolerance, computeBounds, unionBounds,
buildAllSegmentPositions, buildTransitionMarkers and the TransitionMarker type. The first seven
are arc geometry and bounds with nothing to do with rendering, sitting in the render layer because
that is where they were first needed. Issue #80 is open against exactly this and proposes one geometry
layer below the viewer.
src/types/gcode.ts imports five types from src/parser. So src/parser is the bottom of the
graph, not src/types: the shared vocabulary is defined where each construct is detected — Units,
SubprogramInfo, CannedCycleInfo, ToolTableEntry, G10Offset — and gcode.ts re-exports them.
These are type-only imports and cost nothing at runtime.
src/checks/fixes/ imports src/transforms. Two symbols only, applyEdits and the Edit type.
A guided fix produces the same edit shape a cleanup tool does, so this is reuse of the splice
machinery rather than a check reaching into the transform UI.
src/codemirror/check-marks.ts imports src/sidebar/severity.ts. One symbol, SEVERITY_LABEL.
The editor's gutter marks and the checks panel have to name a severity the same way.
src/editions has no importer yet, and that is temporary rather than the shape of the graph.
Edition, Capability, the manifest table and entitled() (issue #261, ADR-0055) exist to be
imported widely — every future gated call site in store/, viewer/ and App.tsx reads it — but
#261 built the mechanism and deliberately carved nothing out of the app itself; that is #262 (free,
basic) and #263 (demo). So today it sits below every layer with zero edges in either direction,
unlike src/harvest/src/reference below, which are permanently outside the graph by design.
The directories outside the app entirely
src/harvest is outside the app's dependency graph entirely. Not the engine layer's floor, and
not omitted from the flowchart by an oversight: it imports nothing under src/, and nothing under
src/ imports it back. bin/harvest-user-stories (issue #65 / ticket 267) is a CLI script run by
hand or on a schedule, never by the app in a browser — it reads docs/user-stories/lexicon.txt and
sources.txt and writes .user-stories-cache/hits.jsonl, and sits under src/ only so
src/__tests__/pure/harvest/ gets the fast, DOM-free project the same way every other pure module
does.
src/reference imports nothing outside itself, and nothing outside its own tests imports it.
It is the reference-interpreter trace schema and adapters (issue #272) — bin/capture-reference-traces
(outside src/) shells out to two externally-built interpreters and feeds their output through it to
write the committed JSONL under test-fixtures/reference/, and src/__tests__/pure/reference/ reads
both the adapters and those committed captures. No runtime code path reaches it; it exists for the
test suite and for a developer regenerating captures out-of-band, never for the built app.
Neither is the engine layer's floor: the flowchart above is the three-layer app (React, store, engine), and a box with no edge into any of them would say nothing the table rows above don't already say.
Where the store is genuinely absent
The engine subgraph reaches no React and no store, and that is what makes src/__tests__/pure/ the
project most of the suite lives in — it runs in a node environment with no jsdom setup, in roughly
a quarter of the whole suite's time. src/transforms straddles the line: its .ts core is pure, and
its *Dialog.tsx components import the store. Only useDeferredTransform.ts does so among the
.ts files, and it is a React hook.
What this map does not tell you
It is directory-level, so it hides the shape inside each box. src/viewer alone holds 33 plain .ts
modules next to its scene components. It also draws every edge at equal weight, which is why
the load path, the write path and the simulation pipeline get their own diagrams rather than being
read off this one.