goal-30-plan.md 9.4 KB

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] <module> 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 <script> expands at build time to the four ordered script tags (/_statum/resource/statum-core.js → document → markdown → forms), one build warning per page naming page + expansion; explicit module tags pass through unwarned; a page mixing statum.js with explicit module tags emits the full ordered set once, deduped, and the warning says so. example/main.html stays byte-identical.
  • frontend-demo: server.php serves the four module files (replacing /statum.js), index.html loads them in order with the toast-override script after; demo must work end to end and the override mechanism (onNotifyHandler/onConfirmHandler/onErrorHandler setters + trigger*) still functions — proven by the split-suite assertions plus a served-page check (four tags in order, modules fetchable, override script present).
  • WHATS_NEW.md (root): stm-prefill semantics incl. checked/radio groups, stm-clear-on-success + statum.forms.reset, statum.markdown.render/renderTo, statum.registerAttribute for module authors, per-module script tags + ordered load contract, automatic pstm-res rewrite with the legacy-reference warning. Short, example-driven, links to model.md/getting-started.md.
  • Docs: model.md/getting-started.md delivery sections rewritten for modules + rewrite; statum-issues.md S-4 note (superseded by stm-prefill) and mkpstm docs updated.
  • Verify: full build; generated HomePage.vala contains the four tags (backwards-compat proof with the original unmodified reference); run statum-example + curl page/modules; install php via dnf, run frontend-demo (PHP_CLI_SERVER_WORKERS=5) + curl; all three JS suites + meson test green; no dead single-file remnants (grep -r "resource/statum.js" only matches the rewrite map + docs).

Non-goals / constraints

  • ES5, zero dependencies, no bundler, no router module; SSE/SharedWorker stays in core.
  • No behaviour change to the engine, the override mechanism, or directive semantics — moved, not redesigned. Non-regression on C1/C2/stateless-walk per task 186.
  • USM packaging/MANIFEST untouched.

Risks

  • The DOM shim is the largest new test surface; scoped to the walk + prefill paths only, and state injection uses a documented test hatch rather than widening the public API.
  • mkpstm expansion touches libxml2 node surgery (insert siblings before remove) — covered by a generated-output check in the example build, and by keeping the generic pstm-res-* rewrite untouched for non-script elements.