1. Pure CQRS: commands answer 204; the stream is the ONE view channel
A command handler stores state, answers 204 No Content, publishes.
The view — for every tab, the acting one included — arrives as the
stream's whole-page render (the fat-morph — house term). On table
and product paths this is absolute: never answer a command with
rendered fragments, no matter how much faster it seems.
The ONE sanctioned exception (scoped 2026-08-30, per Mike): a
SELF-CONTAINED exploratory surface — the import-wizard body, the admin
css-chart toggle, the SQL console result — may answer with a targeted
replace-mode patch of its own data-ignore-morph-armored region. The
exception is narrow by construction: the region is morph-armored (the
fat-morph won't fight it), page-local, and renders nothing any other
tab needs. If another tab could care, it's a table path — 204 + publish.
ONE further exception, stated and dated (2026-09-17, gf-362): POST /start/pick (handler/handler.go StartPick) answers SIGNALS — the g:
place reference, the map fix, the office's _name/_website. It is
sanctioned because the sign-up wizard has no org and opens no stream
(Page.NoStream), so there is no view channel to answer on. Every page
that streams keeps the one-channel rule.
Why this is a law and not a preference — the hybrid (fragment answer + publish) was tried and produced three distinct failures:
- The echo: the actor's own stream re-rendered and re-shipped the whole page it had just received — double server render, double bandwidth on the same thin pipe the next answer needed.
- THE RACE: publish fired before the answer was written; when the whole-page morph reached the browser first it morphed stale DOM into the new state at full cost (~5s on a 3.5k-row reorder), striking at random — which made every instrumented run disagree with every lived experience. Nondeterministic bugs cost 10× the tokens.
- Cold dictionaries: each fragment answer compressed from scratch (130KB for a table 99% identical to what the client held). The stream's long-lived brotli window compresses successive near-identical frames to almost nothing — the cheap channel was the stream all along.
Corollaries: any state a view needs must be stored state (even the
half-typed search term — TableLens.Live), because the stream can only
render what the store holds. Signals are TRANSPORT, not truth: they
carry the input up; the store renders it back down, and a morph
re-merging a signal server-authoritatively (plain data-signals, no
__ifmissing) is correct behavior. And if you ever must publish from a
path that also writes a response: answer first, publish after,
always.
The store's fine print (each line was a bug):
- No lens state in the URL, ever — sort/scope/unlinked/facets are
@posts against the prefs row (
sys_pref, the lens key per user/org/section —db.TableLens), merged on write. - A nil prefs patch value DELETES its key, so absent == spec defaults; writers must know absent-vs-empty or a lens write resurrects hidden columns.
- Selection is server state (a
sys_prefrow per user/org/section); bulk verbs read the store, and select-all is oneINSERT…SELECTunder the live lens WHERE. - Search terms REFINE (AND); same-facet values OR within their key;
facet counts must filter
archived_ator parked rows leak in. - Default lens =
status:active— stored-lens ABSENCE seeds the filter; an explicit clear is respected. The reset verb restores default lens + spec columns + empty selection in ONE 204.
Actor-exclusion (tab ids, skip-the-actor publishes) was built and then deleted in the same day: under pure CQRS there is no second channel to deduplicate. If you find yourself building exclusion, you have two channels — remove one instead.