# Goal 30 — Split statum.js into four modules + automatic pstm rewrite Branch `statum-js-modules` (cut from master c741172). All findings below verified against the source on this saddle. The locked brief and task #212 comment 814 are authoritative; this plan follows them, with two implementation choices called out where 814 left naming open. ## Verified ground truth (from investigation) - js/statum.js (3,943 lines) section boundaries match the 814 inventory exactly: core = 1–1123 + 3531–3561(renderAll/guards/hooks) + 3690–3943 (boot/state/init/API); document = 1125–1256 + 3357–3688 minus `applyErrorClasses`; markdown = 1257–3335. - `processInner` :3504 hardcodes the order class→attribute→disabled→text/html/markdown/demotion→wireAction→recurse. Splitting stm-markdown out (it loads AFTER document) means plain registration-order execution would demote before markdown renders — the seam needs explicit ordering. - `applyErrorClasses` :3638 (document territory) is called by core's `reportFailure` :304 → moves to core per the brief. - `performAction` :935 rejects on HTTP error / error-directive (`reportFailure` :302 re-throws unless suppressed) → `stm-clear-on-success` can await the `__stmAction` stash. - Delivery today: `src/meson.build` embeds js/statum.js via mkres as `/_statum/resource/statum.js` (ClientScript.vala); mkpstm `rewrite_resources_in` :401 does the generic `pstm-res-*` rewrite; example/main.html:8 has the single `pstm-res-src="statum.js"` (stays UNMODIFIED — the compat proof); frontend-demo/server.php:362 serves `/statum.js` off disk; index.html:405 references it, with toast overrides right after. Markdown tests load the file via `std.loadFile` under a stub shim. - Toolchain: gcc/meson/valac + all pkg-config deps present. php NOT installed (dnf has it — needed for the demo e2e). qjs builds vendored on demand. - No js/ references in MANIFEST.usm/.usmignore/.gitignore — packaging untouched. ## Design decisions (implementation-level, stated per the "deviate only with reason" rule) 1. **Bridge = `window.statum._bridge`** — core's IIFE builds the public API plus this internal object holding the shared seam (evalExpr, childScope, stateScope, renderAll, registerAttribute, installRenderDriver, performAction, trigger*, handlers, hooks, and the forms-hosted dirty WeakSet slot). Underscore prefix follows the existing `_testMarkdown` private-by-convention precedent. "Guarded IIFE" = each module starts by checking `window.statum && window.statum._bridge`, else `console.error('[statum] requires statum-core.js')` and returns (no throw). 2. **registerAttribute ordering** — `bridge.registerAttribute(name, fn, opts)` where `opts.after` / `opts.before` name another attribute; core resolves to the ordered handler list `processInner` iterates. Document registers its handlers in today's relative order; markdown registers `stm-markdown` with `after:'stm-html'`; document registers `stm-h-demotion` with `after:'stm-markdown'` (present-but-late if markdown never loads — same as today's "runs after html" guarantee). A split test pins the final order byte-for-byte to today's. 3. **Interim tree coherence** — each task leaves the tree building and `meson test` green: task A deletes js/statum.js and rewires meson to the modules that exist (3 after A, 4 after B); the example page only becomes servable again in task C when mkpstm expands the reference. ## Tasks (sequential; each a separate task branch) ### Task A — Split core/document/markdown + seams (implement) - Create js/statum-core.js, statum-document.js, statum-markdown.js per the 814 line map; delete js/statum.js. Core keeps: config/state/persistence/session/listeners/mutation, headers+freshness, HTTP helpers, entrypoint/action fetch, directive dispatch + terminal directives, realtime channel, boot visibility, guards/hooks/renderAll, public state accessors, init/autoInit/public API, `handlers`+`trigger*` (:74/:249–307) preserved exactly, `applyErrorClasses` moved in. Document keeps: loop/conditional/class/attribute/disabled handlers, render walk (render/processElement/processInner/applyDefs), text/html binding, h-demotion, action wiring (wireAction/runElementAction/collectData/setBusyState/reapplyDisabled). Markdown = engine :1257–3335 verbatim + the stm-markdown binding + `statum.markdown.render(src)` / `renderTo(el, src)` sharing the binding's single code path + the `_testMarkdown` hatch moves with it. - Add `_bridge`, registerAttribute seam, render-driver install point (renderAll calls the installed driver inside its existing try/catch; absent driver = hooks+guards still run, no walk). - Rewire src/meson.build to three mkres resources (statum.js resource name disappears; ClientScript class replaced by per-module classes registered in Statum.vala). - Tests: new tests/split (load order, public surface incl. getter/setter handlers, handler order, double registration, module-without-core guard, markdown-without-document cherry-pick); tests/markdown loaders switched to core+markdown (regressions + spec suite stay green, gate 95%). - Verify: tests/markdown/run.sh, tests/split, `meson setup && ninja && meson test`. ### Task B — Forms module: stm-prefill + clear-on-success (implement) - js/statum-forms.js registering `stm-prefill` (early in handler order): property writes only — `.value` for input/select/textarea (radio name-group matching within the form), `.checked` for checkbox (boolean coercion) and radio (value match); skips any element in the dirty WeakSet. - Dirty tracking: one WeakSet hosted on `_bridge` (per the brief, reusable by the future task-186 fix) + capture-phase `input`/`change` listeners on document, and a `reset` listener that un-dirties the form's controls (doubling as the server form-reset un-dirty path). - `__stmAction` promise stash: one-line change in statum-document.js wireAction listener (`el.__stmAction = runElementAction(el); return el.__stmAction;` shape). - `stm-clear-on-success` on a form: submit listener awaits the triggering element's stashed promise; on resolve → `form.reset()` (fires the reset un-dirty path) ; rejection → leave fields. `statum.forms.reset(form)` as the programmatic equivalent. - Non-regression: stm-attribute.value keeps writing the attribute (server-wins, untouched); no change to C1/C2/stateless-walk behaviour. - Tests: tests/dom — real-ish DOM shim (~250 lines, ordered attributes, classList, tree mutation, atob/btoa, stubbed fetch; state injected via hand-built frames or a documented `__STATUM_TEST__` hatch) covering the walk (if/else/for/text) and prefill scenarios: value, dirty-skip after input, checked, radio group, clear-on-success resolve/reject, forms.reset, reset-event un-dirty. model.md attribute table gains stm-prefill / stm-clear-on-success rows. meson gains the 4th resource. - Verify: tests/dom + tests/split + markdown suite + meson test. ### Task C — mkpstm rewrite + delivery + docs + demo e2e (implement) - mkpstm: `pstm-res-src="statum.js"` on a `