docs: frame QoL Stage 3, long lines

A line wider than the window cannot be read past the edge in the TUI.
The GPU is not in that state --- it already wraps --- so the
cross-frontend defect is not unreadability. It is that NEITHER behavior
was chosen: the TUI truncates because a cell walk breaks at max_cols,
the GPU wraps because cosmic-text's Wrap::WordOrGlyph default was never
overridden. Two accidents that disagree, and no way to state a
preference in either.

Eleven revisions of review, and the corrections were the substance.
Revision 1 claimed both frontends render from the same CellGrid, which
inverted the entire cost model --- pmacs-gpu ignores the grid variants
and lays out locally, and its own comment says so. Later rounds caught
an impossible round-trip invariant, a "collision" between two
coordinates that were one position, an off-grid argument against a
function that has no grid, a false binary for view_top, and a
renumbered visible-line space that does not exist. Each is recorded
with its reasoning rather than quietly fixed, because the pattern ---
an assertion that reads as precise while resting on something
unverified --- is more useful to the next reader than any single fix.

All six questions are answered and the framing is approved:

  LL1  wrap + truncate, default wrap; horizontal scroll is Stage 4
  LL2  buffer-local mode, resolved into Viewport like folds
  LL4  do not adopt editing.fill-column; ours is ui.line-wrap
  LL5  character wrap in BOTH frontends
  LL6  no global map; byte-anchored view_top; additive DisplayCoord

Two of those deserve to be found later rather than discovered:

GUI users lose word wrap. Character-wrap parity is cheap, true, and
matches Emacs, whose default wrap is also a character wrap. But the GPU
has word-wrapped since it existed and nobody opted into losing it. The
alternative was a UAX #14 dependency, because a whitespace-based grid
wrap would give only APPROXIMATE parity against cosmic-text's Unicode
line breaking --- which is worse than honest divergence, since it looks
unified until it is not.

The audit strategy is asymmetric deliberately. The coordinate functions
gain a required context parameter so the compiler enumerates every call
site; DisplayCoord gains an additive sub_row so untouched consumers
stay CORRECT rather than merely findable. Compiler-enforced where
enforcement is possible, correct-by-default where it is not.

Also corrects two ledger headers that still said OPEN for PRs merged
earlier today (#218 at 09:59Z, #217's absorption; #219 at 13:41Z).
Neither lane is removed --- rule 4 removes a lane when its ARC is done,
not when a PR merges, and the QoL arc has this stage left. Read each
block before cutting it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016bqGA6s9tTUFzYpbeW3tai
This commit is contained in:
Levi Neuwirth 2026-08-06 22:29:54 +02:00
parent 218d2e7acd
commit bd752f242b
No known key found for this signature in database
2 changed files with 1293 additions and 2 deletions

View File

@ -196,7 +196,7 @@ hazard in a shape that looks committed. **A documented error message
that never appears is worse than no documentation**, because the reader
waits for a signal that is not coming.
## Docs absorption after #217PR #218 OPEN
## Docs absorption after #217MERGED as #218 (2026-08-06 09:59Z)
**PR #218** — https://github.com/levineuwirth/pmacs/pull/218. **This
block was written with the lane's first commit, before the PR
@ -225,7 +225,7 @@ are the lanes this one creates, not work it does. Retiring the
CI-CRDT, Distribution, or reap-ledger lanes: each still owns undone
work and rule 4 does not apply to them.
## Honoring `full_grid` (QoL Stage 1) — PR #219 OPEN
## 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
@ -296,6 +296,80 @@ 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.
## Long lines (QoL Stage 3) — BRANCHED, no PR yet
**Branch `long-lines`**, base `githubsucks/main` @ `218d2e7` (the #219
merge). `githubsucks/long-lines` 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 long-lines`.
**This block was written with the lane's first commit, before any PR
exists** — the standing correction from #171, #215 and #220.
- **Framing `docs/long-lines-framing.md` revision 14, APPROVED**, after
**eleven** revisions of review. All six questions answered.
- **Independent of #220.** Stage 2 (GUI zoom) and Stage 3 share no
code, so this branch does not wait on that merge — which matters,
because #220 is blocked on a GitHub Actions outage, not on itself.
### The defect
A line wider than the window is unreadable past the edge **in the
TUI**. The GPU is not in that state: it already wraps. So the
cross-frontend defect is **not** unreadability — it is that *neither
behavior was chosen*. The TUI truncates because a cell walk breaks at
`max_cols`; the GPU wraps because cosmic-text's `Wrap::WordOrGlyph`
default was never overridden. Two accidents that disagree, with no way
for the user to express a preference in either.
### The six answers
- **Q#LL1** — ships `wrap` + `truncate`, **default `wrap`**; horizontal
scroll is **Stage 4**. No default preserves both frontends, so this
knowingly changes the TUI's behavior and leaves the GPU's alone.
- **Q#LL2** — the mode is **buffer-local**; `Viewport` carries the
*resolved* mode as it already carries `folds`, so `TextView` stays
config-agnostic.
- **Q#LL4** — do **not** adopt `editing.fill-column`; it is orphaned
because its consumer (`M-q` / auto-fill) does not exist. Sharpen its
description, and name ours `ui.line-wrap` (`ConfigKind::Enum`).
- **Q#LL5****character wrap in both frontends**; the GPU document
buffer gets its first explicit `set_wrap`, `Wrap::Glyph`.
- **Q#LL6** — no global map (every vertical consumer is local, so
layout is per-line); `view_top`'s sub-line component is a **byte**;
`DisplayCoord` gains `sub_row` rather than redefining `row`.
### The two decisions most likely to be questioned later
- **GUI users lose word wrap.** Character-wrap parity is cheap and
Emacs-consistent, but the GPU has word-wrapped since it existed.
Accepted deliberately (user, 2026-08-06). **Must appear in the PR
description and release notes**, not only in the framing.
- **The audit strategy is asymmetric on purpose.** The coordinate
functions gain a required context parameter so the **compiler
enumerates** every call site; `DisplayCoord` gains an **additive**
field so untouched consumers stay *correct* rather than merely
findable. Compiler-enforced where possible, correct-by-default where
not.
### Verification (planned, from framing §7)
Cell-level tests at several **window widths** (not offsets — offsets
are Stage 4). Wrapped visual-row mapping with identity on cursor
boundaries and projection elsewhere. The **wrap-point** case: the
position at a soft break belongs to `(k+1, 0)`, while a **hard** line
end filling the row exactly keeps `(k, max_cols)` — a control that the
wrap work did not move it. A `truncate` control for every mapping
claim. A GPU witness that `truncate` is *honored*, since the existing
`wrapped_caret_survives_size_changes` passes against a wrap nobody
configured.
### Not in scope
Horizontal scroll in full (Stage 4). `M-q` / auto-fill / reflow. Word
wrap as a mode value — a named future third choice, not this stage.
Bidi/RTL. Soft-wrap gutter indicators.
## 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,

1217
docs/long-lines-framing.md Normal file

File diff suppressed because it is too large Load Diff