Thing 2.0 build spec: per-piece contracts, build order, tests, prior art
Some checks failed
Portable Eve contracts and browser lowering / ubuntu-latest eve-browser-lowering (pull_request) Has been cancelled
Portable Eve contracts and browser lowering / ubuntu-latest eve-contracts (pull_request) Has been cancelled
Portable Eve contracts and browser lowering / windows-latest eve-browser-lowering (pull_request) Has been cancelled
Portable Eve contracts and browser lowering / windows-latest eve-contracts (pull_request) Has been cancelled
Some checks failed
Portable Eve contracts and browser lowering / ubuntu-latest eve-browser-lowering (pull_request) Has been cancelled
Portable Eve contracts and browser lowering / ubuntu-latest eve-contracts (pull_request) Has been cancelled
Portable Eve contracts and browser lowering / windows-latest eve-browser-lowering (pull_request) Has been cancelled
Portable Eve contracts and browser lowering / windows-latest eve-contracts (pull_request) Has been cancelled
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
0ba536eec1
commit
e95f1ab5ba
2 changed files with 750 additions and 0 deletions
746
docs/thing2-build-spec.md
Normal file
746
docs/thing2-build-spec.md
Normal file
|
|
@ -0,0 +1,746 @@
|
|||
# Thing 2.0 build spec
|
||||
|
||||
Status: proposal for review (PR), 2026-11-05. Written for Meta's ask t12-m0
|
||||
(#cultlib, 2026-11-04) on top of `docs/thing2-target.md` (why, invariants) and
|
||||
`docs/thing2-map.md` (body facts P1-P22, model page, consumer contract, authority
|
||||
map, cut order). It does not restate them; it adds what the map leaves at
|
||||
deliverable level: **the pieces, each piece's contract, the build order, and the
|
||||
test that defends each piece**, with prior art for every design choice.
|
||||
|
||||
Nothing here overrides a ruling. Where this spec makes a choice the rulings do not
|
||||
force, it is labelled **D<n>** (decision, with confidence) and the ones it could
|
||||
not settle are in "Open" at the end, each with a recommendation.
|
||||
|
||||
## How to read the prior-art tags
|
||||
|
||||
Each piece ends with **Prior art**. Tags: **[read]** = a source read in an earlier
|
||||
Eyes or Imagination pass, with its evidence pointer in `docs/research/*` (cited as
|
||||
R-F = `thing2-foundation-prior-art.md`, R-T = `thing2-text-input-a11y-prior-art.md`,
|
||||
R-K = `thing2-contract-forks-prior-art.md`, R-C = `thing2-consumer-census.md`);
|
||||
**[background]** = prior knowledge, not re-fetched for this spec, and the first
|
||||
thing to verify when its piece is mapped into a `cut_spec`. Confidence is stated
|
||||
per decision. Meta's rule, as passed to this run, is "prior-art sources cited";
|
||||
if the rule has more to it (format, depth, a required venue) the Open list asks.
|
||||
|
||||
## 0. The machine in one page
|
||||
|
||||
```
|
||||
provider ──publishes──▶ CultMesh state (typed .cc records)
|
||||
│ StatePort (subscribe / read)
|
||||
▼
|
||||
┌───────────────────────── thing-core (one crate, internal modules) ─────────────────────────┐
|
||||
│ resolve ─▶ bind ─▶ layout ─▶ text ─▶ render ─┐ │
|
||||
│ (docs, (Bind, (Taffy) (Parley or ├─▶ FrameOutput { commands, a11y, outbound, │
|
||||
│ compos.) Repeat) cells) │ ime, diagnostics } │
|
||||
│ input (focus, edit, hit-test, scroll) ─▶ command dispatcher ─▶ outbound CommandInvocation │
|
||||
│ a11y (AccessKit tree) │
|
||||
└───────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
▲ FrameInput { now, viewport, scale, events } │ commands + a11y
|
||||
│ ▼
|
||||
host event loop ◀────── thin backend: TUI (crossterm) | web (wasm + JS) | later hosts
|
||||
```
|
||||
|
||||
Core is a **function of (documents, state, input, time)**. The host owns the loop
|
||||
and calls `Core::frame`; core never spawns, sleeps, reads a clock or touches a
|
||||
socket except through a port. This is what makes every test below runnable without
|
||||
a daemon (`doctrine/gamecult.md`, "dependency injection is boring") and what makes
|
||||
goldens byte-stable (map rule 10).
|
||||
|
||||
**D1. One crate for core, internal modules, not eight crates.** (Confidence
|
||||
medium-high.) Authority separation (resolve, bind, layout, text, render, command)
|
||||
is real, but the modules share one frame pipeline and one set of types; a crate per
|
||||
stage buys build fan-out and a public surface no consumer may use, which
|
||||
`narrow-consumer-contract` forbids. Splits that *do* earn a crate, each naming the
|
||||
invariant it protects:
|
||||
|
||||
| Crate | Earns its boundary because |
|
||||
| --- | --- |
|
||||
| `thing-contract` (landed) | consumers depend on it alone (`narrow-consumer-contract`); three runtimes are held to its goldens |
|
||||
| `thing-core` | owns geometry, text, focus, binding, results; depends on no transport, no clock, no platform |
|
||||
| `thing-mesh` | the only crate that depends on CultMesh/CultNet: `StatePort`/discovery implementations. Keeps rule 7 and the mockable-port invariant checkable by a dependency-tree grep on `thing-core` |
|
||||
| `thing-tui` | backend; depends on `thing-core` output types and `crossterm`, never `taffy`/`parley` (rule 8) |
|
||||
| `thing-web` | wasm entry (`wasm-bindgen`) plus the JS host package; same dependency rule |
|
||||
| `thing-testkit` | fake ports, fixture loader, golden harness, shared by every crate's tests; never a dependency of a shipped artifact |
|
||||
|
||||
Prior art: Xilem and egui split by renderer/backends and keep one core
|
||||
(R-K §1: `masonry_core`, `egui`+`epaint`, backends as crates) **[read]**; AccessKit
|
||||
and Parley are separate repos only where independently consumed or needing another
|
||||
toolchain (R-K §1) **[read]**; Flutter merged repos to end rolls (R-K §1)
|
||||
**[read]**. Same repo, many crates, is the dominant pattern.
|
||||
|
||||
## 1. Build order and dependency graph
|
||||
|
||||
```
|
||||
0 decisions ─▶ 1 contract-ts ┐
|
||||
├▶ 1 contract-cs ┴▶ 1x cross-store check
|
||||
└▶ 2 testkit+ports ─▶ 3 resolve ─▶ 4 bind ─▶ 5 layout ─▶ 6 render+goldens ─▶ 7 commands
|
||||
│ │
|
||||
8 TUI ◀────────┘ │
|
||||
9 text(Parley)/focus/edit ─▶ 10 a11y ─▶ 11 web ◀───┘
|
||||
12 thing-mesh (parallel from 4 on)
|
||||
13 consumer ports (one per consumer) ─▶ 14 retirement
|
||||
```
|
||||
|
||||
Rules for the order: (a) contract before core (map, rationale); (b) the pipeline is
|
||||
built **end to end thin first** (cut 8 draws a real surface in a terminal before
|
||||
text, IME or a11y exist), then deepened, because the first full run is where unknown
|
||||
unknowns show; (c) `thing-mesh` runs beside core from cut 4 because it is the
|
||||
other half of the `StatePort` contract and needs the same fixtures; (d) no consumer
|
||||
port starts before cut 7 and one backend (cut 8) work, so a port can be viewed.
|
||||
|
||||
Cuts are sized to Hands' budget (about 150 calls, 0.5M tokens is the danger line):
|
||||
each row below is one `cut_spec`, or two where marked. Sizes are Self's estimates
|
||||
in thousand lines of owned code (excluding goldens), pressure not metric.
|
||||
|
||||
| # | Cut | Waits on | Est. | Parallel with |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| 1a/1b | contract-ts, contract-cs (specs `thing2:cut_spec:cut-contract-*.r2`) | contract-rs (landed `6ae2da0`) | mapped | each other |
|
||||
| 1x | cross-store check | 1a, 1b | 0.3 | 2 |
|
||||
| 2 | testkit + ports | contract-rs | 1.2 | 1a, 1b |
|
||||
| 3 | resolve | 2 | 1.8 | - |
|
||||
| 4 | bind | 3 | 1.5 | 12 |
|
||||
| 5 | layout | 4 | 1.8 | - |
|
||||
| 6 | render + goldens | 5 | 1.5 | - |
|
||||
| 7 | commands | 4 (needs 6 only for hit-test) | 1.6 | 8 |
|
||||
| 8 | TUI backend | 6, 7 | 1.5 | 7 tail |
|
||||
| 9a/9b | text: measure+shape, then focus+editing+IME state | 6 | 2 + 2 | 8 |
|
||||
| 10 | a11y tree | 9b | 1.0 | 11 start |
|
||||
| 11a/11b/11c | web: wasm+drawing, IME mirror + a11y mirror, shell+discovery | 6, 9b, 10 | 1.5+1.5+1.0 | - |
|
||||
| 12 | thing-mesh | 2, 4 | 1.5 | 5-8 |
|
||||
| 13.n | consumer ports | 7, 8 (web for browser consumers: 11c) | per consumer | one repo each |
|
||||
| 14 | retirement | last port | -1x | - |
|
||||
|
||||
## 2. Cross-cutting contracts
|
||||
|
||||
### 2.1 Core API (the one public surface core has)
|
||||
|
||||
```
|
||||
Core::new(ports: Ports, config: Config) -> Core
|
||||
Core::frame(&mut self, input: FrameInput) -> FrameOutput
|
||||
FrameInput { now: Instant, viewport: Size, scale: f32, events: Vec<InputEvent>, metrics: MetricsMode }
|
||||
FrameOutput { commands: Vec<RenderCommand>, a11y: Option<TreeUpdate>, outbound: Vec<CommandInvocation>,
|
||||
ime: Option<ImeRequest>, diagnostics: Vec<Diagnostic>, next_wake: Option<Instant> }
|
||||
Ports { state: dyn StatePort, fonts: dyn FontPort, clock: dyn Clock, ids: dyn IdSource, host: dyn HostEffects }
|
||||
```
|
||||
|
||||
- **One owner for time and ids.** `now` arrives in the frame input; `IdSource`
|
||||
mints invocation ids. Tests substitute both, so a run is a pure function and
|
||||
invocation ids in goldens are reproducible.
|
||||
- **Outbound is invocations only** (rule 7). `Ports.state` has `read`, `subscribe`,
|
||||
`list(schema, key_prefix)`; it has no `write`. The one write path is
|
||||
`FrameOutput.outbound`, which the host or `thing-mesh` forwards.
|
||||
- **`HostEffects`** has exactly the closed `effect` set of the contract:
|
||||
`open_surface(key)`, `open_external(url)`; plus `clipboard` read/write when the
|
||||
text piece needs it (see Open O7).
|
||||
- Metrics mode (`Pixels` | `Cells`) is a core input, because the TUI measures in
|
||||
cells inside the Taffy measure function (R-T §5: iocraft does exactly this
|
||||
**[read]**) and rule 8 forbids the backend from owning any layout input. The
|
||||
cell-metrics provider is part of core (small: `unicode-width` and
|
||||
`unicode-segmentation`), not of `thing-tui`.
|
||||
|
||||
Prior art: Clay's per-frame contract (set dimensions → pointer state → begin layout
|
||||
→ end layout returns the command array) is the model for `frame` **[read, R-F
|
||||
§2.1]**; egui and Bevy UI emit shapes or commands to thin backends **[read, R-F
|
||||
§2.4, R-T §1]**. Elm/Redux style "update is a pure function of state and event"
|
||||
**[background]**. Confidence high: no contrary evidence found.
|
||||
|
||||
### 2.2 The closed render-command list (D2)
|
||||
|
||||
`FillRect{rect, radius, color}`, `StrokeRect{rect, radius, width, color}`,
|
||||
`Text{origin, run, color}` where `run` is `Glyphs{font, size, glyphs}` (Pixels mode)
|
||||
or `Cells{col, row, text, style}` (Cells mode), `Image{rect, catalog, asset, fit}`,
|
||||
`ClipPush{rect, radius}`, `ClipPop`, `Polyline{points, width, color}`,
|
||||
`Polygon{points, color}` (plot marks), `Embed{rect, slot_kind, slot_id}`.
|
||||
Nine variants. Selection highlights, carets, focus rings and scrollbars are
|
||||
`FillRect`/`StrokeRect` that core emits. Z order is emission order; there is no
|
||||
z-index, no layers, no custom escape. A new command is a reviewed change to core
|
||||
plus every backend and the goldens at once (that is the point of closing it).
|
||||
|
||||
Colours in commands are resolved literals; theme tokens never cross to a backend.
|
||||
Coordinates are `f32` logical pixels in Pixels mode, integer cells in Cells mode.
|
||||
|
||||
Prior art: Clay's complete enum is `NONE, RECTANGLE, BORDER, TEXT, IMAGE,
|
||||
SCISSOR_START, SCISSOR_END, OVERLAY_COLOR_START/END, CUSTOM`, with renderers for
|
||||
SDL, raylib, canvas2d, termbox2 and a terminal, all thin **[read, R-F §2.1]**.
|
||||
Thing's list differs on purpose: no `CUSTOM` (Embed is the closed host slot), no
|
||||
overlay-colour pair (an overlay scrim is a `FillRect`), plus polylines for `Plot`.
|
||||
Confidence medium: the count is a proposal; whether `Plot` should lower to
|
||||
`Polyline`/`Polygon` or be left to an `Embed` is O3.
|
||||
|
||||
### 2.3 Diagnostics (D3)
|
||||
|
||||
A closed `Diagnostic { code, node, document, detail }`: `UnresolvedUse`,
|
||||
`BindSchemaMismatch`, `BindMemberMissing`, `UnknownNode`, `UndeclaredCommand`,
|
||||
`DuplicateNodeId`, `StaleVersionIgnored`, `MalformedResult`, `UnknownInvocation`,
|
||||
`InvocationTimedOut`, `DecodeRefused`. The same value drives (a) the placeholder or
|
||||
error node drawn in the frame, (b) a log line, (c) the a11y tree (an error node is
|
||||
announced), and (d) the tests. Rules 2, 4, 5 and 6 are asserted on these codes, so
|
||||
a rule that is deleted fails by name, not by a screenshot.
|
||||
Confidence high; the alternative (free-text logs) is what Thing 1's gap ledgers were.
|
||||
|
||||
### 2.4 Test layers and golden format (D4)
|
||||
|
||||
Doctrine splits tests into local-authority unit tests, adjacent-organ pipeline
|
||||
smokes, and end-to-end Verse contract runs; every piece below names which layer.
|
||||
|
||||
- **Fixtures** (`tests/fixtures/*.surface.cc`, committed): typed documents encoded
|
||||
through `thing-contract`, one per row of the fixture table (§2.5). Built by a
|
||||
`thing-testkit` builder in Rust, then frozen; the bytes are what is loaded.
|
||||
- **Geometry golden**: for a fixture + viewport + state script, the resolved node
|
||||
tree with `(node id, x, y, w, h)` in a stable text form.
|
||||
- **Render golden**: the `Vec<RenderCommand>` encoded with CultCache MessagePack
|
||||
through the same derive the contract uses, plus a line-oriented text rendering
|
||||
beside it for diffs. The binary form is the oracle (byte-identical, rule 10); the
|
||||
text form is only for review.
|
||||
- **Regeneration** is `cargo xtask bless`, runs only on a reviewed core change,
|
||||
and a backend crate cannot call it (model page: goldens are the core's).
|
||||
- **Layout correctness is inherited, tree building is tested.** Taffy is itself
|
||||
validated against Chrome with generated fixtures (`just gentest`, R-F §2.2
|
||||
**[read]**), as is Yoga (`gentest/`, R-F §2.3 **[read]**); Thing does not re-prove
|
||||
flexbox. Thing's tests prove that *our* style mapping puts the right inputs into
|
||||
Taffy and that the output flows into commands unchanged. One extra check
|
||||
re-derives a small fixture's geometry from the raw Taffy API directly and compares
|
||||
(guards against our mapping drifting while goldens are re-blessed).
|
||||
- **Mutation**: `tools/cargo-mutants.sh` once per Hands pass, scoped to the cut's
|
||||
diff, per the Eureka rule. Each piece's "Test" below names the rules whose
|
||||
mutants must die; a survivor is triaged, not hidden.
|
||||
- **Property tests** (`proptest` [background]) only where a total function has a
|
||||
clear law: contract round-trip (exists), `resolve` never panics and either
|
||||
returns a tree whose ids are unique or a refusal, `bind` is idempotent for the
|
||||
same state, `frame` with no events and unchanged state returns an equal command
|
||||
list (stability). Fuzzing the decoder (`cargo-fuzz` [background]) is deferred
|
||||
until `decode-stack-safety` lands (the decode hazard is CultLib's, map P20).
|
||||
|
||||
Prior art: golden command lists are not published by any project read (R-F §2.5:
|
||||
"not found, not searched exhaustively"), so this is the least-supported decision
|
||||
in the spec; the nearest supported techniques are Taffy/Yoga generated geometry
|
||||
tests and egui's `egui_kittest` snapshot crate (R-K §1 lists the crate;
|
||||
its snapshot method is **[background]**). Confidence medium: the idea is sound
|
||||
because commands are renderer-independent by construction; cost of churn on
|
||||
intentional change is the risk, mitigated by `bless` being one command.
|
||||
|
||||
### 2.5 Fixture set (the census sizes the floor)
|
||||
|
||||
Twelve fixtures cover the node set and every rule; each is named for the consumer
|
||||
shape it models (R-C §7 cross-table is the source, and Thing is a general library,
|
||||
so a fixture exists for a capability a reasonable consumer would expect even with
|
||||
no caller today):
|
||||
|
||||
| Fixture | Exercises | Rules |
|
||||
| --- | --- | --- |
|
||||
| `f01-swarm-dashboard` | VoidBot-shaped: Box grid, bound Text/Format, `Repeat` over a collection prefix, one Button | 6, 9 |
|
||||
| `f02-login-effect` | Ghostlight/Heimdall: TextInput, Button, `OpenExternal{resume}` result | 1, 2 |
|
||||
| `f03-input-target` | Muninn: `position: absolute` target over an `Image`, bound label (P6) | - |
|
||||
| `f04-form` | Toggle, Slider, Select, TextInput number, `captures` and `clear_drafts` | 1 |
|
||||
| `f05-modal-overlay` | `Overlay` modal with dismiss command, focus trap | - |
|
||||
| `f06-scroll-list` | `overflow: scroll`, `Repeat` 1k items, keyed insert/remove | - |
|
||||
| `f07-plot` | Stonks/StreamPixels-shaped `Plot` series | - |
|
||||
| `f08-embed-viewport` | `Embed` slot as a leaf of given size | - |
|
||||
| `f09-composition-stack` | nested `Use` with typed args and child slots, `thing.std` plus a publisher composition | 5 |
|
||||
| `f10-hostile` | unknown arm, undeclared `CommandRef`, duplicate id after expansion, unresolved `Use`, schema-mismatched `Bind` | 4, 5, 6 |
|
||||
| `f11-deep` | nesting at the 256 bound (decoded on a large-stack thread until CultLib's fix) | - |
|
||||
| `f12-text-edge` | bidi, CJK wide characters, combining marks, emoji ZWJ, long wrap | - |
|
||||
|
||||
## 3. The pieces
|
||||
|
||||
Each piece lists **Owner** (one writer), **In**, **Out**, **Ports**,
|
||||
**Invariants** (what must die under mutation), **Failure**, **Test** (layer and the
|
||||
rule each pins), **Deletes/forbidden** (what the piece must not contain), and
|
||||
**Prior art**. Map rules are cited as M1-M10 (the ten in the map's "Rules that must
|
||||
die under their own mutation").
|
||||
|
||||
### Piece 1: contract (packages for TS and C#, and the store check)
|
||||
|
||||
Rust (`crates/thing-contract`) landed after two Soul passes; `contract-ts` and
|
||||
`contract-cs` are mapped (`thing2:cut_spec:cut-contract-ts.r2`, `-cs.r2`) and have
|
||||
branches (`hands/thing2-contract-ts`, `hands/thing2-contract-cs`) not yet merged.
|
||||
This spec adds no contract change.
|
||||
|
||||
- **Owner:** the contract crate; packages own nothing (map authority).
|
||||
- **Test (per package):** the shared golden fixtures decode and re-encode
|
||||
byte-identical (M1, M3); the refusal goldens (`seq` 0, extra slot, unknown union
|
||||
arm) are refused; a hand-built object with seq 0 is refused at encode.
|
||||
- **1x cross-store check:** the C# package opens a `.cc` store the TS package wrote
|
||||
and the reverse, and both open Rust's. The check lives in `thing-testkit` as a
|
||||
scripted cross-runtime job run on Yggdrasil's verify stopgap
|
||||
(`tools/stopgap/ygg-verify.sh`), because only that image has all three runtimes.
|
||||
It proves the catalog type names agree (map cut 1 waiting line).
|
||||
- **Prior art:** CultCache wire parity against the C# reference is the standing
|
||||
CultLib rule; probe P12-P13 are the evidence **[read, map]**; golden-file
|
||||
interoperability is how Protobuf and Cap'n Proto test language bindings
|
||||
(`conformance` suites) **[background]**. Confidence high.
|
||||
|
||||
### Piece 2: testkit and ports
|
||||
|
||||
- **Owner:** `thing-testkit` owns fakes; `thing-core` owns the port *traits*
|
||||
(a trait is the narrow port; the fake is a test crate's business).
|
||||
- **In/Out:** `StatePort { read(schema,key)->Option<Entry>, list(schema,prefix),
|
||||
subscribe(schema,key|prefix) -> Stream<Change> }` where `Change` is
|
||||
`Upsert(entry)|Delete(key)`; `FontPort { resolve(family, weight, style) ->
|
||||
FontHandle, bytes(handle) }`; `Clock`; `IdSource`; `HostEffects`. All object-safe.
|
||||
- **Fakes:** `MemState` (a `CultCache`-backed map with a scripted change feed and
|
||||
a record of every call), `FixedFonts` (one bundled test font, so Parley output is
|
||||
deterministic across machines), `ManualClock`, `SeqIds`.
|
||||
- **Invariants:** `StatePort` has no write method (M7, checked by the trait's
|
||||
method list in a test that enumerates it and by a grep over `thing-core`);
|
||||
`MemState` returns entries only as decoded contract records, never `Value`.
|
||||
- **Failure:** a decode refusal from the port surfaces as a `Change::Refused`
|
||||
carrying the code, never as a missing record (a refusal must not look like
|
||||
absence).
|
||||
- **Test:** unit. `MemState` change ordering; fixture loader round-trips all twelve
|
||||
fixtures through the contract (a fixture that fails the contract is a bug in the
|
||||
fixture before it is a bug in core).
|
||||
- **Deletes/forbidden:** no global state, no `Instant::now()` in core (negative
|
||||
grep).
|
||||
- **Prior art:** the port style is the project's own doctrine; Xilem's
|
||||
`masonry_testing` and egui's `egui_kittest` are test-harness crates beside a core
|
||||
(R-K §1) **[read]**. Confidence high.
|
||||
|
||||
### Piece 3: resolve
|
||||
|
||||
- **Owner:** `thing-core::resolve`. It is the only code that interprets Thing
|
||||
Script structure.
|
||||
- **In:** `SurfaceDocument`, a `CompositionStore` (std compiled in, plus publisher
|
||||
compositions read through `StatePort`). **Out:** `ResolvedTree` (arena of
|
||||
nodes, unique ids, every `Use` expanded or replaced by a placeholder node, every
|
||||
`CommandRef` checked against the document's `CommandDecl`s, bindings still
|
||||
unresolved), plus diagnostics.
|
||||
- **Invariants:** M4 (unknown kind, undeclared `CommandRef`, duplicate id after
|
||||
expansion refuse the document *whole*, and the old document stays displayed);
|
||||
M5 (unresolved `Use` is a visible placeholder with a diagnostic); expansion is
|
||||
substitution only (no conditionals, no arithmetic: a test that every
|
||||
`thing.std` composition expands under a parameter-less evaluator); expansion
|
||||
terminates (recursion in compositions is refused with a cycle diagnostic and a
|
||||
depth bound); expansion is deterministic and order-preserving; ids of expanded
|
||||
nodes are derived from the `Use` node id and the template id by one scheme, so
|
||||
they are stable across republishes (focus and scroll state key on them).
|
||||
- **Failure:** a referenced composition arrives later → the placeholder is
|
||||
replaced next frame without a republish.
|
||||
- **Test:** unit per rule on `f09`, `f10`; property (`resolve` total, ids unique or
|
||||
refusal); pipeline smoke: `resolve` output fed to `layout` (cut 5) in `f01`.
|
||||
Mutants that must die: skip the duplicate-id check; treat an undeclared
|
||||
`CommandRef` as a warning; drop the placeholder; expand `Use` without pinning the
|
||||
composition version.
|
||||
- **Deletes/forbidden:** no `kind` string dispatch anywhere (closed enum only); no
|
||||
fallback along a kind path (M4); no expression evaluator.
|
||||
- **Prior art:** Adaptive Cards separates template expansion from render
|
||||
(`adaptivecards-templating` expands a template against data before render)
|
||||
**[read, R-K §2]**; Thing's `Use` is the same split without an expression
|
||||
language, which R-K §2/P5 shows the failure of (the DSL's renderer-local `derive`)
|
||||
**[read]**. Slint compiles a text language to a typed tree at build/run time
|
||||
**[read, R-K §2]**. Confidence high on substitution-only; medium on id-derivation
|
||||
(no source read; it is the same problem React solves with keys **[background]**).
|
||||
|
||||
### Piece 4: bind
|
||||
|
||||
- **Owner:** `thing-core::bind`. Only code that reads typed state.
|
||||
- **In:** `ResolvedTree`, `StatePort`. **Out:** `BoundTree` with every `Bind`
|
||||
replaced by a typed value or a binding-error value; `Repeat` expanded to one
|
||||
subtree per item, keyed by the item key; subscriptions established and released
|
||||
as bindings appear and disappear.
|
||||
- **Invariants:** M6 (different schema or missing member renders a typed error,
|
||||
never a coerced value); M9 (a version at or below the held one is ignored, and
|
||||
the ignore is a `StaleVersionIgnored` diagnostic at most once per document
|
||||
version, not per frame); a value change never triggers layout of anything but the
|
||||
nodes whose bound values changed (dirty tracking is observable through a counter
|
||||
the test reads); `Format` is total and locale-free in core (formats are the
|
||||
closed set; any locale is a host input, see O8); `Repeat` item keys must be
|
||||
unique, a duplicate key refuses the document at bind time as M4 does; keyed
|
||||
reorder preserves node identity, so focus and scroll survive a reorder.
|
||||
- **Subscription rule:** core subscribes to exactly the `(schema,key)` pairs and
|
||||
prefixes the live tree references; a document replacement diffs its subscription
|
||||
set. An unsubscribed record's arrival changes nothing.
|
||||
- **Failure:** record absent → a typed `Absent` value rendered as the node's
|
||||
declared empty text (the contract field), not an error; schema mismatch → error.
|
||||
- **Test:** unit per rule on `f01`, `f06`, `f10` with scripted `MemState` feeds;
|
||||
pipeline smoke `bind → layout` checks dirty-node count; mutants: coerce a
|
||||
mismatched schema; drop the version gate; resubscribe every frame; reorder by
|
||||
index instead of key.
|
||||
- **Deletes/forbidden:** no local `var`/`derive`/`collection` store (P5); no draft
|
||||
binding (BD2: editing state is `input`'s).
|
||||
- **Prior art:** BD1-BD9 collapse in the map **[read, map]**; keyed list
|
||||
reconciliation is React's `key` and Elm's `Html.Keyed` **[background]**;
|
||||
Clay retains element state by ID and requires `CLAY_IDI` for loops (R-F §2.1)
|
||||
**[read]**, the same constraint. Flutter's retained tree keeps state by key
|
||||
**[background]**. Confidence high.
|
||||
|
||||
### Piece 5: layout
|
||||
|
||||
- **Owner:** `thing-core::layout`. The only crate-user of `taffy`.
|
||||
- **In:** `BoundTree`, viewport, metrics mode, `FontPort`. **Out:** `LaidTree`:
|
||||
per node a content box, scroll extents, clip, and absolute-positioned overlay
|
||||
boxes.
|
||||
- **Contract:** the mapping from contract `BoxStyle` to Taffy style is one
|
||||
function with an exhaustive match (a new contract field fails to compile until it
|
||||
is mapped). Leaf measure for `Text`, `TextInput`, `Image`, `Embed`, `Plot`
|
||||
goes through one `Measure` trait: Pixels → Parley (piece 9), Cells → cell metrics,
|
||||
`Image`/`Embed`/`Plot` → declared sizes. Overlay nodes are laid out as a
|
||||
separate root attached to an anchor node's final rectangle or the viewport.
|
||||
Scroll containers expose `content size` and clamp offsets that `input` owns.
|
||||
- **Invariants:** layout is a pure function of (tree, viewport, scale, metrics,
|
||||
fonts): same inputs, byte-identical geometry (M10); a style field with a literal
|
||||
and a token resolves via the one theme resolver before reaching Taffy; `fr`,
|
||||
`%`, `auto` and `content` sizes reach Taffy as the matching Taffy dimensions
|
||||
with no rewriting; an `Embed` is a leaf of exactly its declared size.
|
||||
- **Failure:** NaN or negative sizes from a document are refused at resolve (the
|
||||
contract already refuses at decode for `Count`); layout never panics on a
|
||||
constraint it cannot satisfy and instead overflows per CSS rules.
|
||||
- **Test:** unit geometry goldens on `f01`, `f03`, `f05`, `f06`, `f08`; the
|
||||
raw-Taffy rederivation on one small tree (§2.4); property: layout is
|
||||
deterministic across repeats and scale 1 vs 2 scales positions linearly when
|
||||
fonts are scale-independent (fixed font); Cells mode: `f12` widths equal
|
||||
`unicode-width` expectations. Mutants: swap row/column mapping; drop `gap`;
|
||||
round positions; ignore `position: absolute` insets.
|
||||
- **Deletes/forbidden:** `taffy` appears in no other crate's tree (M8, `cargo tree
|
||||
-i taffy` negative check on `thing-tui`, `thing-web`, `thing-mesh`, contract).
|
||||
- **Prior art:** Taffy implements Block, Flexbox and Grid, is used by Servo, Blitz,
|
||||
Bevy, Slint, iocraft, Zed/GPUI and is validated against Chrome (R-F §2.2) **[read]**;
|
||||
measure-function leaf text is the documented integration point (R-F §2.2, R-T §1)
|
||||
**[read]**; iocraft's measure for Cells (R-T §5) **[read]**. Ruling
|
||||
`thing:ruling:thing2-one-solver-taffy`. Pin: Taffy 0.14.0 (2026-08-24 [read]);
|
||||
Slint pins 0.10 flexbox-only and iocraft 0.5.2 (R-T §1, §5), so the Grid feature
|
||||
surface at 0.14 is the one to prove first (confidence medium).
|
||||
|
||||
### Piece 6: render and goldens
|
||||
|
||||
- **Owner:** `thing-core::render`. Turns `LaidTree` + paint + input state into the
|
||||
command list; draws selection, caret, focus ring, scrollbars.
|
||||
- **Invariants:** M10 (byte-identical command list for equal inputs); painter
|
||||
order is document order, then overlays last; `ClipPush`/`ClipPop` balance (a
|
||||
validator over every golden); every `Embed` and `Image` node produces exactly
|
||||
one command or a visible missing-asset placeholder (diagnostic `AssetMissing`
|
||||
added to D3's list when this cut lands, O9); nothing outside the clip stack is
|
||||
drawn for `overflow: clip|scroll` nodes.
|
||||
- **Backends must prove consumption (M10 second half):** each backend's parity
|
||||
test replays a golden list and asserts one draw call per command; a backend that
|
||||
skips a command variant fails by name.
|
||||
- **Test:** golden suite described in §2.4 over all fixtures; pipeline smoke
|
||||
`resolve→bind→layout→render` on `f01`; mutants: drop `ClipPop`, reorder overlay,
|
||||
emit caret when unfocused, emit a command with a theme token not a literal.
|
||||
- **Prior art:** Clay's sorted command array and scissor start/end (R-F §2.1)
|
||||
**[read]**; egui/epaint clipped shapes to thin backends (R-F §2.4) **[read,
|
||||
background]**. Confidence high on structure, medium on exact variants (O3).
|
||||
|
||||
### Piece 7: commands (dispatcher and result validation)
|
||||
|
||||
- **Owner:** `thing-core::command`. Mints invocations, tracks them, validates
|
||||
results. The only producer of `FrameOutput.outbound`.
|
||||
- **Lifecycle (D5):** `Issued → Pending* → Completed | Denied | Failed`, with two
|
||||
core-only terminal states, `Malformed` (result failed to decode, or named an
|
||||
unknown invocation) and `TimedOut`. Terminal states are final: a later result
|
||||
for a terminal invocation is a diagnostic, not a state change.
|
||||
- **In:** input events that activate a `CommandRef` node; results arriving as
|
||||
`StatePort` changes of schema `gamecult.thing.command_result.v1`.
|
||||
**Out:** `CommandInvocation` (id from `IdSource`, `captured` from `input`'s editing
|
||||
state for the nodes `CommandDecl.captures` lists, `surface` = key + held version,
|
||||
`issued_at` from the clock; **no caller identity field written**, the transport
|
||||
sets it), UI state for the issuing node (busy, denied-at-node), effects through
|
||||
`HostEffects`.
|
||||
- **Invariants:** M2 (a result that fails to decode, or an unknown `invocation_id`,
|
||||
becomes a visible `Malformed` state for that invocation and a log line; no
|
||||
invocation stays pending past its timeout without one); M7 (core writes nothing
|
||||
but invocations); idempotency: one user action mints exactly one id, and a
|
||||
re-delivered invocation carries the same id; `Completed{clear_drafts:true}`
|
||||
clears exactly the captured nodes' drafts and no others; `OpenExternal{resume}`
|
||||
is executed once per result `seq`; a `Denied{node}` marks that node and no
|
||||
other; a double-click on a busy Button mints no second invocation while the first
|
||||
is non-terminal (the Button node's enabled state is derived from lifecycle, not a
|
||||
flag a document sets).
|
||||
- **Timeout:** a core config value, default 15 s in `Config` (O5: it is a guess).
|
||||
- **Failure:** `StatePort` refusal on a result (piece 2's `Change::Refused`) maps to
|
||||
`Malformed`, which is the case P2 shows Thing 1 lacked (result rejected whole
|
||||
by `ajv` with nothing visible).
|
||||
- **Test:** unit lifecycle table (every transition, every illegal one), driven
|
||||
with `ManualClock` for timeouts; pipeline smoke with `MemState`: press a Button in
|
||||
`f02`, assert exactly one outbound invocation with the right captures, feed
|
||||
`Pending` then `Completed{OpenExternal}`, assert one host effect; negative:
|
||||
result with extra field (M1) never reaches the lifecycle. Mutants: no timeout;
|
||||
mint id per frame; `clear_drafts` clears all; accept results after terminal;
|
||||
write caller identity.
|
||||
- **Prior art:** idempotency keys for commands and retries (Stripe API
|
||||
idempotency, RFC draft "Idempotency-Key" header) **[background]**; command/result
|
||||
split with a pending phase (job/operation resources in Google AIP-151 long-running
|
||||
operations) **[background]**; Flutter's `TextInputConnection` shows what drafts the
|
||||
host must hold (R-T §4) **[read]**. Evidence of the failure it prevents: P2, P7
|
||||
**[read, map]**. Confidence medium-high; the `Malformed` rule follows directly
|
||||
from P2.
|
||||
|
||||
### Piece 8: TUI backend (`thing-tui`)
|
||||
|
||||
- **Owner:** `thing-tui`: terminal drawing, keyboard and mouse into `InputEvent`.
|
||||
First end-to-end consumer of core; also the agents' reader.
|
||||
- **In:** `FrameOutput.commands` in Cells mode. **Out:** terminal bytes via
|
||||
`crossterm`; `InputEvent`s.
|
||||
- **Invariants:** M8 (no `taffy`/`parley` in the tree); every `RenderCommand`
|
||||
variant is handled and each is either drawn or explicitly declared
|
||||
not-representable in cells (`Image` → block placeholder with alt text,
|
||||
`Polyline` → braille or block approximation): "not representable" is a table in
|
||||
the crate, tested; wide and zero-width characters advance the cursor as the
|
||||
`Cells` run says (the backend never re-measures); the screen diff writes only
|
||||
changed cells (testable on a `Vec<Cell>` buffer without a terminal).
|
||||
- **Test:** unit on a virtual screen (`ratatui`-style test buffer is
|
||||
**[background]**; ours is a `Vec<Cell>`): every golden command list renders to a
|
||||
stored screen dump; pipeline smoke: `f01` through the whole of cuts 3-7 to a
|
||||
screen dump; parity (M10): every command variant produces a cell effect.
|
||||
Mutants: skip `ClipPop`; mis-advance on a wide char; draw a command outside the
|
||||
clip; ignore `Text` colour.
|
||||
- **Forbidden:** layout of any kind.
|
||||
- **Prior art:** Clay has `termbox2` and terminal renderers as thin command
|
||||
consumers (R-F §2.1) **[read]**; iocraft is a Taffy-based TUI (R-T §5) **[read]**;
|
||||
ratatui uses Cassowary not Taffy and pins `unicode-width >= 0.2.0` (R-T §5)
|
||||
**[read]**, a version issue to carry into our own pin (confidence medium: width
|
||||
tables differ between 0.1.x and 0.2.x, so the cell widths in goldens must be
|
||||
pinned to a crate version, D6).
|
||||
|
||||
### Piece 9: text (9a measure+shape, 9b focus+editing+IME state)
|
||||
|
||||
- **Owner:** `thing-core::text` (ruling `thing2-thing-owns-text`).
|
||||
- **9a In/Out:** string + `TextStyle` + width constraint → `TextLayout` with
|
||||
line boxes and glyph runs (Pixels, via Parley) or cell lines (Cells). One
|
||||
`Measure` implementation per metrics mode behind the layout `Measure` trait.
|
||||
- **9b In/Out:** `InputEvent`s targeted by hit-test/focus → editing state per
|
||||
`TextInput` node (value, selection, composition range), caret rectangle,
|
||||
`ImeRequest { rect, text_window, selection }` (the data a hidden host element
|
||||
needs), and edits fed to `command` as captured values.
|
||||
- **Focus (D7):** one focus owner in core. Tab order is document order of
|
||||
focusable nodes, overlays trap focus while modal, focus is keyed by node id and
|
||||
survives republish when the id survives, and a focused node removed moves focus
|
||||
to its previous sibling then parent. A backend never owns focus; it forwards host
|
||||
focus loss/gain only.
|
||||
- **Invariants (9a):** shaping and bidi never run in a backend (M8); a run's glyph
|
||||
positions are a pure function of (text, style, width, font bytes); wrap and
|
||||
`max_lines` are applied by core and appear in the command list.
|
||||
- **Invariants (9b):** the draft is core's state and dies with the session (model
|
||||
page row 5); composition text is displayed but not committed or captured until
|
||||
the host commits; an IME update never rewrites committed text; every selection
|
||||
edit is on grapheme-cluster boundaries (`unicode-segmentation`); `ImeRequest`
|
||||
windows the text rather than mirroring a whole large value, and coalesces to one
|
||||
per frame (R-T §2: GPUI's mirror coalesces writes because each write restarts the
|
||||
IME connection **[read]**).
|
||||
- **Test:** 9a: unit on `f12` (bidi order, CJK width, combining marks, ZWJ
|
||||
emoji clusters, wrap at several widths) with the bundled font; golden glyph
|
||||
runs. 9b: scripted event sequences (type, move, select, delete across a
|
||||
cluster, compose-update-commit, compose-cancel, undo if D8 includes it) assert
|
||||
value/selection/`ImeRequest` at each step; focus: traversal order on `f04`,
|
||||
trap on `f05`, removal on a republish. Mutants: byte-index deletion in a
|
||||
cluster; commit composition on update; focus not keyed by id; mirror whole value.
|
||||
- **Prior art:** Parley: rich-text layout with HarfRust shaping, ICU4X bidi and
|
||||
segmentation, `PlainEditor`, IME primitives, an AccessKit editor example
|
||||
(R-T §1) **[read]**; Bevy (taffy 0.14 + parley 0.11), Slint (`shared-parley`),
|
||||
Blitz (Stylo + Taffy + Parley, text input state in the DOM node), Masonry (R-T §1-2)
|
||||
**[read]**: four independent Taffy+Parley integrations. Cosmic-text is the
|
||||
alternative; Parley wins on the integrations above and AccessKit ties, not on
|
||||
measured quality (R-T §1; no benchmark read). IME-restart and mirror behaviour
|
||||
from GPUI web, egui `text_agent`, Flutter web `text_editing.dart` (R-T §2)
|
||||
**[read]**. Confidence medium: all of this is an assembly of read-but-not-built
|
||||
parts; the biggest risk is Parley under `wasm32` and `no_std`-adjacent builds
|
||||
(not established; O1). Slug/Vello GPU glyphs (R-T §6) is the operator's scope
|
||||
addition and is **out of this build order**: glyphs reach the backend as
|
||||
positioned runs, and how the web backend rasterizes them (canvas 2D first,
|
||||
GPU later) is O2.
|
||||
|
||||
### Piece 10: a11y tree
|
||||
|
||||
- **Owner:** `thing-core::a11y`. Derives an AccessKit `TreeUpdate` per frame from
|
||||
the resolved tree, roles, labels, focus, text editing state and layout.
|
||||
- **Invariants:** every focusable node has a role and an accessible name (a
|
||||
document that offers none gets the diagnostic `NoAccessibleName`, O9); roles come
|
||||
from the contract's closed `Role` set mapped by one exhaustive match; bounds equal
|
||||
layout bounds; updates are incremental (unchanged nodes omitted), keyed by node
|
||||
id; an `Embed` is exposed as a generic node with its label; text input exposes
|
||||
value, selection and composition as AccessKit text-run data.
|
||||
- **Test:** unit: tree for `f01`, `f04`, `f05` compared to stored AccessKit
|
||||
dumps; incremental update test (change one bound value → exactly the changed
|
||||
node plus ancestors needed); mutants: drop `focus`; stale bounds after scroll;
|
||||
role map swap. An actual screen reader cannot be run in CI; it is an operator
|
||||
check at web cutover (O6).
|
||||
- **Prior art:** AccessKit as the schema, with Bevy, Blitz, Masonry, Slint, GPUI
|
||||
and egui integrating it (R-T §3) **[read]**; Blitz's `build_accessibility_tree`
|
||||
tests (`accessibility_roles.rs`) are the closest test style **[read]**. Web
|
||||
adapter: **planned only** in AccessKit (R-F §2.6, R-T §3), so the web backend
|
||||
mirrors the tree to DOM itself (piece 11b); Flutter web is the shipped precedent,
|
||||
and its open issues (semantics DOM stealing taps, R-T §3) are the failure
|
||||
catalogue for 11b. Confidence high on native, medium on web.
|
||||
|
||||
### Piece 11: web backend and shell (`thing-web`)
|
||||
|
||||
- **Owner:** `thing-web` (wasm entry + a small JS host package). Replaces the
|
||||
Nightwing browser reference (`nightwing-eve-browser-reference`, P8).
|
||||
- **11a draw:** core compiled to wasm, `frame` called from `requestAnimationFrame`
|
||||
with batched events; commands drawn to one canvas 2D context (D9: canvas 2D
|
||||
first, because GPUI web already rasterizes through OffscreenCanvas 2D, R-T §2,
|
||||
and the contract is already glyph-run based so a GPU path swaps in under the same
|
||||
commands). The JS host holds **no layout, no text shaping, no focus state**.
|
||||
- **11b IME + a11y:** a hidden `<textarea>` placed from `ImeRequest` (the pattern
|
||||
all three of egui, GPUI and Flutter web converge on, R-T §2), mirrored text
|
||||
windowed and coalesced; a DOM accessibility mirror of the AccessKit update,
|
||||
clipped so it cannot intercept pointer events (the exact Flutter failures in R-T
|
||||
§3: #194011, #192466), off by default until an assistive-technology signal or an
|
||||
explicit toggle, as Flutter does.
|
||||
- **11c shell + discovery:** a web app that discovers surfaces through the Verse's
|
||||
discovery owner (map: discovery is not Thing's, `advertisement-owner`) and mounts
|
||||
core; reachable by `thing-mesh`'s transport or a gateway (O4).
|
||||
- **Invariants:** M8 (no `taffy`/`parley` in the JS package or the wasm host
|
||||
glue); the JS host calls only `Core::frame`, passes only the closed
|
||||
`InputEvent`s, and draws only the closed commands; the hidden textarea never
|
||||
scrolls the page into view (R-T §2: egui's park-over-canvas) and never receives
|
||||
pointer events; one frame's work for `f06` (1k items) stays under a stated budget
|
||||
(O10: a number is owed, none was measured).
|
||||
- **Test:** unit in `wasm-bindgen-test` (headless Chrome and Firefox in the verify
|
||||
image): replay goldens through the canvas and compare against a canvas-drawn
|
||||
reference of the same list (command consumption, M10); IME: scripted composition
|
||||
events at the textarea produce core edits equal to the 9b scripts; a11y: DOM
|
||||
mirror equals the AccessKit tree for `f04`; click-through: a pointer test over a
|
||||
mirror region reaches the canvas target (guards the Flutter failure). Real IME
|
||||
and real screen readers are operator checks (O6).
|
||||
- **Prior art:** R-T §2-3 as above; EditContext is Chrome-only (MDN, R-T §3) so it
|
||||
is not the base path; `egui` web and Flutter web **[read]**; Slint web has no
|
||||
accessibility and is not recommended for general web apps (R-T §2) **[read]** (the
|
||||
failure this piece exists to avoid). Confidence medium: no Taffy+AccessKit
|
||||
web combination exists in the sources read (R-T Gaps), so 11b is first of its
|
||||
kind for this stack.
|
||||
|
||||
### Piece 12: thing-mesh (the CultMesh adapters)
|
||||
|
||||
- **Owner:** `thing-mesh`: `StatePort` and a discovery client over CultMesh/CultNet
|
||||
and CultCache; the outbound invocation sender; the only crate with a transport
|
||||
dependency.
|
||||
- **Invariants:** a `StatePort` never returns `serde_json::Value` or an untyped
|
||||
blob (P3); decode runs through the contract types and refuses on error
|
||||
(`Change::Refused`), never a silently-dropped update; subscription uses the mesh's
|
||||
own mechanism, never a poll loop (BD9 dies); the sender sets the caller identity
|
||||
from the transport session and has no API to override it; decode happens on a
|
||||
thread with the stack CultLib's decode needs until
|
||||
`cultlib-gaps:follow_up:decode-stack-safety` lands (the hazard is in the
|
||||
contract crate's docs, P20).
|
||||
- **Test:** pipeline smoke: a real CultMesh in-process loopback publishes
|
||||
`f01`'s state and a document; core with `thing-mesh` shows it; then a hostile
|
||||
record (unknown arm) yields `Change::Refused`; an invocation sent through the
|
||||
sender arrives at a fake provider with the transport's identity and not any value
|
||||
the core supplied. End-to-end (per Verse contract): later, with Odin's discovery.
|
||||
- **Prior art:** the doctrine itself (CultMesh is the state substrate;
|
||||
`doctrine/gamecult.md`); service registries are not owned by the UI toolkit
|
||||
(R-K §3: Consul, Kubernetes Services **[background]**; Teams manifests vs cards
|
||||
**[background]**). Confidence high on shape, unknown on the CultMesh API surface
|
||||
it must wrap: that API is read when the cut is specced (O4).
|
||||
|
||||
### Piece 13: consumer ports (template)
|
||||
|
||||
One `cut_spec` per consumer, each carrying its rename (T, E1-E7 in
|
||||
`thing-campaign.md`) and deleting its Thing 1 path in the same cut. Order per map
|
||||
cut 5 (Odin, VoidBot, Ghostlight+Heimdall, Muninn/sleipnir, weksa, Stonks, Vili,
|
||||
Gjallar, StreamPixels, gamecult-ops, CultLib), with cutover shape per the
|
||||
`port-cutover` ruling.
|
||||
|
||||
The template (every port cut has the same spec skeleton, so Soul can run the same
|
||||
falsifications):
|
||||
|
||||
1. Fixture first: the consumer's current surface rebuilt as a Thing Script document
|
||||
through its language package, loaded into `thing-testkit`, geometry-golden'd.
|
||||
2. Deletes: its Thing 1 publisher path and every `gamecult.eve.*` reader/writer
|
||||
(exact paths from the census, to `file:line`).
|
||||
3. Commands: each existing command becomes a `CommandDecl` and a handler
|
||||
returning an `Outcome` through the package (no state-as-string, M3).
|
||||
4. Test: the consumer's own suite plus one pipeline smoke that renders the
|
||||
consumer's real surface headlessly through core and asserts the geometry golden
|
||||
and the command round-trip. Operator check: view it in the TUI/web.
|
||||
5. Negative grep: no `gamecult.eve` and no `eve-browser-lowering` imports remain.
|
||||
- **Prior art:** expand/contract (parallel change) and strangler-fig, with the
|
||||
caveat in R-K §4 that Thing 2 kinds are new kinds not renames, so an aggregator
|
||||
reads two kinds, not one under two ids **[read]**. Confidence high on the
|
||||
template, per-consumer specs owe their own census read.
|
||||
|
||||
### Piece 14: retirement
|
||||
|
||||
Delete Thing 1's tree from the kernel repo after the last port. Test: negative
|
||||
greps for `gamecult.eve`, `eve-browser-lowering`, `packages/eve-contracts`,
|
||||
`web/eve-dsl.js`; workspace builds with the directories gone; README front door
|
||||
(`doctrine`: first screen true) rewritten; Idunn targets (P15) re-pointed. Soul
|
||||
pass before the tag.
|
||||
|
||||
## 4. What every cut's Soul pass attacks (derived, not new)
|
||||
|
||||
Rules M1-M10 are the checklist; in addition the shared failure shapes from this
|
||||
spec: (i) a backend that grows layout, text or focus (compare dependency trees and
|
||||
public fns); (ii) a decode refusal that reads as absence; (iii) a rule held by a
|
||||
constructor but skipped by decode (the contract's P18 lesson, now applied to
|
||||
`CommandInvocation` and results received by core); (iv) a golden re-blessed to
|
||||
match a bug (Soul reruns generation on a clean checkout and diffs); (v) an `Embed`
|
||||
or placeholder that silently drops.
|
||||
|
||||
## 5. Decisions made here (summary)
|
||||
|
||||
| ID | Decision | Confidence |
|
||||
| --- | --- | --- |
|
||||
| D1 | One `thing-core` crate with modules; six crates in total | medium-high |
|
||||
| D2 | Nine-variant closed render-command list | medium |
|
||||
| D3 | Closed diagnostics enum drives placeholders, logs, a11y and tests | high |
|
||||
| D4 | Byte-compared binary render and geometry goldens, `bless` task | medium |
|
||||
| D5 | Invocation lifecycle with core-only `Malformed` and `TimedOut` | medium-high |
|
||||
| D6 | Cell widths pinned to a `unicode-width` version in goldens | medium |
|
||||
| D7 | One focus owner in core, id-keyed, modal trap | medium-high |
|
||||
| D8 | (see O7) edit history scope | open |
|
||||
| D9 | Web draws through canvas 2D first | medium |
|
||||
|
||||
## 6. Open: what this spec could not decide
|
||||
|
||||
Each with a recommendation; none blocks cuts 1-8.
|
||||
|
||||
- **O1. Parley (and AccessKit types) on `wasm32`.** No source read establishes
|
||||
that Parley + fontique build and perform for wasm; GPUI web has its own text
|
||||
system and Slint web uses femtovg, not Parley. Recommendation: one half-day
|
||||
probe (build `thing-core` text module to wasm, shape `f12`) before cut 9a is
|
||||
specced; the fallback is browser `Canvas.measureText` behind the `Measure` trait,
|
||||
which weakens "core owns text" for the web only and needs an operator ruling if
|
||||
taken. Needs: the probe.
|
||||
- **O2. Glyph rasterization path** (canvas 2D, Slug, Vello family). Operator scope
|
||||
addition in R-T §6. Recommendation: canvas 2D now; revisit after a measured
|
||||
frame budget (O10). Needs: operator word on whether GPU glyphs gate web
|
||||
cutover.
|
||||
- **O3. The exact command list**, in particular whether `Plot` lowers to
|
||||
`Polyline`/`Polygon` in core or becomes an `Embed`. Recommendation: lower in core
|
||||
(consumers expect charts; an `Embed` makes every host write one). Needs: review
|
||||
of D2 by Meta.
|
||||
- **O4. CultMesh/CultNet surface for `thing-mesh` and how a browser reaches it**
|
||||
(direct WebTransport/QUIC, or a gateway). Not read in this pass. Needs: a short
|
||||
Eyes pass over `cultmesh-ts`/`cultnet-ts` in CultLib before cut 12 and 11c are
|
||||
specced.
|
||||
- **O5. Defaults that are guesses:** invocation timeout 15 s, frame budget, scrollbar
|
||||
style, overlay z policy. Recommendation: land as `Config` with these defaults and
|
||||
record a follow-up to measure.
|
||||
- **O6. What only the operator can check:** real IME on Windows/macOS/iOS/Android
|
||||
browsers, a real screen reader on the web mirror. No CI substitute exists; the
|
||||
spec names them as operator checks at cut 11 and at each consumer cutover.
|
||||
- **O7. Editing scope (D8):** undo/redo, clipboard, drag-select auto-scroll in 9b.
|
||||
Recommendation: undo and clipboard are in (a general library's reasonable
|
||||
expectation; clipboard goes through `HostEffects`), word/line navigation in,
|
||||
rich text out. Needs: nothing but a go.
|
||||
- **O8. Locale in `Format`.** Recommendation: a host-supplied `Locale` input,
|
||||
default `en`, formats stay a closed set.
|
||||
- **O9. Diagnostics added by later cuts** (`AssetMissing`, `NoAccessibleName`)
|
||||
extend D3's list; confirm the list stays closed and versioned with core.
|
||||
- **O10. Performance budgets** (frame time at 1k and 10k nodes, wasm size). No
|
||||
number was measured anywhere in the sources read; Clay's "microsecond layout"
|
||||
claim has no table (R-F §2.1) and the Taffy README numbers are layout-only
|
||||
(R-F §2.2). Recommendation: cut 6 records the first measured numbers in a
|
||||
bench fixture, and budgets are set from them rather than guessed.
|
||||
- **O11. Thing Script text syntax.** `thing2:ruling:thing-script-form` decides it;
|
||||
that ruling is not in this mind, so this spec assumes the typed document is the
|
||||
authority under every option, as the map does. If a text form lands, it is a
|
||||
compiler to the typed document (cut after 3), tested by round-tripping through
|
||||
`resolve`.
|
||||
- **O12. Meta's rule.** This spec reads "prior-art sources cited" as: every
|
||||
decision names the sources and a confidence, with [read]/[background] honesty.
|
||||
If the rule also fixes a format, a minimum source count or a required venue,
|
||||
the tags above are where to retrofit it.
|
||||
- **O13. Where the repo's unmerged contract-ts / contract-cs branches stand.** The
|
||||
branches exist on the forge; this spec assumes the map's description of them
|
||||
and did not re-verify their state beyond the branch heads.
|
||||
|
||||
## 7. Substrate gaps felt writing this spec
|
||||
|
||||
- The mind this run could read has none of `thing2:*` (rulings, specs, questions);
|
||||
the spec cites them through the map's prose only. Requirement: the `huginn`
|
||||
view of the `thing2` campaign for any Mind that is asked to work it.
|
||||
- No published golden-command suite exists to copy (D4); the harness is ours to
|
||||
build and is itself a cut-1 testkit deliverable.
|
||||
|
|
@ -473,6 +473,10 @@ code fails it.
|
|||
|
||||
## Cut order (deliverables)
|
||||
|
||||
Per-piece contracts, the build graph with sizes, a test per piece and the prior-art
|
||||
citations for each choice are in `docs/thing2-build-spec.md`; this list is the
|
||||
summary.
|
||||
|
||||
0. **`stale-voidbot-cleanup`** (Eve main, any time): spec
|
||||
`thing2:cut_spec:cut-stale-voidbot-cleanup.r1`. Owned here by
|
||||
`thing:follow_up:stale-voidbot-cleanup-on-main`.
|
||||
|
|
|
|||
Loading…
Reference in a new issue