Repository layout
src/
atoms/ Jotai state: editor/document atoms (incl. undo history), UI + dialog stack
editor/ Invertible edits and placement checks for every map layer
data/ Reference tables (tilesets, players/colours, units, upgrades, techs, trigger definitions)
formats/
chk/ CHK container, section registry, typed section codecs
mpq/ .scm/.scx open + save on top of mopaq
tileset/ cv5/vf4/vr4/vx4/wpe decoding, megatile atlas, terrain catalogue, palette
dat/ units/flingy/sprites/images.dat, .tbl, GRP, PCX, .lo and iscript.bin decoders
units/ Unit data, lazy GRP/.lo/remap loading, frame cache, the iscript animator
triggers/ TrigEdit-syntax printer and parser
plugins/ Plugin API (the contract), host, loader, built-in registry
services/ Map open/save pickers, PNG export, startup preload
components/
chrome/ MenuBar (Radix Menubar), ToolBar, StatusBar
panels/ Left dock (layer rail + palettes), right dock (Minimap, Layers, Properties), plugin panels
viewport/ Canvas map view with rulers, hover brush, context menu
dialogs/ All scenario dialogs + DialogHost registry
splash/ Splash card that fades over the editor
ui/ Primitives: Button, inputs, Check, Group, ListBox, Tabs, Tip, DialogFrame
styles/ tokens → base → ui → chrome → panels → viewport → dialogs → splash
plugins/ Built-in plugins (plugin.json + plugin.ts each), bundled by Vite, loaded like remote ones
State is all Jotai, with no context or provider layering beyond the default store.
CSS is plain, layered in that import order, with design tokens as CSS variables in
tokens.css.
Two deliberate sources of truth: scenarioAtom holds the parsed Scenario that gets
written to disk and is mutated in place, while the older atoms in editorAtoms.ts
(mapNameAtom, mapWidthAtom, …) are what the chrome displays. Change one and you
have to change the other. Because the scenario is mutated rather than replaced, React
does not see terrain edits; terrainRevisionAtom and its siblings are counters bumped
after every edit, and the viewport subscribes to them.
Adding a dialog means touching two places: the DialogId union in
src/atoms/uiAtoms.ts and the REGISTRY in
src/components/dialogs/DialogHost.tsx.
CLAUDE.md goes into more detail on each subsystem than this file does.