2 — How a dropped file becomes a picture
Written: 2026-08-15 · Established by: reading src/parser/index.ts, src/parser/tokenizer.ts,
src/parser/interpreter.ts, src/checks/index.ts, src/store/file-slice.ts,
src/platform/nativeFileAccess.ts and src/platform/useNativeFileEvents.ts at commit 6052e10.
This is the path everything else hangs off. A file arrives, parseGCode turns its text into a
GCodeFile, the store holds that one object, and the viewer and the sidebar are both projections of
it. Nothing is cached between the text and the picture, so re-parsing is how every later change lands
too — see diagram 4.
Intake and fan-out. The store holds one GCodeFile, and three surfaces are projections of it.
Two shapes of intake feed the same loadFile: a browser File object, read client-side, or —
under Tauri only — a path read on the Rust side and handed over already as text, since there is no
File object for a path the OS gave the app rather than a <input> element.
native is src/platform/nativeFileAccess.ts's openFilePicker() (the File menu's Open, and the
header's Open button) plus src/platform/useNativeFileEvents.ts's two live subscriptions
(Finder/Explorer drag-and-drop, and the .nc/.ngc file a launch was opened with) — three call
sites, one Rust command, both converging on the same loadFile the browser path already used.
Inside parseGCode. tokenize is the only stage that reads the raw text; every other stage reads
blocks, and three of them read the interpreted moves as well.
The order in parseGCode is the contract
tokenize is the only thing that reads the raw text. Everything downstream reads GCodeBlock[], and
three of the six stages read the interpreted Move[] as well. That split is why a check can report a
line number without re-lexing, and why src/transforms/apply.ts can splice text back by block index.
interpretBlocks is where the program is actually executed. It carries modal state, expands
subprogram calls and canned cycles, and returns the four things the rest of the parse needs: the
moves, the block index it halted at if it could not continue, the calls whose expansion hit the cap,
and the cycle it declined. Those last three exist so runChecks can report a limit as a finding
rather than the parser failing silently.
Halting is normal and partial. haltedAtBlockIndex is a real state a production file reaches —
issues #291, #293 and #296 are all programs that halt on a construct Ranger does not yet interpret.
The moves found before the halt are still drawn.
Two invariants this path enforces that the diagram cannot show
Position known-ness is per axis. A move that commands an axis never established is kept with
fromAssumed: true rather than dropped, and anything turning moves into geometry, bounds or a count
of real motion filters on isDrawable — ADR-0016.
Units are 'mm' | 'inch' | null, and null is not millimetres —
ADR-0021. detectUnits returns the
third value for a program that declares neither, which is a program that runs in whatever the control
was left in.
Where the second entry point is
safeParseGCode in src/parser/index.ts wraps the same call in a try. The store's loadFile has its
own private tryParse doing the same thing, because a caller outside the store — a second file picked
for comparison in src/compare/ — has no undo history to fall back on if the parse throws.