scmJS docs

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.