docs(framing): the panel cell-mapping generation (v25) --- SS5b revision 14
Own branch, own slice, protocol-bearing, runs alone. Framing only; no implementation. Blocks `panel-pointer-replay`, which blocks GUI arc 1b. Answers review of revision 13. Every item below reverses or completes something 13 got wrong. **GATING IS REFUSAL, NOT FALLBACK.** Revision 13 said a bare `PanelPointer` from a new peer would be "handled under the old semantics". That is a BYPASS: it leaves the exact hole this slice exists to close, reachable by omitting a field. A >= v25 session sending the legacy event is REFUSED before mutation, and a >= v25 frontend REJECTS a legacy `Present` rather than painting a band it cannot safely hit-test. Only negotiated <= v24 keeps legacy semantics; `Absent` stays common to both families. **ONE AUTHORITATIVE PER-FRONTEND KEY**, used by projection AND inbound validation, advanced after any mapping mutation and BEFORE the next inbound pointer is handled --- whether or not anything has rendered. Comparing against the last EMITTED frame recreates the hole, because a mutation not yet painted has still changed the inverse mapping. **STALE TAILS TERMINATE; THEY DO NOT VANISH.** A blanket drop breaks liveness: a refused `Up` leaves an empty document selection armed with a stale anchor, and leaves a reporting terminal child HOLDING A BUTTON FOREVER. Cancellation is now a ruled outcome --- producer latch reset, daemon selection and click-chain cleanup, and the child's release delivered at the last coordinate known good. A cancelled gesture is explicitly not a replayed one: the release is for liveness, and no selection or scroll effect is applied from the stale event. Stale BEGINNINGS may still simply drop. **THE DOMAIN WAS INCOMPLETE.** `view_left` is added, because 1b makes horizontal scrolling real. "Cursor movement is stable" is now CONDITIONAL: a cursor move that triggers vertical or horizontal follow changes `view_top` or `view_left` and therefore does change the mapping. Terminal panels are ruled explicitly --- their coordinates are decided by the SCREEN, so output and scrollback movement change the generation while their buffer revision does not. **SS5b HAD NO ACCEPTANCE MATRIX.** G1-G11 now cover the foreign edit before render, every changing and stable domain entry ROW BY ROW, a selection repaint that must preserve the generation and let a drag continue, mid-gesture cancellation, v24 and v25 positive controls with both wrong-family refusals, identical cells across a generation change still emitting, atomic retention of frame and generation on an invalid frame, and fail-closed exhaustion. The per-entry enumeration is deliberate: one aggregate row cannot show WHICH input moved the key, and a key ignoring `view_left` passes every vertical-only row. **MAPPED MOTION KEEPS ITS COALESCING TAGS.** A new variant falling through to the lossless default would put pixel-rate `Move`/`Drag` on a bounded queue. **PINS ACCUMULATE.** Revision 13 said the pin "moves", which would delete coverage of the shape it protects. `PanelPointer` is retained; exact `TextInput` bytes are added as the previous-final `FrontendEvent`; the complete nested `PanelFrame(Absent)` bytes are added as the previous-final `PanelFramePayload`. Recorded honestly: I could find NO exact-bytes pin for `PanelPointer` anywhere in the tree, though `pmacs-protocol/src/message.rs:524` says one is in the tests. Either my search missed it or the doc overclaims; this slice resolves it either way, since it must add exact pins regardless. **AND THIS SLICE OWNS THE VERSION CORRECTION.** `docs/gui-stage1-input-framing.md` now says 1e's `OpenTarget` is **v26**, with the reason stated at the top. An expected rebase conflict on `gui-stage1b-pointer-scroll` is not grounds for leaving the canonical document false --- which is what I argued last round, and it was wrong. `ADVERTISED_PROTOCOL_VERSION` stays pinned at 20. Gates: all ELEVEN green under `env -u TMPDIR`, with `--protocol` (`build-crdt`, `sweep-crdt`), log 20260814T162843Z. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016bqGA6s9tTUFzYpbeW3tai
This commit is contained in:
parent
5f2015c26f
commit
ca816a1917
|
|
@ -270,6 +270,65 @@ hazard in a shape that looks committed. **A documented error message
|
||||||
that never appears is worse than no documentation**, because the reader
|
that never appears is worse than no documentation**, because the reader
|
||||||
waits for a signal that is not coming.
|
waits for a signal that is not coming.
|
||||||
|
|
||||||
|
## Panel cell-mapping generation (v25) — ACTIVE, framing only
|
||||||
|
|
||||||
|
**Written with the branch's FIRST commit**, per the standing correction
|
||||||
|
from #171 and #215.
|
||||||
|
|
||||||
|
- **Branch `panel-mapping-generation`**, base `githubsucks/main` @
|
||||||
|
**`72da24a`**, worktree
|
||||||
|
`/home/jeans/Repos/personal/pmacs-mapping-gen`.
|
||||||
|
**`githubsucks/panel-mapping-generation` is the authoritative tip.**
|
||||||
|
Recover with `git fetch githubsucks && git checkout
|
||||||
|
panel-mapping-generation`.
|
||||||
|
- **No PR yet. Checkpoint: framing revision 14 (§5b) AWAITING
|
||||||
|
APPROVAL; NO IMPLEMENTATION.**
|
||||||
|
- **PROTOCOL-BEARING — v25, and it runs alone.**
|
||||||
|
`ADVERTISED_PROTOCOL_VERSION` stays pinned at **20**.
|
||||||
|
- **Why it exists.** A `PanelPointer` names a cell and nothing on the
|
||||||
|
wire says which inverse mapping the frontend saw, so the daemon
|
||||||
|
inverts against whatever is current. `buffer_id` catches
|
||||||
|
replacement, `panel_epoch` close/reopen, `geometry_epoch` a
|
||||||
|
declaration race — **nothing catches "the text under that cell
|
||||||
|
changed"**. Q#BP-R3 first accepted this as narrow; that was
|
||||||
|
**overruled**, because a **foreign** edit moves the mapping with
|
||||||
|
`view_top` untouched, the error is unbounded, and the window lasts
|
||||||
|
until the frontend **presents** the new frame.
|
||||||
|
- **A generation, not a per-frame token.** A token moving with the
|
||||||
|
frame would cancel a drag on the next repaint — the mistake
|
||||||
|
`panel_epoch` is stable to avoid. The key moves with the inverse
|
||||||
|
mapping (`view_top`, **`view_left`**, grid size, folds, wrap/gutter
|
||||||
|
geometry, buffer content, terminal output/scrollback) and holds
|
||||||
|
across focus, styling, selection-only repaints and cursor motion
|
||||||
|
**that the follow rules absorb**.
|
||||||
|
- **One authoritative per-frontend key**, used by projection AND
|
||||||
|
inbound validation, advanced after any mapping mutation and
|
||||||
|
**before** the next inbound pointer — comparing against the last
|
||||||
|
emitted frame recreates the hole.
|
||||||
|
- **Gating is REFUSAL, not fallback.** A ≥ v25 session sending bare
|
||||||
|
`PanelPointer` is refused; a ≥ v25 frontend rejects legacy
|
||||||
|
`Present`. Only ≤ v24 sessions keep legacy semantics; `Absent` is
|
||||||
|
common.
|
||||||
|
- **Stale tails TERMINATE, they do not vanish.** A dropped `Up` leaves
|
||||||
|
an empty selection armed and a reporting child holding a button
|
||||||
|
forever, so cancellation is a ruled outcome: producer latch reset,
|
||||||
|
daemon selection/click-chain cleanup, and the child's release
|
||||||
|
delivered.
|
||||||
|
- **Pins ACCUMULATE**: keep `PanelPointer`, add exact `TextInput`
|
||||||
|
bytes (previous-final `FrontendEvent`) and the complete nested
|
||||||
|
`PanelFrame(Absent)` bytes (previous-final `PanelFramePayload`).
|
||||||
|
**No exact-bytes pin for `PanelPointer` could be found in the tree**
|
||||||
|
despite `pmacs-protocol/src/message.rs:524` claiming one; this slice
|
||||||
|
resolves that either way.
|
||||||
|
- **Owns the version correction.** `docs/gui-stage1-input-framing.md`
|
||||||
|
moves 1e's `OpenTarget` to **v26** here — an expected rebase
|
||||||
|
conflict on `gui-stage1b-pointer-scroll` is not grounds for leaving
|
||||||
|
the canonical document false.
|
||||||
|
- **Chain: this slice → `panel-pointer-replay` (rebases onto it) →
|
||||||
|
GUI arc 1b.**
|
||||||
|
- **Gates:** the four `bottom_panel_*` suites, the GUI 1a wire suite,
|
||||||
|
`PMACS_REQUIRE_GPU=1 cargo test -p pmacs-gpu`, and **`--protocol`**.
|
||||||
|
|
||||||
## GPU launcher / probe SIGINT teardown — MERGED as #241 (`f8033bc`)
|
## GPU launcher / probe SIGINT teardown — MERGED as #241 (`f8033bc`)
|
||||||
|
|
||||||
- **MERGED 2026-08-20** at approved head `5089715`, merge commit
|
- **MERGED 2026-08-20** at approved head `5089715`, merge commit
|
||||||
|
|
@ -722,6 +781,7 @@ from #171 and #215.
|
||||||
bite, direct-test diagnosis, unaffected foreground success, mutation,
|
bite, direct-test diagnosis, unaffected foreground success, mutation,
|
||||||
an otherwise unchanged gate, a distinct `error` outcome, and a
|
an otherwise unchanged gate, a distinct `error` outcome, and a
|
||||||
non-Linux-unix statement.
|
non-Linux-unix statement.
|
||||||
|
||||||| parent of 8a4711e (docs(framing): the panel cell-mapping generation (v25) --- SS5b revision 14)
|
||||||
|
|
||||||
## `scripts/gate` TMPDIR isolation — PR #240 OPEN
|
## `scripts/gate` TMPDIR isolation — PR #240 OPEN
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1770,6 +1770,196 @@ cannot preserve the old panel-focused attach leak.
|
||||||
preserving the Stage 1 unknown-value rollback assertions; the Stage 3 PR
|
preserving the Stage 1 unknown-value rollback assertions; the Stage 3 PR
|
||||||
then runs the full gate suite.
|
then runs the full gate suite.
|
||||||
|
|
||||||
|
## 5b. The cell-mapping generation — a protocol slice (Q#BP-R3)
|
||||||
|
|
||||||
|
**Status: revision 14 — AWAITING APPROVAL. Nothing implemented.** This
|
||||||
|
slice **blocks** panel-pointer replay (`panel-pointer-replay`), which
|
||||||
|
blocks GUI arc 1b. It is protocol-bearing and runs alone.
|
||||||
|
|
||||||
|
### What it fixes
|
||||||
|
|
||||||
|
A `PanelPointer` names a **cell**; the daemon must invert that to a
|
||||||
|
**byte**. Nothing on the wire says which inverse mapping the frontend
|
||||||
|
was looking at, so the daemon inverts against whatever is current — and
|
||||||
|
the mapping can move for reasons the clicking frontend neither caused
|
||||||
|
nor can observe.
|
||||||
|
|
||||||
|
This is the one hole in the ladder: `buffer_id` catches replacement,
|
||||||
|
`panel_epoch` catches close/reopen, `geometry_epoch` catches a
|
||||||
|
declaration race, and **nothing catches "the text under that cell
|
||||||
|
changed"**.
|
||||||
|
|
||||||
|
**Revision 12 accepted this as narrow. It is not.** A **foreign** edit
|
||||||
|
moves the mapping with `view_top` untouched; ticks, paging, folds,
|
||||||
|
edits and reloads accumulate without bound; and the window lasts until
|
||||||
|
the frontend **presents** the replacement frame, which a backed-up
|
||||||
|
frontend widens arbitrarily.
|
||||||
|
|
||||||
|
### The generation, and why not a token
|
||||||
|
|
||||||
|
**A per-frame token is the wrong object.** Panels repaint constantly,
|
||||||
|
and a token that moved with the frame would invalidate a live drag on
|
||||||
|
the next repaint — the mistake `panel_epoch` is stable to avoid.
|
||||||
|
|
||||||
|
**`mapping_generation` identifies the INVERSE MAPPING.**
|
||||||
|
|
||||||
|
| changes it | leaves it alone |
|
||||||
|
|---|---|
|
||||||
|
| `view_top` | focus gained or lost |
|
||||||
|
| **`view_left`** — 1b makes horizontal scrolling real | styling, theme, face changes |
|
||||||
|
| panel grid size | selection-only changes |
|
||||||
|
| fold state | a re-emitted identical frame |
|
||||||
|
| wrap mode, gutter geometry | **cursor motion that moves nothing else** |
|
||||||
|
| **buffer content — any edit, from any source** | |
|
||||||
|
| **terminal output or scrollback movement** (terminal panels) | **terminal buffer revision** (see below) |
|
||||||
|
|
||||||
|
**"Cursor movement is stable" is CONDITIONAL, and revision 13 stated it
|
||||||
|
flatly.** A cursor move that triggers vertical or horizontal follow
|
||||||
|
changes `view_top` or `view_left`, and therefore **does** change the
|
||||||
|
generation. The stable case is a cursor move the follow rules absorb.
|
||||||
|
|
||||||
|
**Terminal panels are ruled explicitly.** What a coordinate denotes
|
||||||
|
there is decided by the terminal's **screen**, so output and scrollback
|
||||||
|
movement change the generation. Their **buffer revision** does not — a
|
||||||
|
terminal buffer's revision counter tracks something else, and keying on
|
||||||
|
it would both miss real changes and fire on non-changes.
|
||||||
|
|
||||||
|
**The stability half is load-bearing, not an optimisation.** A drag
|
||||||
|
provokes selection repaints on every motion; a generation that moved
|
||||||
|
with them would cancel the drag after one step.
|
||||||
|
|
||||||
|
### Ownership and refresh timing
|
||||||
|
|
||||||
|
**One authoritative per-frontend mapping key**, owned by the daemon,
|
||||||
|
**used by both projection and inbound validation**. Not two derivations
|
||||||
|
that agree by inspection.
|
||||||
|
|
||||||
|
- It **advances after any mapping mutation and BEFORE the next inbound
|
||||||
|
pointer is handled**, whether or not a frame has been rendered or
|
||||||
|
emitted since.
|
||||||
|
- **Comparing against the last EMITTED frame recreates the hole.** A
|
||||||
|
mutation that has not yet been painted still changes the inverse
|
||||||
|
mapping, and a gesture arriving in that gap must be refused.
|
||||||
|
- Projection stamps the frame with the same key it validates against,
|
||||||
|
so "what the frontend was shown" and "what the daemon checks" cannot
|
||||||
|
drift.
|
||||||
|
|
||||||
|
### Wire shape — appended variants, never widened
|
||||||
|
|
||||||
|
Postcard encodes enums **positionally**, so a shipped variant's field
|
||||||
|
list is frozen. Both messages gain **new variants**:
|
||||||
|
|
||||||
|
- `PanelFramePayload::PresentMapped { .. }` beside `Present`.
|
||||||
|
- `FrontendEvent::PanelPointerMapped { .. }` beside `PanelPointer`.
|
||||||
|
|
||||||
|
`Absent` is unchanged and **common to both families** — hiding a band
|
||||||
|
carries no mapping.
|
||||||
|
|
||||||
|
### Bilateral gating — REFUSAL, not fallback
|
||||||
|
|
||||||
|
Revision 13 said an unmapped event from a new peer is "handled under
|
||||||
|
the old semantics". **That is a bypass**: it leaves the exact hole the
|
||||||
|
slice exists to close, reachable by omitting a field.
|
||||||
|
|
||||||
|
| negotiated | daemon sends | daemon accepts | frontend sends | frontend accepts |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| **≤ v24** | `Present` | `PanelPointer` | `PanelPointer` | `Present` |
|
||||||
|
| **≥ v25** | `PresentMapped` | `PanelPointerMapped` **only** | `PanelPointerMapped` | `PresentMapped` **only** |
|
||||||
|
|
||||||
|
- A **≥ v25 session sending bare `PanelPointer` is REFUSED**, dropped
|
||||||
|
before any mutation, exactly as an out-of-epoch event is.
|
||||||
|
- A **≥ v25 frontend receiving legacy `Present` REJECTS it** rather
|
||||||
|
than painting a band it cannot safely hit-test.
|
||||||
|
- Only a negotiated **≤ v24** session retains legacy semantics.
|
||||||
|
- `Absent` is accepted from either family.
|
||||||
|
|
||||||
|
### Enforcement, and the liveness it must not break
|
||||||
|
|
||||||
|
On `PanelPointerMapped`, the daemon compares the echoed generation with
|
||||||
|
the authoritative key and refuses the gesture before any mutation when
|
||||||
|
they differ.
|
||||||
|
|
||||||
|
**But a blanket drop breaks liveness, and revision 13's did.** A
|
||||||
|
refused **tail** is not the same as a refused **beginning**:
|
||||||
|
|
||||||
|
- **Stale BEGINNINGS may simply drop.** A `Down` that never took effect
|
||||||
|
leaves nothing behind.
|
||||||
|
- **Stale TAILS must TERMINATE the gesture, not vanish.** A dropped
|
||||||
|
`Up` leaves an empty document selection armed with a stale anchor,
|
||||||
|
and leaves a **reporting terminal child holding a button forever**.
|
||||||
|
|
||||||
|
**Cancellation is therefore ruled as a first-class outcome:**
|
||||||
|
|
||||||
|
1. **Producer:** the frontend resets its gesture latch —
|
||||||
|
`pointer_held`, `last_pointer_cell`, `gesture_last_content_cell` —
|
||||||
|
on the same signal, so no further `Drag` is manufactured.
|
||||||
|
2. **Daemon, document:** the panel's selection is cleared if empty, the
|
||||||
|
click chain is cleared, and no cursor move is applied.
|
||||||
|
3. **Daemon, terminal:** the child receives its **release** — a
|
||||||
|
reporting child must not be left holding a button because the
|
||||||
|
mapping moved — and the controller claim is settled.
|
||||||
|
|
||||||
|
**A cancelled gesture is not a replayed one**: the release is delivered
|
||||||
|
for liveness, at the last coordinate known to be valid, and no
|
||||||
|
selection or scroll effect is applied from the stale event.
|
||||||
|
|
||||||
|
### Motion must stay coalesced
|
||||||
|
|
||||||
|
`PanelPointerMapped` carrying `Move` or `Drag` **takes the same
|
||||||
|
tail-coalescing tags as `PanelPointer`** (`pmacs-gpu/src/attach.rs:374`
|
||||||
|
onward). A new variant that fell through to the lossless default would
|
||||||
|
put pixel-rate motion on a bounded queue — the failure the coalescing
|
||||||
|
tags exist to prevent. `Down`/`Up`/wheel/context stay lossless and
|
||||||
|
ordered, as today.
|
||||||
|
|
||||||
|
### Pins ACCUMULATE
|
||||||
|
|
||||||
|
**They are not moved.** Revision 13 said the pin "moves to the previous
|
||||||
|
final variant", which would delete coverage of the shape it was
|
||||||
|
protecting.
|
||||||
|
|
||||||
|
| pin | covers |
|
||||||
|
|---|---|
|
||||||
|
| existing `FrontendEvent::PanelPointer` | retained, unchanged |
|
||||||
|
| **new**: exact `FrontendEvent::TextInput` bytes | the previous-final `FrontendEvent`, appended by 1a and never pinned |
|
||||||
|
| **new**: complete nested bytes of `InstanceMessage::PanelFrame(PanelFramePayload::Absent)` | the previous-final `PanelFramePayload` variant |
|
||||||
|
|
||||||
|
**A note on what exists today.** The panel protocol suite round-trips
|
||||||
|
these shapes and asserts `Absent` differs from an empty `Present`
|
||||||
|
(`tests/bottom_panel_stage2b_protocol_acceptance.rs:196`), but I could
|
||||||
|
find **no exact-bytes pin** for `PanelPointer`, despite
|
||||||
|
`pmacs-protocol/src/message.rs:524` stating one is in the tests. Either
|
||||||
|
it is somewhere my search missed, or the doc overclaims — **this slice
|
||||||
|
resolves it either way**, since it must add exact-byte pins regardless.
|
||||||
|
|
||||||
|
### Acceptance and mutation matrix
|
||||||
|
|
||||||
|
| # | row | mutation |
|
||||||
|
|---|---|---|
|
||||||
|
| G1 | a **foreign** edit before the next render → the old generation is refused | never advance on content change → G1 passes a stale hit |
|
||||||
|
| G2 | every **changing** entry of the domain table moves the generation, one row each | omit that entry from the key |
|
||||||
|
| G3 | every **stable** entry leaves it unchanged, one row each | include that entry → drags cancel on repaint |
|
||||||
|
| G4 | a **selection repaint** preserves the generation and an in-flight drag **continues** | as G3 |
|
||||||
|
| G5 | a mid-gesture mapping change **cancels**: latch reset, empty selection cleared, click chain cleared, **child receives its release** | drop the stale tail silently → the child holds the button and the selection stays armed |
|
||||||
|
| G6 | **v24 positive control** — a legacy session drives a panel gesture end to end | gate ≤ v24 off → old peers lose the panel |
|
||||||
|
| G7 | **v25 positive control** — a mapped session drives one end to end | — |
|
||||||
|
| G8 | **wrong-family refusals, both directions** — bare `PanelPointer` from a v25 session is refused; legacy `Present` at a v25 frontend is rejected | accept either → the bypass returns |
|
||||||
|
| G9 | **identical cells, changed generation, still emitted** — motion dedupe is per cell, and a mapping change makes the same cell a different byte | dedupe across a generation change → the second gesture is suppressed |
|
||||||
|
| G10 | an **invalid** mapped frame retains the previous frame **and** its generation, atomically | update one without the other → a valid generation names a frame never shown |
|
||||||
|
| G11 | **generation exhaustion fails CLOSED** — refuse gestures rather than accept unchecked ones | fail open → the hole returns at the boundary |
|
||||||
|
|
||||||
|
G2 and G3 are enumerated per entry deliberately: one row asserting "the
|
||||||
|
generation changed" cannot show **which** input moved it, and a key
|
||||||
|
that ignores `view_left` passes every row that only scrolls
|
||||||
|
vertically.
|
||||||
|
|
||||||
|
### Consequence for the GUI arc
|
||||||
|
|
||||||
|
This slice takes **v25**, so GUI arc **1e's `OpenTarget` moves to
|
||||||
|
v26** — corrected in `docs/gui-stage1-input-framing.md` **by this
|
||||||
|
slice**, because a canonical document that says v25 is false the moment
|
||||||
|
this lands. `ADVERTISED_PROTOCOL_VERSION` stays pinned at **20**.
|
||||||
|
|
||||||
## 6. Deferred (named)
|
## 6. Deferred (named)
|
||||||
|
|
||||||
Left / right / top side windows; multiple slots per side; **rehoming a leaf
|
Left / right / top side windows; multiple slots per side; **rehoming a leaf
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,13 @@ review overturned; revision 11 retracts it and P2 is implemented as
|
||||||
written** (§6). **Q#S1-8, Q#S1-9 and Q#S1-10 are RULED.** **1-pre is
|
written** (§6). **Q#S1-8, Q#S1-9 and Q#S1-10 are RULED.** **1-pre is
|
||||||
IMPLEMENTED**; 1a onward may begin from this document.
|
IMPLEMENTED**; 1a onward may begin from this document.
|
||||||
|
|
||||||
|
**v26, not v25 — corrected by the panel mapping-generation slice.**
|
||||||
|
That slice (`docs/bottom-panel-framing.md` §5b) takes **v25** for
|
||||||
|
`PanelFramePayload::PresentMapped` / `FrontendEvent::PanelPointerMapped`,
|
||||||
|
and it lands ahead of 1e because panel-pointer replay blocks 1b.
|
||||||
|
Protocol slices stay serialized; one was inserted in front.
|
||||||
|
`ADVERTISED_PROTOCOL_VERSION` remains pinned at **20**.
|
||||||
|
|
||||||
**Verification base:** §2 is **re-measured at `4f77491`** (2026-08-12),
|
**Verification base:** §2 is **re-measured at `4f77491`** (2026-08-12),
|
||||||
the tip after 1-pre; it was originally taken at `a994f37`. Sections
|
the tip after 1-pre; it was originally taken at `a994f37`. Sections
|
||||||
other than §2 were written against `a994f37` and their *rulings* are
|
other than §2 were written against `a994f37` and their *rulings* are
|
||||||
|
|
@ -350,7 +357,7 @@ says nothing about glyphs or hit tests staying at scale 1.
|
||||||
| D5 | Overlay clears on **empty `Preedit`, `Ime::Disabled`, and focus loss** — all three | no overlay | clear on focus loss only → `Disabled` row leaves stale text |
|
| D5 | Overlay clears on **empty `Preedit`, `Ime::Disabled`, and focus loss** — all three | no overlay | clear on focus loss only → `Disabled` row leaves stale text |
|
||||||
| D6 | Dead-key state owned here; 1a buffers nothing | dead keys dropped | buffer in 1a → D6 by construction |
|
| D6 | Dead-key state owned here; 1a buffers nothing | dead keys dropped | buffer in 1a → D6 by construction |
|
||||||
|
|
||||||
### 1e — `OpenTarget` (v25)
|
### 1e — `OpenTarget` (v26)
|
||||||
|
|
||||||
| # | Contract | Witness (fails today because) | Mutation |
|
| # | Contract | Witness (fails today because) | Mutation |
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
|
|
@ -560,7 +567,7 @@ and flushed to the socket**. **Bound: 250 ms.**
|
||||||
|
|
||||||
| | **1a — `TextInput`** | **1e — `OpenTarget` + `OpenTargetResult`** |
|
| | **1a — `TextInput`** | **1e — `OpenTarget` + `OpenTargetResult`** |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| **floor** | **v24** | **v25**, after v24, serialized |
|
| **floor** | **v24** | **v26**, after v25's mapping generation, serialized |
|
||||||
| **encoding** | **appended variant**; never widen a field in place — postcard is positional | appended variants |
|
| **encoding** | **appended variant**; never widen a field in place — postcard is positional | appended variants |
|
||||||
| **byte pin** | frozen-byte fixture on the **previous final variant** | same |
|
| **byte pin** | frozen-byte fixture on the **previous final variant** | same |
|
||||||
| **gate** | daemon accepts from `>= 24`; producer withholds below | `>= 25`; producer withholds below |
|
| **gate** | daemon accepts from `>= 24`; producer withholds below | `>= 25`; producer withholds below |
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue