docs: retire the QoL arc's three lanes, re-homing their durable residue (#224)
* docs: retire the long-lines lane, the QoL arc having closed at #223 Rule 4, applied in its stated order: durable facts to `docs/agent-handoff.md` FIRST, then the lane goes. The ledger is for volatile branch/checkpoint/recovery state, and none of that survives a merge worth keeping. What moved to the handoff, because it is architecture rather than bookkeeping: - The GPU is not a grid consumer, so it could not inherit `view_left`; `code_scroll_left` is its own, in pixels, local viewport state with no wire and no version bump. - The work was ONE transform and ONE clip, written before any consumer moved. glyphon honors `TextBounds` so the text layers clip themselves; the manual quad and squiggle renderers do not, and nothing needed them to before this stage because no code-relative x could be negative. - Washes crop rather than drop — the same boundary Stage 4's review caught the TUI painter getting wrong. - Both lifecycle resets are observed PRE-MOTION, because a later cursor motion repairs the offset anyway. - `pmacs_protocol:📜:follow_left`: the follow rule beside `classify`, called by both frontends. The approved exception to Stage 5's scope, recorded in the framing doc §1.2a. - The two witnesses that exist because mutation testing found the TESTS wrong rather than the code — the unwitnessed glyph layer, and the completion predicate a far-off-left test could not catch. "Test a boundary AT the boundary" is the transferable part. The `## Long lines (QoL arc)` block is removed whole; nothing else in the ledger referenced it. NOT REMOVED, and not an oversight: the QoL Stage 1 (#219) and Stage 2 (#220) blocks. Rule 4 would now reach them too — the arc they belong to is closed — but Stage 2 carries a live named follow-on, capability-aware keymap resolution (Q#Z3 option A, deliberately deferred), plus the "first eager state consumer" restore-seam finding. Those need re-homing before their blocks can go, which is a judgment beyond retiring the lane that was asked for. Flagged rather than done. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016bqGA6s9tTUFzYpbeW3tai * docs: complete the retirement — Stages 1 and 2, and the stale anchors The previous commit removed the long-lines lane and stopped short. Two things were still wrong. STALE ANCHORS. The handoff was dated 2026-08-06 with `main` at `db1bbe9` — five merges behind. Now 2026-08-08 at `9a26ac8`, with the arc's five merges named in the ancestry chain, and the same in active-work's canonical-base record and recovery floor (a tree at `db1bbe9` no longer passes; it would lack the whole arc). **The recovery path was re-exercised, not SHA-swapped.** That file warns that advancing the base is exactly when the commands are most likely to have rotted and that a swapped SHA reads identically to a verified one — so: fresh clone into an empty directory, `githubsucks` alias added, `git fetch --prune`, `9a26ac8` confirmed an ancestor of `githubsucks/main`, and a worktree recovered with the three-argument form. All four steps clean. STAGE 1 AND STAGE 2 BLOCKS. Keeping them left false live planning standing: "Stage 3 is long-line wrap/scroll, which is a design round: no horizontal viewport exists at all" — written before #221–#223 built one. A merged lane that still describes the future is worse than no lane. Re-homed first, per Rule 4's order: - **FG-INV is a CONSUMER contract**, and it lives on the protocol type because that is where consumer authors read it. It had been a doc comment on a PRIVATE PRODUCER FIELD, which is why the one consumer never honored it. - **Seven tests covered that flag and all seven tested the producer.** None asserted a consumer acts on it. "Add a test for the flag" had already been done — §5's enforcement/documentation drift in a second register. - **`install_state_dirs` is the eager-state-consumer seam.** Builtins and `init.lua` run before it, so `pmacs.state.read` at module load returns nothing, always. `saveplace` and `recentf` escape it only because both read lazily. Any future eager consumer belongs at the same seam. - **A GPU-only binding cannot be expressed**: `Scope` has no frontend identity and `FrontendEvent` no command-invocation variant. #220 shipped commands without bindings for that reason, not preference. - **Capability-aware keymap resolution** is now a named §6 backlog item: CROSS-CUTTING, NOT STARTED, needs its own framing. It says so explicitly, and says not to start it as a half-lane attached to another stage's branch — which is how it would arrive by accident. No implementation, no lane, no design. What is deliberately NOT preserved: the recovery commands for `full-grid-resync` and `gui-zoom`. Those branches are merged; a recovery command for a branch nobody should check out is the kind of stale instruction this ledger exists to avoid. The framing docs remain on disk as the historical record. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016bqGA6s9tTUFzYpbeW3tai * docs: the live schema range is v6..=v22, and #224 gets its own lane Two review findings against the retirement. THE SCHEMA RANGE. The canonical-base paragraph still said `v6..=v21`. The upper bound moved to **v22 at #221**, which added `InstanceMessage::LineWrapFacts` — so that line had been wrong for two merges, including the one this branch is retiring the lane for. Verified against `pmacs-protocol/src/message.rs` rather than carried forward: `SUPPORTED_PROTOCOL_VERSIONS` is `6..=22`, `PROTOCOL_VERSION` is 22, and `ADVERTISED_PROTOCOL_VERSION` is **20** and did not move. The paragraph now says so, and says the advertised constant must not be edited to chase the range — it is a permanent baseline, and the session version is settled one message later by the frontend's counter-offer. It also now states which claims it governs: historical `v21` statements elsewhere describe a stage as it landed and are correct there. Only this current-state paragraph tracks the live range, so only this one goes stale when the range moves. A LANE FOR #224. This file requires a lane for **every open PR**, and the PR that retires other lanes is not exempt. Added with the branch, the ref-not-a-SHA recovery command, the docs-only scope, and the verification — including that the recovery path was re-exercised rather than SHA-swapped, and that the full gate suite is deliberately not re-run for a change that cannot reach it. The entry is honest about its own lateness: it was written AFTER the PR existed, which is the standing correction from #171 and #215 being missed again, and it took review asking. Back-dating the block to look compliant would have destroyed the only evidence that the practice still slips. It carries its own retirement instruction — next absorption after #224 merges. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016bqGA6s9tTUFzYpbeW3tai * docs: scope the two provenance paragraphs that claimed to be current Both said "this line is the head-of-`main` anchor". Neither was, and a provenance note that claims currency is worse than a plainly historical one — it disagrees with the real anchor at the top of the file while looking authoritative. The bottom-panel paragraph keeps its `v6..=v21` facts, which are correct for the stage they describe. What changes is scope: "a current session" becomes "a session at that anchor", and the closing clause now says these statements describe the historical `6c9e765` anchor, with the live range pointed at "Repository authority" in `docs/active-work.md` (`v6..=v22` since #221; advertised baseline still v20). The counter-offer mechanism is called out as still current independent of which numbers it carries, since that part did not go stale. The second was MY inconsistency, introduced earlier on this branch. I rewrote that bullet's opening to "Beneath the QoL arc, at `db1bbe9`" and left its closing claiming to be the head-of-`main` anchor, so the bullet contradicted itself. It now names what it actually is — the `db1bbe9` ancestry chain — and points at the top of the file. Both cross-references name the file they point into. "Repository authority" is a section of `docs/active-work.md`, not of this one, and an unqualified "above" would resolve to nothing here. Noted, not acted on: the bottom-panel arc has its own currency drift in this file (§1 says Arc 7 COMPLETE at #213, two later paragraphs still call Stage 3 the remaining step). Pre-existing, unrelated to the QoL retirement, and not this PR's to fix. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016bqGA6s9tTUFzYpbeW3tai --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
9a26ac8f9f
commit
b833b139e2
|
|
@ -111,7 +111,11 @@ lesson, §1 for the two framings).
|
||||||
are identical on every machine. Remote names are otherwise
|
are identical on every machine. Remote names are otherwise
|
||||||
machine-local: `origin` may name this canonical URL, a release mirror,
|
machine-local: `origin` may name this canonical URL, a release mirror,
|
||||||
or something else, and therefore has no authority by name alone.
|
or something else, and therefore has no authority by name alone.
|
||||||
- Canonical base at this snapshot: **`githubsucks/main` @ `db1bbe9`** —
|
- Canonical base at this snapshot: **`githubsucks/main` @ `9a26ac8`** —
|
||||||
|
GPU horizontal scroll **#223**, which **closes the QoL arc**, atop
|
||||||
|
`2b56d16` TUI horizontal scroll **#222**, `02f3ec3` `ui.line-wrap`
|
||||||
|
**#221** (protocol v22), `218d2e7` GUI zoom **#220** and `da56bec`
|
||||||
|
`full_grid` **#219**. Beneath those, `db1bbe9`:
|
||||||
the tree primitive **#217**, atop `2657568` the macOS CI
|
the tree primitive **#217**, atop `2657568` the macOS CI
|
||||||
signal-integrity **Stage 2 #216** (which retired R2 and R4), atop
|
signal-integrity **Stage 2 #216** (which retired R2 and R4), atop
|
||||||
`12f2970` its Stage 1 registry **#215**, atop `f186253`:
|
`12f2970` its Stage 1 registry **#215**, atop `f186253`:
|
||||||
|
|
@ -123,13 +127,20 @@ lesson, §1 for the two framings).
|
||||||
#206, Journey Stage 1b-3 #205, 1b-2 #204 and 1b-1 #203, the
|
#206, Journey Stage 1b-3 #205, 1b-2 #204 and 1b-1 #203, the
|
||||||
reap-ledger diagnostic #202, the isolation framing #201, the
|
reap-ledger diagnostic #202, the isolation framing #201, the
|
||||||
process-signal diagnostic #200 and the ledger absorption #199.
|
process-signal diagnostic #200 and the ledger absorption #199.
|
||||||
**Protocol schema support is `v6..=v21`; the production server-first
|
**Protocol schema support is `v6..=v22`; the production server-first
|
||||||
`Hello` still advertises v20** — two different facts, and #184 landed
|
`Hello` still advertises v20** — two different facts, and #184 landed
|
||||||
only the first.
|
only the first. The upper bound moved to **v22 at #221**, which added
|
||||||
|
`InstanceMessage::LineWrapFacts`; `ADVERTISED_PROTOCOL_VERSION` did
|
||||||
|
not move and must not be edited to chase it. Verified against
|
||||||
|
`pmacs-protocol/src/message.rs`, not carried forward: this line said
|
||||||
|
`v6..=v21` for two merges after that stopped being true.
|
||||||
|
**Historical `v21` statements elsewhere in this file are correct
|
||||||
|
where they describe a stage as it landed** — only this
|
||||||
|
current-state paragraph tracks the live range.
|
||||||
**The recovery floor advances with the base**, so the check below
|
**The recovery floor advances with the base**, so the check below
|
||||||
now requires `db1bbe9` or newer; a tree at `12f2970` no longer
|
now requires `9a26ac8` or newer; a tree at `db1bbe9` no longer
|
||||||
passes — it would lack #216 and #217, both of which this file
|
passes — it would lack the entire QoL arc, which this file and the
|
||||||
describes as complete. That is deliberate — a check accepting an older commit than
|
handoff both describe as complete. That is deliberate — a check accepting an older commit than
|
||||||
the declared base passes on a tree the rest of this file does not
|
the declared base passes on a tree the rest of this file does not
|
||||||
describe.
|
describe.
|
||||||
**Lanes below that name an older base have not been re-based; derive
|
**Lanes below that name an older base have not been re-based; derive
|
||||||
|
|
@ -167,7 +178,7 @@ git worktree list
|
||||||
git status --short --branch
|
git status --short --branch
|
||||||
```
|
```
|
||||||
|
|
||||||
The `git log` command must expose `db1bbe9` — the base named above — or a
|
The `git log` command must expose `9a26ac8` — the base named above — or a
|
||||||
newer intentional main. Keep this threshold and the canonical-base line in
|
newer intentional main. Keep this threshold and the canonical-base line in
|
||||||
step: a recovery check that accepts an older commit than the base it
|
step: a recovery check that accepts an older commit than the base it
|
||||||
declares canonical will pass on a tree the rest of this file does not
|
declares canonical will pass on a tree the rest of this file does not
|
||||||
|
|
@ -175,12 +186,15 @@ describe.
|
||||||
If it does not, stop and repair the remote/fetch configuration.
|
If it does not, stop and repair the remote/fetch configuration.
|
||||||
|
|
||||||
**This path was exercised, not asserted, at this snapshot** — re-run
|
**This path was exercised, not asserted, at this snapshot** — re-run
|
||||||
from an empty directory on 2026-08-06 when the base advanced to
|
from an empty directory on 2026-08-08 when the base advanced to
|
||||||
`db1bbe9`, rather than having its SHA swapped. `git clone` the
|
`9a26ac8`, rather than having its SHA swapped. That distinction is the
|
||||||
canonical URL, add the `githubsucks` alias, `git fetch githubsucks
|
whole point of this paragraph: advancing the base is exactly when the
|
||||||
--prune`, confirm `db1bbe9` is an ancestor of `githubsucks/main`, and
|
recovery commands are most likely to have rotted, and a swapped SHA
|
||||||
recover a lane with the three-argument `git worktree add <path> -b
|
reads identically to a verified one. `git clone` the canonical URL, add
|
||||||
<local> githubsucks/<branch>` form. All four steps ran clean.
|
the `githubsucks` alias, `git fetch githubsucks --prune`, confirm
|
||||||
|
`9a26ac8` is an ancestor of `githubsucks/main`, and recover with the
|
||||||
|
three-argument `git worktree add <path> -b <local> githubsucks/<branch>`
|
||||||
|
form. All four steps ran clean.
|
||||||
|
|
||||||
**Correction, found by re-running it.** This file claimed the
|
**Correction, found by re-running it.** This file claimed the
|
||||||
two-argument form fails for a remote-only branch with `fatal: invalid
|
two-argument form fails for a remote-only branch with `fatal: invalid
|
||||||
|
|
@ -196,6 +210,41 @@ 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.
|
||||||
|
|
||||||
|
## QoL arc retirement — PR #224 OPEN (docs only)
|
||||||
|
|
||||||
|
**PR #224** — https://github.com/levineuwirth/pmacs/pull/224. Written
|
||||||
|
**after** the PR existed rather than with the lane's first commit —
|
||||||
|
which is the standing correction from #171 and #215 being missed again,
|
||||||
|
and it took review asking. Recorded that way rather than quietly
|
||||||
|
back-dated: this file requires a lane for **every open PR**, including
|
||||||
|
the PR that retires other lanes.
|
||||||
|
|
||||||
|
- **Branch `retire-long-lines-lane`**, base `githubsucks/main` @
|
||||||
|
`9a26ac8` (the #223 merge). **`githubsucks/retire-long-lines-lane` is
|
||||||
|
the authoritative tip** — the ref, not a SHA, since any edit to this
|
||||||
|
block advances past whatever SHA it records. Recover with
|
||||||
|
`git fetch githubsucks && git checkout retire-long-lines-lane`.
|
||||||
|
- **Docs only.** Two files, `docs/active-work.md` and
|
||||||
|
`docs/agent-handoff.md`. No `src/`, no crate, no manifest, no test
|
||||||
|
changes.
|
||||||
|
- **Scope:** Rule 4 for the QoL arc, closed at #223. Remove the three
|
||||||
|
merged lane blocks (long lines, Stage 1 #219, Stage 2 #220) **after**
|
||||||
|
re-homing their durable residue to the handoff; advance the handoff's
|
||||||
|
date and `main` anchor and this file's canonical-base record and
|
||||||
|
recovery floor; add capability-aware keymap resolution as a named
|
||||||
|
handoff §6 backlog item — cross-cutting, **not started**, needs its
|
||||||
|
own framing.
|
||||||
|
- **Verification:** `git diff --check` clean. **The recovery path was
|
||||||
|
re-exercised, not SHA-swapped** — fresh clone into an empty
|
||||||
|
directory, `githubsucks` alias, `--prune` fetch, `9a26ac8` confirmed
|
||||||
|
an ancestor of `githubsucks/main`, worktree recovered with the
|
||||||
|
three-argument form; all four steps clean. Swept for dangling
|
||||||
|
references to the removed lanes and their branches. The full gate
|
||||||
|
suite is **not** re-run for a change that cannot reach it, and that
|
||||||
|
is stated rather than left as a gap.
|
||||||
|
- **Retire this block in the next absorption after #224 merges.** It
|
||||||
|
describes a docs PR; once merged there is nothing volatile left.
|
||||||
|
|
||||||
## Docs absorption after #217 — MERGED as #218 (2026-08-06 09:59Z)
|
## Docs absorption after #217 — MERGED as #218 (2026-08-06 09:59Z)
|
||||||
|
|
||||||
**PR #218** — https://github.com/levineuwirth/pmacs/pull/218. **This
|
**PR #218** — https://github.com/levineuwirth/pmacs/pull/218. **This
|
||||||
|
|
@ -225,162 +274,6 @@ are the lanes this one creates, not work it does. Retiring the
|
||||||
CI-CRDT, Distribution, or reap-ledger lanes: each still owns undone
|
CI-CRDT, Distribution, or reap-ledger lanes: each still owns undone
|
||||||
work and rule 4 does not apply to them.
|
work and rule 4 does not apply to them.
|
||||||
|
|
||||||
## Honoring `full_grid` (QoL Stage 1) — MERGED as #219 (2026-08-06 13:41Z)
|
|
||||||
|
|
||||||
**PR #219** — https://github.com/levineuwirth/pmacs/pull/219. **This
|
|
||||||
block was written with the lane's first commit, before the PR
|
|
||||||
existed** — the standing correction from #171 and #215 — so the row
|
|
||||||
below was filled in rather than invented.
|
|
||||||
|
|
||||||
- **Branch `full-grid-resync`**, base `githubsucks/main` @ `da56bec`
|
|
||||||
(the #218 merge). `githubsucks/full-grid-resync` is the
|
|
||||||
authoritative tip; any edit to this block advances past whatever SHA
|
|
||||||
it records. Recover with `git fetch githubsucks && git checkout
|
|
||||||
full-grid-resync`.
|
|
||||||
- **Framing `docs/full-grid-resync-framing.md` revision 2**, approved
|
|
||||||
with **Q#FG1 = A**: the sole grid consumer honors the flag by
|
|
||||||
resetting style, clearing, then applying spans.
|
|
||||||
- **First of three QoL stages**, from daily-driver use. Stage 2 is GUI
|
|
||||||
zoom (the machinery exists — `FontMetrics::scale` already derives
|
|
||||||
every dimension and is driven by an attach message in centi-pixels;
|
|
||||||
it is unbound and unpersisted). Stage 3 is long-line wrap/scroll,
|
|
||||||
which is a design round: **no horizontal viewport exists at all**
|
|
||||||
(`view_left` / `col_offset` / `hscroll` match nothing in `src/` or
|
|
||||||
`pmacs-gpu/src/`). Separate branches on purpose — Stage 1 is a
|
|
||||||
contained fix and must not wait behind Stage 3's design.
|
|
||||||
|
|
||||||
### What it ships
|
|
||||||
|
|
||||||
`FG-INV` moves onto the protocol type, where consumer authors read it:
|
|
||||||
a `full_grid: true` delta carries only the frame's **non-default**
|
|
||||||
cells, so a consumer MUST blank its surface first. The rule already
|
|
||||||
existed — in the doc comment of a **private field** on the producer's
|
|
||||||
struct (`src/instance_render.rs:36`), which is why the one consumer
|
|
||||||
never honored it.
|
|
||||||
|
|
||||||
`emit_cell_delta` joins the existing pure escape-sequence helpers in
|
|
||||||
`src/frontend.rs` (`emit_span`, `emit_status_overlay`, …), and
|
|
||||||
`apply_message` routes through it.
|
|
||||||
|
|
||||||
### Why a green suite missed it for so long
|
|
||||||
|
|
||||||
Seven tests cover the flag. Every one asserts the **producer sets it**;
|
|
||||||
none asserted a **consumer acts on it**, and no runtime reader existed
|
|
||||||
anywhere in the workspace. "Add a test for the flag" had already been
|
|
||||||
done. This is handoff §5's *enforcement and documentation drift apart
|
|
||||||
silently* in a second register, and it is why the fix ships the
|
|
||||||
contract and the consumer together.
|
|
||||||
|
|
||||||
### Verification
|
|
||||||
|
|
||||||
- Three unit witnesses, all bitten: order (reset → clear → spans),
|
|
||||||
**empty spans still clear**, and a differential frame clears never.
|
|
||||||
The empty-spans case discriminates on its own — under the plausible
|
|
||||||
`spans.is_empty()` early return the order test still passes and only
|
|
||||||
that one fails.
|
|
||||||
- PTY acceptance (`tests/full_grid_resync_acceptance.rs`) drives a real
|
|
||||||
`SIGWINCH`. **The mark is anchored to content, not time**: a
|
|
||||||
time-based settle cannot work because a settled pmacs screen emits
|
|
||||||
per-frame bytes forever. Bitten against the original defect: 34,831
|
|
||||||
bytes after the first painted frame with no `CSI 2 J`.
|
|
||||||
- **What it does not prove:** the suites assert on raw bytes; there is
|
|
||||||
no screen model and no `vt100`/`termwiz`/`vte` dependency. This shows
|
|
||||||
pmacs *emitted* a blank at the right moment, not that the screen
|
|
||||||
ended correct. A terminal emulator in test deps is a candidate, not
|
|
||||||
smuggled in here.
|
|
||||||
|
|
||||||
### Not in scope
|
|
||||||
|
|
||||||
Stages 2 and 3. The `pmacs.terminal` child-PTY `SIGWINCH` path. Any
|
|
||||||
change to *when* `needs_full_grid` is set — the producer's triggers
|
|
||||||
were verified correct, along with per-frame geometry sync and
|
|
||||||
`view_top` reconciliation on shrink.
|
|
||||||
|
|
||||||
## GUI zoom (QoL Stage 2) — MERGED as #220 (2026-08-07 08:25Z)
|
|
||||||
|
|
||||||
**PR #220** — https://github.com/levineuwirth/pmacs/pull/220. **Written
|
|
||||||
with the lane's first commit, before the PR existed** — the standing
|
|
||||||
correction from #171 and #215 — so the row below was filled in rather
|
|
||||||
than invented.
|
|
||||||
|
|
||||||
- **Branch `gui-zoom`**, base `githubsucks/main` @ `218d2e7` (the #219
|
|
||||||
merge). Pushed; **`githubsucks/gui-zoom` is the authoritative tip** —
|
|
||||||
the ref, deliberately not a SHA, since writing one into the commit
|
|
||||||
that updates this lane makes it stale in that same commit.
|
|
||||||
Recover: `git fetch githubsucks && git checkout gui-zoom`.
|
|
||||||
- **Framing `docs/gui-zoom-framing.md` revision 5**, approved after
|
|
||||||
four review rounds; revision 5 adds §3.2 for a finding raised against
|
|
||||||
the implementation. **Q#Z1 = (c)** configured base with `None`
|
|
||||||
preserved; **Q#Z2 = additive**; **Q#Z3 = (C)** commands only, no
|
|
||||||
default bindings; **Q#Z4** eager restore inside `install_state_dirs`.
|
|
||||||
|
|
||||||
### What it ships
|
|
||||||
|
|
||||||
`builtin/runtime/zoom.lua`: two settings (`ui.gpu-font-size-base`,
|
|
||||||
`ui.gpu-zoom-step`), three commands (`gpu.zoom-in` / `-out` /
|
|
||||||
`-reset`), and `pmacs.zoom.restore` called from `install_state_dirs`.
|
|
||||||
**No rendering work** — `FontMetrics::scale` already derived every GUI
|
|
||||||
dimension and `apply_font_facts` already re-metriced everything; this
|
|
||||||
drives the preference that existed.
|
|
||||||
|
|
||||||
### The findings review caught, none of which was in revision 1
|
|
||||||
|
|
||||||
- **Q#Z3 was not implementable.** `keymap_stack::Scope` is
|
|
||||||
`Buffer | Mode | Global` with no frontend identity, and
|
|
||||||
`FrontendEvent` has no command-invocation variant — so neither "bind
|
|
||||||
on GPU only" nor "the GPU asks for a command" exists. Commands ship;
|
|
||||||
the binding waits on **capability-aware keymap resolution**, now a
|
|
||||||
named follow-on.
|
|
||||||
- **The restore seam did not exist.** Builtins and `init.lua` both run
|
|
||||||
*before* `install_state_dirs`, so a `pmacs.state.read` at module load
|
|
||||||
returns nothing, always. `saveplace` and `recentf` never meet this
|
|
||||||
because **both read lazily**; zoom must apply with no user action,
|
|
||||||
making it the **first eager state consumer**. Restore lives at the
|
|
||||||
end of `install_state_dirs` — by definition when state becomes
|
|
||||||
readable, so it cannot be mis-ordered or missed by a future third
|
|
||||||
startup path.
|
|
||||||
- **Every size write clobbered the family.** `set_font` replaces both
|
|
||||||
fields unconditionally, so `{ size = n }` alone silently cleared a
|
|
||||||
configured family until restart.
|
|
||||||
- **The bounds could not carry the round-trip guarantee** (raised
|
|
||||||
against the implementation, not the framing). `ConfigKind::Number`
|
|
||||||
validates finiteness and bounds and *nothing else*, and `on_change`
|
|
||||||
is notified after the value is stored — so it cannot veto. A step of
|
|
||||||
`0.015` is therefore settable, and used raw it broke the framed
|
|
||||||
exact round trip: `16.00 -> 16.02 -> 16.01`. Fixed by quantizing the
|
|
||||||
step **and** the base at the point of use, which is the operation
|
|
||||||
`validate_font_size` already applies to sizes, one level up.
|
|
||||||
Set-time enforcement was rejected: the registry cannot express
|
|
||||||
precision, and a validating wrapper is bypassed by a direct
|
|
||||||
`pmacs.config.set` — the seam `autosave` documents about
|
|
||||||
`interval_ms`.
|
|
||||||
|
|
||||||
A fifth, documentation-only: the explanation of *why* `0.015` broke
|
|
||||||
said the two intermediates rounded in opposite directions. They do not.
|
|
||||||
`16.015` and `16.005` are exactly `1601.5` and `1600.5` centi-pixels —
|
|
||||||
both exact ties, and half-up sends **both up**. The mechanism is that
|
|
||||||
half-up is not symmetric under negation, so the two roundings
|
|
||||||
accumulate rather than cancel. Corrected in all three copies; no
|
|
||||||
behavior change.
|
|
||||||
|
|
||||||
### Verification
|
|
||||||
|
|
||||||
15 acceptance tests, four bitten: dropping family preservation fails
|
|
||||||
3; reverting to the framing's first parser `^(%d+)$` fails 4 including
|
|
||||||
the seam restore (it anchors to end-of-subject and rejects the
|
|
||||||
newline-terminated file the writer emits); hardcoding the 16.0 origin
|
|
||||||
fails the base test; **using the raw step instead of the quantized one
|
|
||||||
fails the unrepresentable-step witness** with
|
|
||||||
`left: Some(16.01) / right: Some(16.0)` **while the pre-existing 0.37
|
|
||||||
test still passes** — which is exactly why the new case had to be its
|
|
||||||
own test rather than another parameter of that one.
|
|
||||||
|
|
||||||
### Not in scope
|
|
||||||
|
|
||||||
Stage 3 (long lines). Capability-aware keymap resolution — Q#Z3's
|
|
||||||
option (A), deliberately deferred rather than half-built. Per-buffer
|
|
||||||
zoom. Any change to `FontFacts` or the wire.
|
|
||||||
|
|
||||||
## Empty-content readiness, a fourth and fifth instance — FOR THE R6 AUDIT
|
## Empty-content readiness, a fourth and fifth instance — FOR THE R6 AUDIT
|
||||||
|
|
||||||
Found 2026-08-06 while gating this lane, recorded here because it
|
Found 2026-08-06 while gating this lane, recorded here because it
|
||||||
|
|
@ -435,293 +328,6 @@ Whether `docs/ci-red-signatures.md` should grow a short non-row section
|
||||||
for this class is an open question for its owner, not something this
|
for this class is an open question for its owner, not something this
|
||||||
lane decided.
|
lane decided.
|
||||||
|
|
||||||
## Long lines (QoL arc) — Stages 3 and 4 MERGED (#221, #222); Stage 5 closes it
|
|
||||||
|
|
||||||
**Rewritten, not removed.** Rule 4 removes a lane when its ARC is done;
|
|
||||||
this one has **Stage 5** ahead. The durable facts of Stages 3 AND 4 are
|
|
||||||
both in `docs/agent-handoff.md` §1 — rule 4's precondition, satisfied
|
|
||||||
rather than deferred — so what remains here is the Stage 5 plan and
|
|
||||||
only the residue from earlier stages that constrains it.
|
|
||||||
|
|
||||||
> **RULE 4 APPLIES AT STAGE 5's MERGE, AND NOT BEFORE.** The arc closes
|
|
||||||
> at Stage 5 (GPU horizontal scroll). Q#HS1 split the GPU out
|
|
||||||
> deliberately and time-boxed it; retiring this lane at Stage 4's merge
|
|
||||||
> would have orphaned exactly the half the time box exists to
|
|
||||||
> guarantee, while `truncate` was still a dead end in the GUI. **Do not
|
|
||||||
> remove this block until Stage 5 has merged** — and when it does, the
|
|
||||||
> handoff bullets are what makes removal legitimate rather than lossy.
|
|
||||||
|
|
||||||
**Branch `horizontal-scroll`**, based on `githubsucks/main` @ `02f3ec3`
|
|
||||||
(the #221 merge). `githubsucks/horizontal-scroll` is the authoritative
|
|
||||||
tip — the ref, not a SHA, since any edit to this block advances past
|
|
||||||
whatever SHA it records. Recover:
|
|
||||||
`git fetch githubsucks && git checkout horizontal-scroll`.
|
|
||||||
|
|
||||||
**Stage 4 MERGED as #222** (`2b56d16`). Long lines are now reachable
|
|
||||||
**in the TUI**; the GUI half is Stage 5.
|
|
||||||
|
|
||||||
**Branch `gpu-horizontal-scroll`**, based on `githubsucks/main` @
|
|
||||||
`2b56d16`. `githubsucks/gpu-horizontal-scroll` is the authoritative tip
|
|
||||||
— the ref, not a SHA. Recover:
|
|
||||||
`git fetch githubsucks && git checkout gpu-horizontal-scroll`.
|
|
||||||
|
|
||||||
**Status: PR #223 OPEN, awaiting CI. DO NOT MERGE until the user says
|
|
||||||
so.** `https://github.com/levineuwirth/pmacs/pull/223`. Framing revision
|
|
||||||
4 approved 2026-08-07; review round 1 answered 2026-08-08 (the
|
|
||||||
completion point-predicate defect below).
|
|
||||||
|
|
||||||
**The tip is the ref, `githubsucks/gpu-horizontal-scroll`, not a SHA
|
|
||||||
written here.** The first version of this line pinned `55faa45` — which
|
|
||||||
the very commit that wrote it invalidated, because recording the PR
|
|
||||||
moved the head. Verify CI against the PR's live `headRefOid`, never
|
|
||||||
against a SHA quoted in a document.
|
|
||||||
|
|
||||||
G1 pixels (exact conversion via the
|
|
||||||
supported monospace advance); G2 automatic cursor-follow only, zeroing
|
|
||||||
on **both** wrap transition and `BufferSnapshot`; G3 monospace-only by
|
|
||||||
the existing font contract; G4 minimap unchanged; G5 accepted whole —
|
|
||||||
**all twelve witnesses written and mutation-tested** (see below).
|
|
||||||
|
|
||||||
**Scope: local GPU viewport state.** No wire message, no protocol bump,
|
|
||||||
no command surface, no minimap movement.
|
|
||||||
|
|
||||||
**ONE APPROVED EXCEPTION TO THAT SCOPE** (user, 2026-08-08; recorded in
|
|
||||||
the framing doc at §1.2a). Q#G5's
|
|
||||||
TUI-parity witness asks for agreement that is "checkable rather than
|
|
||||||
asserted". Two tests in two crates asserting the same literal is not
|
|
||||||
that — it is exactly the structural duplication
|
|
||||||
`pmacs-protocol::scroll`'s own module docs condemn, and that module
|
|
||||||
exists because **this arc already shipped that defect** (the scroll
|
|
||||||
indicator, fixed in one copy and left wrong in the other). So the follow
|
|
||||||
rule moved to `pmacs_protocol::scroll::follow_left`, beside `classify`,
|
|
||||||
and **both** frontends call it: `src/editor.rs::horizontal_follow`
|
|
||||||
delegates, and the GPU converts px ↔ columns around it (exact by Q#G3).
|
|
||||||
|
|
||||||
What it costs: Stage 5 touches `src/editor.rs`, which the scope line
|
|
||||||
above does not cover. What it buys: the two frontends *cannot* choose
|
|
||||||
different edges. The approval turned on what it does **not** do — it
|
|
||||||
moves no viewport state, adds no wire message, and needs no
|
|
||||||
protocol-version bump.
|
|
||||||
|
|
||||||
**Review round 1 (2026-08-08) — one non-zero-offset defect, fixed.**
|
|
||||||
`completion_anchor_px` reused `survives_code_clip_left` and passed
|
|
||||||
`line_height` as the horizontal extent: **a vertical dimension standing
|
|
||||||
in for a horizontal one**. An anchor up to a line-height left of the
|
|
||||||
gutter survived, and `completion_dropdown_rect` clamps `ax` against the
|
|
||||||
right margin only — so the popup painted over the line numbers. Now a
|
|
||||||
point predicate, `screen_x < code_clip_left()`.
|
|
||||||
|
|
||||||
**The lesson is about the witness, not the predicate.** The existing
|
|
||||||
test placed the anchor 200px off-left, which fails a width-based
|
|
||||||
predicate too — it stayed green straight through the defect and the
|
|
||||||
mutation battery agreed with it, because the battery only ever asked
|
|
||||||
whether *removing* the check was caught. **A boundary this stage cares
|
|
||||||
about must be tested AT the boundary**: the replacement straddles the
|
|
||||||
edge by ±0.05px and additionally asserts the popup's own left edge stays
|
|
||||||
out of the gutter, which is what makes "`completion_dropdown_rect` needs
|
|
||||||
no left clamp" a checked claim rather than a comment.
|
|
||||||
|
|
||||||
**Its first finding corrects Stage 4's framing.** §1.3 there said the
|
|
||||||
GPU "needs a mechanism that does not exist", and I endorsed the
|
|
||||||
Stage 4/5 split partly on that basis. Half of it was right —
|
|
||||||
`Scroll::horizontal` really is discarded, because glyphon 0.11 never
|
|
||||||
applies it — but the document `TextArea` already carries an explicit
|
|
||||||
`left` origin and a `TextBounds` clip, and shifting that origin is the
|
|
||||||
same "paint from 0, clip at the edge" shape the grid uses. The split
|
|
||||||
stays right (the three consumers below are real work), but it was
|
|
||||||
justified partly by an overstatement.
|
|
||||||
|
|
||||||
The real work is applying **one** offset to the three consumers Stage
|
|
||||||
4's framing did name correctly: the caret (`code_byte_px`), decoration
|
|
||||||
geometry (`push_glyph_extent_rects`), and hit testing
|
|
||||||
(`gutter_aware_rel_x`). **No wire and no version bump** — the GPU owns
|
|
||||||
its viewport locally, exactly as it owns `scroll_top`.
|
|
||||||
|
|
||||||
**What Stage 4 shipped**, beyond the `view_left` contract below:
|
|
||||||
|
|
||||||
- **`Viewport::visible_cols` — one clip rule, five adopters.** Review
|
|
||||||
found the first version had translated the base glyph walk and
|
|
||||||
nothing else, so syntax styling, diagnostic underlines, search
|
|
||||||
washes, `BufferStyleOverlay` and the selection painter all kept
|
|
||||||
painting at absolute columns: decorations drifting off the characters
|
|
||||||
they describe, only once a window had been scrolled. The selection
|
|
||||||
painter was worst — it asked `pos_to_display` through the live
|
|
||||||
context, which returns `None` left of the edge, so a selection
|
|
||||||
starting off-screen painted **nothing at all**.
|
|
||||||
|
|
||||||
A second review round caught that my reason for letting selection
|
|
||||||
keep its own copy of the rule (a width the viewport supposedly
|
|
||||||
lacked) was **false**: the render viewport is already
|
|
||||||
`rect.size.cols - gutter_w` with an origin past the gutter. It now
|
|
||||||
takes that viewport. `StyleSpanOverlay` / `VirtualCellOverlay` stay
|
|
||||||
untouched — viewport-relative by contract.
|
|
||||||
- **The `ui.line-wrap` description now names what `truncate` costs**,
|
|
||||||
closing the #221 gap where only the toggle's status message said it.
|
|
||||||
- **`R7`** in `docs/ci-red-signatures.md` — an unrelated, unreproduced
|
|
||||||
`pmacs-gpu` managed-retry `BrokenPipe` under full-sweep load. First
|
|
||||||
incident this session with a complete signature, so a matchable row
|
|
||||||
rather than a `U` note.
|
|
||||||
|
|
||||||
**What Stage 5 shipped, beyond the offset itself:**
|
|
||||||
|
|
||||||
- **`crop_to_code_clip_left`, and `survives_code_clip_left` delegating
|
|
||||||
to it.** One boundary rule, so a caret the crop would discard is never
|
|
||||||
painted. The washes **crop** rather than drop — a selection running in
|
|
||||||
from off the left edge must paint the part that IS visible, which is
|
|
||||||
the same boundary Stage 4's review caught the TUI painter getting
|
|
||||||
wrong.
|
|
||||||
- **Twelve witnesses, each mutation-tested.** Eleven production
|
|
||||||
mutations (unshifted wash x, uncropped wash, unshifted math origin,
|
|
||||||
uncropped math rule, untested caret left edge, missing snapshot reset,
|
|
||||||
missing wrap reset, unhidden completion anchor, unscrolled glyphs,
|
|
||||||
inverted hit-test sign, pixel-instead-of-column snap) each fail the
|
|
||||||
intended witness as an **assertion** failure, not a compile error.
|
|
||||||
The minimap-stability witness was mutation-tested separately by
|
|
||||||
threading the offset into `minimap_vertex_bytes`.
|
|
||||||
- **The glyph-motion witness exists because the first pass lacked it.**
|
|
||||||
The gutter byte-identity test's "the code area must actually have
|
|
||||||
moved" assertion is satisfied by a decoration wash and the caret
|
|
||||||
alone: it **passed with `TextArea.left` pinned to `text_left`**. The
|
|
||||||
mutation battery caught that, not review. Its replacement isolates the
|
|
||||||
glyph layer — no decorations, and a source line carrying no caret.
|
|
||||||
- **`R8`** in `docs/ci-red-signatures.md` — a **deterministic**,
|
|
||||||
pre-existing `m4_acceptance` listview failure, confirmed on `main` by
|
|
||||||
merge-base control. Not this lane's, and deliberately not fixed here.
|
|
||||||
|
|
||||||
**Answered by the user 2026-08-07:**
|
|
||||||
|
|
||||||
- **Q#HS1 — the GPU is Stage 5, not Stage 4.** A conscious, bounded
|
|
||||||
divergence rather than a repeat of Stage 3's accidental one. The time
|
|
||||||
box is concrete: Stage 5 is the *immediately-next* QoL lane after
|
|
||||||
Stage 4 merges, `wrap` stays the default until it lands, Stage 4's
|
|
||||||
release notes state the asymmetry, and the `truncate` affordances
|
|
||||||
name the GUI gap while it exists.
|
|
||||||
- **Q#HS2 — automatic only.** The cursor-visibility pass gains a
|
|
||||||
horizontal component; no commands, no bindings, no new interaction
|
|
||||||
island. Explicit `ui.scroll-*` is deliberately out.
|
|
||||||
- **Q#HS6 — `wrap` stays the default.** Coupled to Q#HS1: with the GPU
|
|
||||||
deferred, a `truncate` default would ship a mode that is navigable in
|
|
||||||
the TUI and a **dead end in the GUI** for anyone who never opened the
|
|
||||||
setting. Revisit after Stage 5, on use evidence.
|
|
||||||
|
|
||||||
- **Q#HS7 — ACCEPTED.** `view_left` is an unsnapped window display
|
|
||||||
column; the effective edge is derived **per line**; a bisected wide
|
|
||||||
glyph's trailing cell renders as styled blank and is designated to
|
|
||||||
the **glyph's start** byte; a straddling tab's surviving cells keep
|
|
||||||
the **existing** forward rounding to the byte after the tab
|
|
||||||
(`src/text_view.rs:224` — preserved, not chosen). Together these make
|
|
||||||
the mapping **total over visible cells**, which is the (d) invariant.
|
|
||||||
The discriminating witness is multi-line, with glyph widths differing
|
|
||||||
at the same column.
|
|
||||||
- **Q#HS5 — APPROVED: yes, persist, no `DESKTOP_VERSION` bump** —
|
|
||||||
conditional on `#[serde(default)]` **and** a literal v1 JSON fixture
|
|
||||||
omitting the field, asserting restore at zero. Both conditions are
|
|
||||||
part of the approval.
|
|
||||||
|
|
||||||
- **Q#HS3** is re-confirmed rather than open (per-window, per Q#LL2);
|
|
||||||
**Q#HS4** is deferred, live only if explicit commands arrive.
|
|
||||||
|
|
||||||
**The reasoning worth keeping from the withdrawn Q#HS7(c).** Revision
|
|
||||||
2 voted to snap `view_left` to a valid boundary when set. That cannot
|
|
||||||
exist, and the reason generalizes: `view_left` is ONE per-window
|
|
||||||
column, but *"does column N bisect a wide glyph?"* is a **per-line**
|
|
||||||
question. No setter-time value is canonical for every visible line,
|
|
||||||
and snapping per line instead would break the vertical alignment a
|
|
||||||
column-oriented view exists to provide. Recorded because the same trap
|
|
||||||
waits for any future window-wide value derived from per-line content.
|
|
||||||
|
|
||||||
**One correction carried into revision 2.** Revision 1 claimed the
|
|
||||||
"unreachable past the edge" caveat is recorded in the setting's
|
|
||||||
description. It is not — `builtin/runtime/linewrap.lua:23` says only
|
|
||||||
"truncate at the edge"; the word appears in the toggle's status message
|
|
||||||
and a source comment, neither of which a user sees if they set the mode
|
|
||||||
in `init.lua`. **A real, small user-facing gap shipped in #221**;
|
|
||||||
amending the description is now a Stage 4 deliverable (framing §6).
|
|
||||||
|
|
||||||
### What Stages 3 and 4 shipped that Stage 5 must live with
|
|
||||||
|
|
||||||
- **`ui.line-wrap` is BUFFER-local** (`ConfigKind::Enum`,
|
|
||||||
`wrap`/`truncate`, default `wrap`). Q#LL2 recorded the consequence
|
|
||||||
and deliberately deferred it: `view_left` is unambiguously
|
|
||||||
**per-window**, because two panes on one buffer must scroll
|
|
||||||
independently exactly as they already hold independent `view_top`s
|
|
||||||
(`src/desktop.rs:92`, `src/window.rs:374`). So the two halves of one
|
|
||||||
user-facing concept land at different scopes. Emacs effectively does
|
|
||||||
this and it is survivable — but Stage 3 signed up for it as *a
|
|
||||||
decision*, and Stage 4 is where the bill arrives.
|
|
||||||
- **`truncate` is the mode Stage 4 makes navigable.** Today text past
|
|
||||||
the right edge is not merely off-screen but **unreachable** — and
|
|
||||||
that is stated **only** in `ui.toggle-line-wrap`'s status message and
|
|
||||||
a source comment, **not** in the setting's description
|
|
||||||
(`builtin/runtime/linewrap.lua:23` says just "truncate at the edge").
|
|
||||||
A user who sets the mode in `init.lua` and never invokes the toggle
|
|
||||||
is told nothing. Amending the description is a Stage 4 deliverable
|
|
||||||
(framing §6); the status message is revisited when scroll lands.
|
|
||||||
- **Under `wrap`, horizontal scroll is meaningless.** Stage 4's surface
|
|
||||||
is therefore conditional on the mode, which is a coherence question
|
|
||||||
(one concept, two behaviors) and not only an implementation one.
|
|
||||||
- **The scroll indicator's `wrap` path takes byte percentages** from
|
|
||||||
`pmacs-protocol::scroll`. It is vertical-only and Stage 4 does not
|
|
||||||
change it — recorded because "scroll" in this lane means horizontal
|
|
||||||
and the two must not be conflated in review.
|
|
||||||
|
|
||||||
### Ground truth gathered for the framing (verify before trusting)
|
|
||||||
|
|
||||||
- **There is no horizontal scroll anywhere in the tree.** No
|
|
||||||
`view_left`, `scroll_left`, or `hscroll` in `src/` or `builtin/`.
|
|
||||||
This is greenfield, not an extension.
|
|
||||||
- **`paint_line` starts every walk at column 0** (`src/text_view.rs`),
|
|
||||||
which is the same walk Stage 3 rewrote for wrapping. A `view_left`
|
|
||||||
enters here, and the wrap rule (`advance_wrapped`) must stay written
|
|
||||||
exactly once.
|
|
||||||
- **The GPU cannot honor horizontal scroll through cosmic-text.**
|
|
||||||
`Scroll::horizontal` is discarded throughout, because **glyphon 0.11
|
|
||||||
never applies it when placing glyphs** — documented at
|
|
||||||
`pmacs-gpu/src/main.rs:1611`, `:6316`, `:8020` and asserted by tests
|
|
||||||
at `:16266`, `:16337`, `:16737`. The GPU's Stage 4 half needs a
|
|
||||||
different mechanism entirely. **This is the fact most likely to
|
|
||||||
invert the cost estimate**, exactly as the "both frontends consume
|
|
||||||
the same `CellGrid`" error did in Stage 3 revision 1.
|
|
||||||
- **`view_top` is persisted per leaf** in `SavedLeaf` alongside
|
|
||||||
`cursor`, at `DESKTOP_VERSION = 1` (`src/desktop.rs:33`). A
|
|
||||||
`view_left` that survives a restart needs either a defaulted field or
|
|
||||||
a version bump — a decision, not an afterthought.
|
|
||||||
- **`scroll_window` carries the cursor with the scroll** to defeat the
|
|
||||||
renderer's "auto-scroll to keep cursor visible" pass, whose comment
|
|
||||||
already records the hazard: an unconditional snap-back makes explicit
|
|
||||||
scrolling feel stuck (`src/editor.rs:3624-3628`). A horizontal analog
|
|
||||||
faces the identical problem, and Q#LL3 deferred the choice — drag the
|
|
||||||
cursor, or let the next motion snap back — to this stage.
|
|
||||||
- **`goal_col` is the existing column-memory field**
|
|
||||||
(`src/window.rs:376`, cleared at seven sites in `src/editor.rs`). Its
|
|
||||||
relationship to a horizontal offset is unexamined and is a framing
|
|
||||||
question, not an implementation detail.
|
|
||||||
|
|
||||||
### Gate note this lane inherits
|
|
||||||
|
|
||||||
Stage 3 put eight broken version assertions on CI by running
|
|
||||||
`CLAUDE.md`'s short gate list instead of `docs/agent-handoff.md` §3's,
|
|
||||||
which includes a full sweep. **§3 is the authority.**
|
|
||||||
|
|
||||||
**Stage 4 should not need a protocol bump at all** — Q#HS1 puts the GPU
|
|
||||||
in Stage 5, and `view_left` is per-window TUI state with no wire. If
|
|
||||||
that changes, §3's protocol-bump form is
|
|
||||||
`cargo test --workspace --no-fail-fast -- --skip basedpyright` in
|
|
||||||
**both** feature configurations.
|
|
||||||
|
|
||||||
**`--workspace`, not `--tests`**, and the distinction is not cosmetic:
|
|
||||||
`--tests` selects 108 targets where `--workspace` selects 110, and the
|
|
||||||
two it drops are **`pmacs_protocol` and `pmacs_gpu`**. Stage 3's own
|
|
||||||
*remediation* sweep used `--tests`, so it never ran `pmacs-protocol`'s
|
|
||||||
25 tests — including the `scroll::classify` tests that lane had just
|
|
||||||
written. They passed, but by luck, and a correction that reproduces the
|
|
||||||
shape of its own mistake is worth naming.
|
|
||||||
|
|
||||||
### Not in scope
|
|
||||||
|
|
||||||
`M-q` / auto-fill / reflow. Word wrap as a mode value — a named future
|
|
||||||
third choice. Bidi/RTL. Soft-wrap gutter indicators.
|
|
||||||
|
|
||||||
## Tree primitive (P5) — MERGED as #217; adoption is the open work
|
## Tree primitive (P5) — MERGED as #217; adoption is the open work
|
||||||
|
|
||||||
**The lane is gone, not the work.** Rule 4 removes a lane after merge,
|
**The lane is gone, not the work.** Rule 4 removes a lane after merge,
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,15 @@
|
||||||
# Agent handoff — cross-machine continuity
|
# Agent handoff — cross-machine continuity
|
||||||
|
|
||||||
**Last updated: 2026-08-06.** `main` is **`db1bbe9`** — the tree
|
**Last updated: 2026-08-08.** `main` is **`9a26ac8`** — GPU horizontal
|
||||||
primitive **#217**, atop **#216**, which completed the macOS CI
|
scroll **#223**, which **closes the QoL arc** (§1). Beneath it the arc's
|
||||||
signal-integrity arc by retiring R2 and R4 with discriminating
|
other four: **#222** TUI horizontal scroll, **#221** `ui.line-wrap` at
|
||||||
witnesses, atop **#215**, which built the registry. **That arc is
|
protocol v22, **#220** GUI zoom, **#219** `full_grid` honored by the
|
||||||
retired**; its live residue (R1, R3, and the newer R5 and R6) is
|
grid consumer. Beneath those, `db1bbe9` — the tree primitive **#217**,
|
||||||
re-homed to the async-runtime, reap-ledger, and readiness-helper-audit
|
atop **#216**, which completed the macOS CI signal-integrity arc by
|
||||||
lanes in `docs/active-work.md`.
|
retiring R2 and R4 with discriminating witnesses, atop **#215**, which
|
||||||
|
built the registry. **That arc is retired**; its live residue (R1, R3,
|
||||||
|
and the newer R5 and R6) is re-homed to the async-runtime, reap-ledger,
|
||||||
|
and readiness-helper-audit lanes in `docs/active-work.md`.
|
||||||
|
|
||||||
The live CI-triage rule in §5 points at `docs/ci-red-signatures.md`,
|
The live CI-triage rule in §5 points at `docs/ci-red-signatures.md`,
|
||||||
which keys on **signature, not test name** — and, since 2026-08-06,
|
which keys on **signature, not test name** — and, since 2026-08-06,
|
||||||
|
|
@ -83,17 +86,48 @@ reads it the way you just did.
|
||||||
For volatile branches, checkpoints, verification, and recovery
|
For volatile branches, checkpoints, verification, and recovery
|
||||||
commands, read `docs/active-work.md` immediately after this file.
|
commands, read `docs/active-work.md` immediately after this file.
|
||||||
|
|
||||||
## 1. Where the project stands (2026-08-07)
|
## 1. Where the project stands (2026-08-08)
|
||||||
|
|
||||||
- **QoL arc — Stages 1-4 merged (#219, #220, #221, #222); Stage 5
|
- **QoL arc — CLOSED. All five stages merged (#219, #220, #221, #222,
|
||||||
remains, and the arc closes there.** From one daily-driver report:
|
#223).** From one daily-driver report: terminal zoom broke TUI
|
||||||
terminal zoom broke TUI rendering and did nothing in the GUI, and a
|
rendering and did nothing in the GUI, and a long line was unreadable
|
||||||
long line was unreadable past the edge.
|
past the edge. Both complaints are answered on both frontends.
|
||||||
- **#219** made the grid TUI honor `full_grid`, so a post-resize
|
- **#219** made the grid TUI honor `full_grid`, so a post-resize
|
||||||
resync blanks the host before repainting.
|
resync blanks the host before repainting.
|
||||||
|
- **`FG-INV` is a CONSUMER contract and lives on the protocol
|
||||||
|
type.** A `full_grid: true` delta carries only the frame's
|
||||||
|
**non-default** cells, so a consumer MUST blank its surface
|
||||||
|
before applying them. The rule already existed — in the doc
|
||||||
|
comment of a **private field on the producer's struct** — which
|
||||||
|
is exactly why the one consumer never honored it. An invariant a
|
||||||
|
consumer must satisfy belongs where consumer authors read it.
|
||||||
|
- **Seven tests covered the flag and all seven tested the
|
||||||
|
producer.** Every one asserted the producer *sets* it; none
|
||||||
|
asserted a consumer *acts* on it, and no runtime reader existed
|
||||||
|
anywhere in the workspace. "Add a test for the flag" had already
|
||||||
|
been done. §5's *enforcement and documentation drift apart
|
||||||
|
silently*, in a second register — and the reason a fix ships the
|
||||||
|
contract and its consumer together.
|
||||||
- **#220** gave the GUI native zoom over the font preference that
|
- **#220** gave the GUI native zoom over the font preference that
|
||||||
already existed, quantizing the step so the round-trip guarantee is
|
already existed, quantizing the step so the round-trip guarantee is
|
||||||
exact rather than approximately true.
|
exact rather than approximately true.
|
||||||
|
- **`install_state_dirs` is the eager-state-consumer seam.**
|
||||||
|
Builtins and `init.lua` both run *before* it, so a
|
||||||
|
`pmacs.state.read` at module load returns nothing, **always**.
|
||||||
|
`saveplace` and `recentf` never meet this because **both read
|
||||||
|
lazily**; zoom must apply with no user action, making it the
|
||||||
|
**first eager state consumer**. Restore therefore lives at the
|
||||||
|
end of `install_state_dirs` — by definition the moment state
|
||||||
|
becomes readable, so a future third startup path cannot
|
||||||
|
mis-order or miss it. **Any future eager consumer belongs at the
|
||||||
|
same seam.**
|
||||||
|
- **A GPU-only key binding cannot be expressed today.**
|
||||||
|
`keymap_stack::Scope` is `Buffer | Mode | Global` with **no
|
||||||
|
frontend identity**, and `FrontendEvent` has no
|
||||||
|
command-invocation variant — so neither "bind on the GPU only"
|
||||||
|
nor "the GPU asks for a command" exists. #220 shipped commands
|
||||||
|
and no default bindings for that reason, not by preference. See
|
||||||
|
the capability-aware keymap resolution backlog item in §6.
|
||||||
- **#221** added **`ui.line-wrap`** — `ConfigKind::Enum`
|
- **#221** added **`ui.line-wrap`** — `ConfigKind::Enum`
|
||||||
(`wrap`/`truncate`), default `wrap`, **buffer-local**, with
|
(`wrap`/`truncate`), default `wrap`, **buffer-local**, with
|
||||||
`ui.toggle-line-wrap`. Both frontends honor it: the grid renderer
|
`ui.toggle-line-wrap`. Both frontends honor it: the grid renderer
|
||||||
|
|
@ -164,17 +198,49 @@ commands, read `docs/active-work.md` immediately after this file.
|
||||||
before the field existed. A literal v1 JSON fixture guards it.
|
before the field existed. A literal v1 JSON fixture guards it.
|
||||||
- **No wire.** `view_left` is per-window viewport state, so no
|
- **No wire.** `view_left` is per-window viewport state, so no
|
||||||
protocol message and no version bump.
|
protocol message and no version bump.
|
||||||
- **Stage 5 is the GPU half** — a split decided rather than inherited
|
- **Stage 5 (#223) is the GPU half, and it closed the arc.** A split
|
||||||
(framing Q#HS1), time-boxed: it is the immediately-next QoL lane,
|
decided rather than inherited (framing Q#HS1). Framing in
|
||||||
and **`wrap` stays the default until it lands**, which keeps the
|
`docs/gpu-horizontal-scroll-framing.md`. The GPU is not a grid
|
||||||
divergence invisible to anyone who has not opted in. Framing in
|
consumer, so it could not inherit `view_left`; it has its own
|
||||||
`docs/gpu-horizontal-scroll-framing.md`.
|
`code_scroll_left`, in **pixels**, local viewport state with no
|
||||||
|
wire and no version bump.
|
||||||
|
- **The work was one transform and one clip, written before any
|
||||||
|
consumer moved.** `code_x_to_screen` / `screen_x_to_code` (exact
|
||||||
|
inverses) and `code_clip_left` / `crop_to_code_clip_left`.
|
||||||
|
**glyphon honors `TextBounds`, so the text layers clip
|
||||||
|
themselves; the manual quad and squiggle renderers do not** — and
|
||||||
|
nothing needed them to before this stage, because no
|
||||||
|
code-relative x could be negative. Five call sites deriving the
|
||||||
|
offset independently is how the caret and its glyphs come to
|
||||||
|
disagree.
|
||||||
|
- **Washes crop, they do not drop.** A selection running in from
|
||||||
|
off the left edge must paint the visible part — the same boundary
|
||||||
|
Stage 4's review caught the TUI painter getting wrong.
|
||||||
|
- **Two lifecycle resets, both observed pre-motion**: the wrap
|
||||||
|
transition and the `BufferSnapshot`. A later cursor motion
|
||||||
|
repairs the offset anyway, so a witness that waits for one cannot
|
||||||
|
tell "reset on snapshot" from "repaired on first motion".
|
||||||
|
- **`pmacs_protocol::scroll::follow_left`** — the follow rule now
|
||||||
|
lives beside `classify` and **both frontends call it**;
|
||||||
|
`src/editor.rs::horizontal_follow` delegates, and the GPU
|
||||||
|
converts px ↔ columns around it (exact, because non-monospace
|
||||||
|
code fonts are rejected). An approved exception to the stage's
|
||||||
|
"local GPU viewport state" scope, recorded in the framing doc
|
||||||
|
§1.2a. It moves no viewport state, adds no wire message, needs no
|
||||||
|
version bump.
|
||||||
|
- **Two witnesses exist because mutation testing found the TESTS
|
||||||
|
wrong, not the code.** The gutter byte-identity test's "the code
|
||||||
|
area must have moved" assertion is satisfied by a wash and the
|
||||||
|
caret alone, so it **passed with `TextArea.left` pinned to the
|
||||||
|
code origin** — the whole glyph-side mechanism was unwitnessed.
|
||||||
|
And the completion predicate passed `line_height` as a
|
||||||
|
*horizontal* extent, which the far-off-left test could not catch
|
||||||
|
because 200px off-left fails a width-based predicate too.
|
||||||
|
**Test a boundary AT the boundary**; the replacements straddle by
|
||||||
|
±0.05px.
|
||||||
|
|
||||||
**Rule 4 removes the long-lines lane when Stage 5 merges** — the
|
- **Beneath the QoL arc, at `db1bbe9`** (it was `main` until #219).
|
||||||
arc closes there, and these bullets are the precondition it depends
|
The **tree primitive #217** — `listview` rows
|
||||||
on.
|
|
||||||
|
|
||||||
- **`main` @ `db1bbe9`.** The **tree primitive #217** — `listview` rows
|
|
||||||
take optional `depth`/`id`, collapse is primitive-owned, folding is
|
take optional `depth`/`id`, collapse is primitive-owned, folding is
|
||||||
**local projection state and not a refresh protocol**, and the LSP
|
**local projection state and not a refresh protocol**, and the LSP
|
||||||
outline is the sole adopter (`COHERENCE.md` §14 ◐; §20 says adoption,
|
outline is the sole adopter (`COHERENCE.md` §14 ◐; §20 says adoption,
|
||||||
|
|
@ -191,7 +257,9 @@ commands, read `docs/active-work.md` immediately after this file.
|
||||||
reap-ledger diagnostic #202, Journey Stage **1b-1** #203, **1b-2**
|
reap-ledger diagnostic #202, Journey Stage **1b-1** #203, **1b-2**
|
||||||
#204 and **1b-3** #205, the ambient-root isolation **implementation**
|
#204 and **1b-3** #205, the ambient-root isolation **implementation**
|
||||||
#206, and discovery Stage 1 #207. Each has its own bullet below; this
|
#206, and discovery Stage 1 #207. Each has its own bullet below; this
|
||||||
line is the head-of-`main` anchor and nothing else.
|
line is the `db1bbe9` ancestry chain and nothing else. **The
|
||||||
|
head-of-`main` anchor is at the top of this file** — it moved to
|
||||||
|
`9a26ac8` at #223, and this bullet's own opening says so.
|
||||||
- **Bottom panel Arc 7 COMPLETE — Stage 3 (#213), the adopter default
|
- **Bottom panel Arc 7 COMPLETE — Stage 3 (#213), the adopter default
|
||||||
flip.**
|
flip.**
|
||||||
Omitting `display` now means the panel for listview, compile and
|
Omitting `display` now means the panel for listview, compile and
|
||||||
|
|
@ -588,12 +656,21 @@ someone forgot.
|
||||||
framing #164, COHERENCE.md #163, find-file #162, Lean 4 Stage 1 #160,
|
framing #164, COHERENCE.md #163, find-file #162, Lean 4 Stage 1 #160,
|
||||||
minimap blank-slab #159, bottom-panel Stage 1 #155).
|
minimap blank-slab #159, bottom-panel Stage 1 #155).
|
||||||
**Protocol schema support is `v6..=v21`, the production server-first
|
**Protocol schema support is `v6..=v21`, the production server-first
|
||||||
`Hello` advertises v20, and a current session nevertheless negotiates
|
`Hello` advertises v20, and a session at that anchor nevertheless
|
||||||
v21.** All three are true at once, and Stage 2B-3 is what made them
|
negotiates v21.** All three are true at once, and Stage 2B-3 is what
|
||||||
compatible: the advertised version is a permanent **baseline** and the
|
made them compatible: the advertised version is a permanent
|
||||||
session's real version is settled one message later by the frontend's
|
**baseline** and the session's real version is settled one message
|
||||||
`AttachRequest` counter-offer. The bullets below describe the arcs in
|
later by the frontend's `AttachRequest` counter-offer — a mechanism
|
||||||
their own terms; this line is the head-of-`main` anchor.
|
that is still current, independent of which numbers it carries. The
|
||||||
|
bullets below describe the arcs in their own terms.
|
||||||
|
|
||||||
|
**These statements describe the historical `6c9e765` anchor; the live
|
||||||
|
protocol range is recorded under "Repository authority" in
|
||||||
|
`docs/active-work.md`** (`v6..=v22` since #221 added `LineWrapFacts`;
|
||||||
|
the advertised baseline is still v20). This line previously called
|
||||||
|
itself "the head-of-`main` anchor", which stopped being true at #219
|
||||||
|
— so a provenance note read as a current-state claim, and disagreed
|
||||||
|
with the anchor at the top of this file.
|
||||||
- **`COHERENCE.md` is now required reading and a required framing input
|
- **`COHERENCE.md` is now required reading and a required framing input
|
||||||
— #163.** It carries the product-coherence thesis, an audited
|
— #163.** It carries the product-coherence thesis, an audited
|
||||||
scorecard, per-concern gaps, and §20's priority order, and it is the
|
scorecard, per-concern gaps, and §20's priority order, and it is the
|
||||||
|
|
@ -2845,6 +2922,23 @@ round-trip cannot detect a discriminant shift.
|
||||||
|
|
||||||
## 6. Named deferrals (the standing backlog, consolidated)
|
## 6. Named deferrals (the standing backlog, consolidated)
|
||||||
|
|
||||||
|
**Capability-aware keymap resolution — CROSS-CUTTING, NOT STARTED,
|
||||||
|
needs its own framing.** Named here because #220 hit its absence and
|
||||||
|
worked around it, not because any of it is designed. Today
|
||||||
|
`keymap_stack::Scope` is `Buffer | Mode | Global` with **no frontend
|
||||||
|
identity**, and `FrontendEvent` carries no command-invocation variant.
|
||||||
|
Two consequences already paid for: **#220 could ship no default zoom
|
||||||
|
bindings** (`gpu.zoom-in` / `-out` / `-reset` are commands only), and
|
||||||
|
nothing can express "this binding exists only where a GPU is
|
||||||
|
attached".
|
||||||
|
|
||||||
|
It is genuinely cross-cutting — it touches the keymap stack, the
|
||||||
|
frontend event vocabulary, and what a capability *is* — so it is a
|
||||||
|
framing round before it is a lane. **Do not start it as a half-lane
|
||||||
|
attached to some other stage's branch**, which is how it would arrive
|
||||||
|
by accident; the workaround (commands without bindings) is stable and
|
||||||
|
costs nothing while it waits.
|
||||||
|
|
||||||
Editing: word kills (`M-d`/`M-BS` — need bytes-returning deleters +
|
Editing: word kills (`M-d`/`M-BS` — need bytes-returning deleters +
|
||||||
prepend-on-backward append), `C-SPC` set-mark, `C-u C-y` / `C-M-w`,
|
prepend-on-backward append), `C-SPC` set-mark, `C-u C-y` / `C-M-w`,
|
||||||
kill-ring browser + persistence, clipboard watching, block comments +
|
kill-ring browser + persistence, clipboard watching, block comments +
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue