system.cssthe engine GitHub

2. Morph at scale

2. Morph at scale — and a stale-measurement warning

Idiomorph over this stack, measured at 3,541 keyed table rows:

operation cost
in-place attribute flips (checkbox, column hide) 300–450ms, no long task
row-set shrink (search narrowing) fast, no long task
reorder (sort) ~5.1s STALE — see below
native swap of the same subtree (outerHTML) 117ms + 268ms datastar rescan

The ~5s reorder figure was measured with since-deleted code in the path: a client table rocket (MutationObservers re-scanning on every morphed node, purged gf-98 — rosters are server markup under the engine's .roster component now) and the race-era double channel (gf-91/93). Re-measured 2026-08-26 on the deployed Pi via the admin Stream bench (gf-99d; the bench itself was retired in gf-111 once it had done this job — the numbers stand, the instrument is gone): an 8,000-row reorder frame — comparable DOM node count to the full 3.5k roster — cost 30.5ms on the client main thread (~4µs/row); server side 57ms warm. The real morph engine is ~165× faster than the stale figure said; the seconds belonged entirely to the deleted code. Lesson twice over: a measurement is stamped with the code that produced it, and quoting it after that code is deleted is guessing wearing a number. Morph cost at scale is REAL but linear-ish, not cliff-shaped; re-measure before fencing anything — the longtask beacon on Admin → Usage ("How it feels") is the standing client instrument, and a scratch Go probe timing each render piece alone (a throwaway _test.go that times one render piece — gf-99c found 424ms/render hiding in one picker fill → 4ms) is the server-side one. The hot window (below) remains the right default because EVERY leg scales with row count. Row id attributes are still mandatory — they make the in-place cases correct and cheap.

Morph armor (the fat-morph's escape hatches — client details in the datastar-components skill): a region whose subtree the server must not diff carries data-ignore-morph on BOTH the shipped and the incoming node, and updates only via replace-mode patches from its own command; a single attribute that must survive (a dialog's open, a style) rides data-preserve-attr. Never use preserve-attr as subtree armor. And a nested <form> is DROPPED by the full-document parser but legal inside a morphed fragment (it arrives through a <template>) — a dialog form that works after a morph and vanishes on a fresh land is this; formaction on a button is the idiom that needs no nesting.

DOM scale laws: one server-filled edit dialog PER ROSTER (filled with the single-selected row on each selection morph), never a dialog per row — N dialogs in the DOM is the 2000-customer scaling bug solved before it shipped.