THE ROCKET STATE CANON (gf-191, per Mike: "there should be canon
solutions for these situations" — paid for three times: the search rocket, toast-card, the combobox)
Every rocket that holds client state under fat-morphs follows these FIVE laws; a new rocket that deviates is wrong until proven otherwise:
- Client-owned state rides an ATTRIBUTE on the host, shielded by
data-preserve-attrin the server markup — never a JS-only flag. (dialogopen,data-ui-statefor a popover,aria-expandedfor a toast's fold,data-ignorefor maps.) An attribute survives inspection, gives CSS a hook, and morphs can be told to keep it; a closure variable dies silently. THE CONVERSE (gf-470): state the SERVER can know is the server's, un-shielded — the toast's own word (unread·read·leaving) is stamped from seen_at and a just-dismissed grace window; the rocket only echoes it a round trip early, and the next morph agrees. A preserved attribute the server also renders is two truths — the server's can never land. - Observers RESTORE and RE-HOME; they never DECIDE. A MutationObserver may re-home derived state (the highlight the morph swapped out from under you, command-palette's resetActive) and re-assert what the preserved attribute already says — it must never originate a state change. An observer that opens things will one day open them on a data-text ripple.
- State changes come from USER INTENT only: focus, clicks, and
isTrustedevents. A signal write dispatches synthetic events (isTrusted: false) — a rocket that treats those as input will act on its own echo. - Rockets never invent data. Every committable choice is a server-rendered element with its own datastar action; Enter/keys just .click() it. If the rocket can fabricate a selection, the server and client can disagree.
- Content is server truth and morphs freely. Armor the CONTAINER
(
data-ignore-morphfor imperative third-party DOM, REPLACE-mode patches for wizard bodies), never the data.