Skip to content

The frontend: where things live

ARCHITECTURE.md § The frontend has how it works, and its source map lists every file. In short:

Where What
src/*.ts Feature modules, flat. A feature usually has a pure model (samplerMap.ts, stepSeq.ts, regionGrab.ts) and a module doing the store and Tauri work beside it.
src/automation.ts, src/automation/ Automation, in several files behind one set of imports.
src/components/*.tsx One file for each pane, window or sheet.
src/components/fx/ Effect and instrument panels and their shared controls (common.tsx, dynControls.tsx).
src/components/pianoRollEditor/, tracksCanvas/, audioTrackEditor/ The parts of PianoRoll.tsx, Arrange.tsx and AudioEditor.tsx: drawing, pointer, keys and menus.
src/components/settingsPanes/ One file per Settings pane, and what they share in parts.tsx.
src/styles/ The stylesheet, one file per area. index.css imports them, and its order is the cascade.
tests/*.test.ts One test per pure module, and the guard tests that read the sources.

A module with no screen in it stays testable. Node loads it directly, so it can’t import Tauri or React at run time. Its own relative imports carry the extension (from "./midiEdit.ts", which Vite and TypeScript accept too), import type can stay as it is, and it can’t use syntax Node has to transform, such as a TypeScript enum (use a const object). The test imports it from ../src/<module>.ts.

A canvas editor is a component and its parts. PianoRoll.tsx, Arrange.tsx and AudioEditor.tsx own the refs and the effects and lay out the panes; drawing and event handling are functions in their folder that take those refs (tracksPointer, noteGestures, editorDrawer). The rules that keep this safe:

  • Hooks stay in the component, so hook order and dependency arrays can be read in one place. The parts hold no state and call no hooks, and being rebuilt on each render costs what the closures always did.
  • Imperative code reads current values through refs (live.current), never from a closure over a render’s values. A value it does take from the render, such as showAutomation, is passed in on each render, and an effect with [] must not capture one.
  • A drag adds mousemove and mouseup listeners on window when it starts and removes them on release (dragWith in the Piano Roll), so it keeps working when the pointer leaves the canvas.

Keep a folder’s name different from its component’s by more than case (settingsPanes/ beside Settings.tsx, not settings/): Windows and macOS file systems ignore case.

Flat modules stay where they are unless there’s a reason. App.tsx imports most of them, and a move touches it, the tests, these docs and any branch in flight. A file that outgrows itself can become a folder of the same name with the old path re-exporting it, as automation.ts did, and importers don’t change.