docs(principles): a front end displays state; it does not reimplement behaviour
User ruling 2026-09-20: minimise the amount of new code a new UI framework needs. Centralise logic in a redux-like store so a second or third front end is a function from state to pixels plus a wire for events, not a second implementation of how the application behaves. Names the pieces that already exist as what they are -- Command is the action, reduce_view and WidgetCache::apply are the reducer, ViewState plus the caches are the store, Sessions::apply is dispatch -- and gives the question to ask of any front-end code: would a third front end have to write this again? Three things make it concrete: the measure is lines of framework-specific code and should trend to 'paint and wire'; a derivation done twice is the defect even when both copies are correct today (improvise-edp is the worked example, gotcha 44 is the same defect still open); and sharing a table beats testing two copies of it, which is why the keymap is shared rather than mirrored. Also says what legitimately stays local: painting, event wiring, and a semantic-value-to-framework-type lookup like AxisColor -> axis_fg. Sections 5-7 renumbered; the §6 testing references in repo-map.md and the code-review skill updated with them. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EoW5BB87ZZdm96SvBTWawH
This commit is contained in:
+4
-4
File diff suppressed because one or more lines are too long
@@ -24,7 +24,7 @@ them here would let the copy drift:
|
||||
|
||||
| Source | What it holds |
|
||||
|---|---|
|
||||
| `context/design-principles.md` | The "why" — functional-first, polymorphism over conditionals, correctness by construction, layering, §6 testing doctrine |
|
||||
| `context/design-principles.md` | The "why" — functional-first, polymorphism over conditionals, correctness by construction, layering, §7 testing doctrine |
|
||||
| `context/repo-map.md` | The "where", plus **numbered gotchas** — read the ones your diff touches |
|
||||
| `prolog-sessions/kb.pl` | Executable knowledge: `invariant/2`, `doctrine/2`, `forbidden/2`, `decision/3`, and prior `work_done/3` |
|
||||
| `AGENTS.md` | Working rules: bug-first tests, `Option`/`Result`/`Box` at the edges, merges over rebases |
|
||||
@@ -75,7 +75,7 @@ Wire tokens are not UI labels and must not follow them.
|
||||
|
||||
## Testing doctrine
|
||||
|
||||
From `design-principles.md` §6 and `doctrine/2`:
|
||||
From `design-principles.md` §7 and `doctrine/2`:
|
||||
|
||||
- **A bug fix needs a test that failed before it.** If the diff claims a fix
|
||||
with no such test, that is the finding. Ask whether it was run against the
|
||||
|
||||
@@ -212,7 +212,60 @@ discarded. No partial writes reach the model.
|
||||
|
||||
---
|
||||
|
||||
## 5. Guidelines for New Code
|
||||
## 5. A Front End Displays State; It Does Not Reimplement Behaviour
|
||||
|
||||
**The goal is to minimise the amount of new code a new UI framework needs.**
|
||||
Logic is centralised in a redux-like store, so a second or third front end is
|
||||
mostly a function from state to pixels plus a wire for events — not a second
|
||||
implementation of how the application behaves.
|
||||
|
||||
The pieces already exist and are worth naming as what they are:
|
||||
|
||||
| redux | here |
|
||||
|---|---|
|
||||
| action | `Command` / `ModelCommand` (`improvise-protocol`) |
|
||||
| reducer | `reduce_view`, and `WidgetCache::apply` per widget |
|
||||
| store | `ViewState` + the per-widget caches |
|
||||
| dispatch | `Sessions::apply` — the one door (gotcha 36) |
|
||||
|
||||
So the question to ask of any code in a front end is: **would a third front end
|
||||
have to write this again?** If yes, it is behaviour and it belongs in the shared
|
||||
layer. Three things make this concrete rather than aspirational:
|
||||
|
||||
**The measure is lines of framework-specific code, and it should trend to
|
||||
"paint and wire".** `web/app.js` is the current benchmark: a few hundred lines
|
||||
that know no protocol, reaching the application through a handful of string
|
||||
calls. A front end that needs to understand `CellKey` coordinates, or which
|
||||
category a row label belongs to, or what a keypress means, is one that has been
|
||||
handed a job the store should have done.
|
||||
|
||||
**A derivation done twice is the defect, even when both copies are correct
|
||||
today.** Two front ends deriving the same fact will diverge, and the divergence
|
||||
is invisible until someone compares them side by side. `improvise-edp` is the
|
||||
worked example — the category tree was flattened twice, two different ways, and
|
||||
the fix put `viewmodel::flatten_cat_tree` where both ends call it. Gotcha 44 is
|
||||
the same defect still open: the TUI derives the grid's *frame* from
|
||||
`GridLayout`, the browser cannot, and so the browser renders `Food/Alice` where
|
||||
the TUI renders two header columns.
|
||||
|
||||
**Sharing the table beats testing two copies of it.** Where a front end must
|
||||
turn input into actions, share the mapping rather than mirroring it: two
|
||||
hand-kept copies held in step by a test are one definition waiting to be made
|
||||
(`doctrine(a_consolidation_that_keeps_its_compatibility_test_is_unfinished)`).
|
||||
That is why the keymap is shared rather than reimplemented per front end, and
|
||||
why `doctrine(mirror_the_cmd_line_for_line)` is a rule about *resolvers* — the
|
||||
narrow, unavoidable residue — and never a licence to restate a table.
|
||||
|
||||
**What legitimately stays per-framework.** Painting, event wiring, and the
|
||||
mapping from a semantic value to a framework type: `AxisColor → ratatui::Color`
|
||||
in `draw.rs::axis_fg`, `MinibufColor → minibuf_fg`. That mapping is the shape
|
||||
to imitate — the *decision* is in the shared layer as a semantic enum, and only
|
||||
the last translation is local. Anything larger than a lookup table is a
|
||||
candidate for the store.
|
||||
|
||||
---
|
||||
|
||||
## 6. Guidelines for New Code
|
||||
|
||||
- **Add a command, not a special case.** If you need new behavior on a keypress,
|
||||
implement `Cmd`, register it, and bind it in the keymap. Do not add an
|
||||
@@ -236,7 +289,7 @@ discarded. No partial writes reach the model.
|
||||
|
||||
---
|
||||
|
||||
## 6. Testing
|
||||
## 7. Testing
|
||||
|
||||
### Coverage and ambition
|
||||
|
||||
|
||||
+1
-1
@@ -484,7 +484,7 @@ Import flags: `--category`, `--measure`, `--time`, `--skip`, `--extract`, `--axi
|
||||
|
||||
## Testing — the short version
|
||||
|
||||
Full guidance lives in `context/design-principles.md` §6. Quick reminders:
|
||||
Full guidance lives in `context/design-principles.md` §7. Quick reminders:
|
||||
|
||||
- Suite runs in <2s. Don't let that drift.
|
||||
- Commands: build a `CmdContext`, call `execute`, assert on returned effects. No terminal needed.
|
||||
|
||||
Reference in New Issue
Block a user