system.cssthe engine GitHub

The rocket state canon

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:

  1. Client-owned state rides an ATTRIBUTE on the host, shielded by data-preserve-attr in the server markup — never a JS-only flag. (dialog open, data-ui-state for a popover, aria-expanded for a toast's fold, data-ignore for 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.
  2. 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.
  3. State changes come from USER INTENT only: focus, clicks, and isTrusted events. A signal write dispatches synthetic events (isTrusted: false) — a rocket that treats those as input will act on its own echo.
  4. 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.
  5. Content is server truth and morphs freely. Armor the CONTAINER (data-ignore-morph for imperative third-party DOM, REPLACE-mode patches for wizard bodies), never the data.