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.