summaryrefslogtreecommitdiffhomepage
path: root/.dispatch/rules
diff options
context:
space:
mode:
authorAdam Malczewski <[email protected]>2026-06-06 22:08:16 +0900
committerAdam Malczewski <[email protected]>2026-06-06 22:08:16 +0900
commite1c8cf3257cb33457aa882c548f5195ecc0f9854 (patch)
treed355147cdab8eb77917ad02caedf26b3d8d0be57 /.dispatch/rules
downloaddispatch-web-e1c8cf3257cb33457aa882c548f5195ecc0f9854.tar.gz
dispatch-web-e1c8cf3257cb33457aa882c548f5195ecc0f9854.zip
Slice 1: surface system + WS transport + composition root
Pure-core feature libraries assembled at the composition root: - core/protocol: pure reducer over surface catalog/spec/error messages - features/surface-host: generic field-kind interpreter (toggle/progress/ selector/stat/button) + pure plan logic; no surface-id special-casing - adapters/ws: injected WebSocket client (effects at the edge) - app: composition root store (Svelte 5 runes over the pure reducer), host-relative surface WS URL resolution (resolveWsUrl), root App.svelte Verified green: svelte-check 0/0, vitest 84 passed, biome clean, vite build ok.
Diffstat (limited to '.dispatch/rules')
-rw-r--r--.dispatch/rules/frontend-inject-transport.md7
-rw-r--r--.dispatch/rules/frontend-interpreter-generic.md8
-rw-r--r--.dispatch/rules/frontend-no-ambient-state.md7
-rw-r--r--.dispatch/rules/frontend-pure-core.md7
4 files changed, 29 insertions, 0 deletions
diff --git a/.dispatch/rules/frontend-inject-transport.md b/.dispatch/rules/frontend-inject-transport.md
new file mode 100644
index 0000000..fbe83d7
--- /dev/null
+++ b/.dispatch/rules/frontend-inject-transport.md
@@ -0,0 +1,7 @@
+# Rule: inject the transport; parsers are pure
+
+The WS/NDJSON framing + parsing is a PURE function (bytes/messages → typed events);
+the socket/fetch is INJECTED. Test the parser with crafted chunk inputs (and
+trace-replay-style fixtures), never a live connection. The op-protocol core is a
+pure state machine: `reduce(intent, incoming) → { viewModel, outgoingCommands }`;
+the carrier (WebSocket) is the injected shell.
diff --git a/.dispatch/rules/frontend-interpreter-generic.md b/.dispatch/rules/frontend-interpreter-generic.md
new file mode 100644
index 0000000..557f2d4
--- /dev/null
+++ b/.dispatch/rules/frontend-interpreter-generic.md
@@ -0,0 +1,8 @@
+# Rule: the surface interpreter is generic
+
+The surface interpreter switches on field KINDS (toggle/progress/selector/stat/
+button/custom), NEVER on a surface id. An `if (surface.id === "...")` imports a
+feature's identity into the platform and breaks isolation (guardrail 1). An
+unknown field `kind` or a `custom` `rendererId` with no registered renderer →
+GRACEFUL SKIP, never a crash. Render from the spec; the backend owns what a
+surface contains.
diff --git a/.dispatch/rules/frontend-no-ambient-state.md b/.dispatch/rules/frontend-no-ambient-state.md
new file mode 100644
index 0000000..663bf1a
--- /dev/null
+++ b/.dispatch/rules/frontend-no-ambient-state.md
@@ -0,0 +1,7 @@
+# Rule: no ambient state (frontend)
+
+State is owned per-unit and passed explicitly. NO module-global mutable store
+reached from everywhere — that is the old FE's "tools leak across tabs" /
+"model resets on tab switch" bug class. Svelte runes (`$state`) are a THIN
+reactive wrapper over a pure reducer, never the home of logic. Subscriptions are
+owned and disposed on unmount (no orphaned or duplicate subscriptions).
diff --git a/.dispatch/rules/frontend-pure-core.md b/.dispatch/rules/frontend-pure-core.md
new file mode 100644
index 0000000..fa7bc2e
--- /dev/null
+++ b/.dispatch/rules/frontend-pure-core.md
@@ -0,0 +1,7 @@
+# Rule: pure core / injected shell (frontend)
+
+Decision logic — reducers, view-models, formatters, parsers — is pure
+(input → output): NO DOM, NO `fetch`/WebSocket, NO Svelte import. Put it in a
+`.ts` module that tests with zero mounting and zero mocks. Effects (socket, fetch,
+IndexedDB, clock) are INJECTED at the edges (props or an adapter). This is for
+testability, not purity dogma — stop where it would only add ceremony.