docs: bottom-panel Stage 2 framing (revision 4)
Closes review round 3 — 1 blocking, 1 high, 1 medium. **R3-1 (blocker) — the call-site table contradicted the source.** The three-boundary model was right; five rows of its classification were not, and each was a real defect: - `:6140` is `completion_dropdown_layout` — DOCUMENT completion placement, deriving the space below the anchor line. Classified status-owned, it would let completion overlap the panel. - `:7195` and `:7212` are the `status_buffer` / `status_left_buffer` `TextBounds.top` — status text bounds, classified document-owned. - `:7351` clips global minibuffer CANDIDATE glyphs to the dropdown's band anchor; classified document-owned, they would be clipped against a boundary the dropdown does not sit above. - `:8561` (`edge_scroll_direction`, document edge scrolling) was missing entirely, leaving it tied to the old bottom. - `:8077` is `code_caret_rect_in_clip` — caret clipping, not completion placement. Its class was right, its label wrong. Every production site is now individually verified against the source and tabulated with what it actually is. The census is stated as arithmetic a reader can check: 29 matches = 20 production + 1 definition + 8 test sites. Root cause recorded in the revision history: rev 3's table was built from a `grep | head -20` over 29 matches, which is precisely why `:8561` vanished. The minibuffer's status-owned status is now argued from Q#BP14b rather than assumed — it is global, bufferless chrome anchored to the status band, so all four of its sites stay with the band. **R3-2 (high) — clamps preserved.** The three equations permitted negative coordinates on a surface shorter than its chrome, where today's `text_area_bottom` clamps with `.max(0.0)`. All three now clamp at zero, which keeps the "exact formula" exact exactly where it matters most. **R3-3 (medium) — attachment rejection classified SHARED.** `validate_cells` also rejects `cell.attachment.is_some()` (`terminal.rs:305`), whose error text reads "which terminals never use" (`:190-191`) — phrased as a terminal-specific fact, which is why rev 3's "exact split" missed it. Panels implement no attachment rendering in Stage 2, so a `PanelFrame` carrying one describes a surface the GPU would silently not draw; shared rejection fails closed on the producer side instead. The message is reworded grid-neutral when it moves, and giving panels attachment rendering later moves the rejection back deliberately rather than by default. A2B-4 now names the counts on both sides (twelve document-owned move, eight status-owned do not) and carries the three symptom-bearing rows that a plausible misclassification produces. §9 records that the GPU three-boundary split belongs to 2B, not 2A — it is only observable once a band can be installed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
857a86ff59
commit
49757e51a8
|
|
@ -399,11 +399,13 @@ flight.
|
|||
`docs/agent-handoff.md` §1; the two round lessons are in §5.
|
||||
- Landed-docs follow-up merged as **#156** (`main` @ `d152120`,
|
||||
2026-07-25).
|
||||
- **Stage 2 framing: `docs/bottom-panel-stage2-framing.md` revision 3**,
|
||||
on branch `githubsucks/bottom-panel-stage2-framing`, worktree
|
||||
`../pmacs-bp-stage2`, based on `githubsucks/main` @ `ccf29e3`. Round 1
|
||||
closed 2 blocking + 3 high; round 2 closed 1 blocking + 2 high + 1
|
||||
medium and decided both open items. No open items remain. The approved
|
||||
- **Stage 2 framing: `docs/bottom-panel-stage2-framing.md` revision 4**,
|
||||
on branch `githubsucks/bottom-panel-stage2-framing` (three commits,
|
||||
one per revision), worktree `../pmacs-bp-stage2`, based on
|
||||
`githubsucks/main` @ `ccf29e3`. Round 1 closed 2 blocking + 3 high;
|
||||
round 2 closed 1 blocking + 2 high + 1 medium and decided both open
|
||||
items; round 3 closed 1 blocking + 1 high + 1 medium. No open items
|
||||
remain. The approved
|
||||
parent framing `docs/bottom-panel-framing.md` (rev 4) remains
|
||||
authoritative, **including its acceptance criteria 37–55**.
|
||||
- Retained, carrying nothing unmerged: branch `bottom-panel` and worktree
|
||||
|
|
@ -433,7 +435,12 @@ flight.
|
|||
the status chrome must stay pixel-identical at the physical window
|
||||
bottom while document consumers move. A blanket rewrite of that helper
|
||||
moves both together and passes an "everything moved" assertion, so the
|
||||
Stage 2 criterion asserts **both directions in one scenario**.
|
||||
Stage 2 criterion asserts **both directions in one scenario**. The
|
||||
census is 20 production sites (8 status-owned, 12 document-owned) + 1
|
||||
definition + 8 test sites = 29 matches; the framing carries the
|
||||
per-site table. The three easiest to misclassify are document
|
||||
completion `:6140`, minibuffer candidates `:7351`, and edge scrolling
|
||||
`:8561` — each with its own visible symptom.
|
||||
- **Folding Stage 3 and this arc's Stage 2 both touch the semantic
|
||||
projection.** Whichever is framed second re-scouts the other's landed
|
||||
state.
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# Bottom panel Stage 2 — the GPU panel band (framing)
|
||||
|
||||
**Revision 3 — pre-implementation. Ground truth: canonical `main` @
|
||||
**Revision 4 — pre-implementation. Ground truth: canonical `main` @
|
||||
`ccf29e3`, protocol v20, 2026-07-25.**
|
||||
|
||||
Stage 1 (#155, merge `e745068`) gave pmacs window placement, window
|
||||
|
|
@ -26,7 +26,35 @@ geometries), Q#BP16 (pointer transport), Q#BP17 (fold projection), and
|
|||
|
||||
## 0. Revision history
|
||||
|
||||
### 0.0 Round 2 (rev 2 → rev 3) — 1 blocking, 2 high, 1 medium, all closed
|
||||
### 0.0 Round 3 (rev 3 → rev 4) — 1 blocking, 1 high, 1 medium, all closed
|
||||
|
||||
- **R3-1 (blocker).** Rev 3's three-boundary model was right but its
|
||||
call-site table was wrong in five places, and each error was a real
|
||||
defect: `:6140` is **document completion placement** (classified
|
||||
status-owned, which would let completion overlap the panel);
|
||||
`:7195`/`:7212` are the two **status text bounds** (classified
|
||||
document-owned); `:7351` clips **global minibuffer candidate glyphs**
|
||||
to the dropdown's band anchor (classified document-owned, which would
|
||||
clip them against the document boundary); `:8561` (**document edge
|
||||
scrolling**) was missing entirely, leaving it tied to the old bottom;
|
||||
and `:8077` was described as completion placement when it is **caret
|
||||
clipping** (its class was right, its label wrong). §5.3's table is
|
||||
rebuilt from the full census and every row is verified against the
|
||||
source.
|
||||
**Root cause worth recording:** rev 3's table was built from a
|
||||
`grep | head -20` over 29 matches. The truncation is exactly why
|
||||
`:8561` vanished. The census is now stated as 20 production sites +
|
||||
1 definition + 8 test sites = 29, so a future reader can check the
|
||||
arithmetic instead of trusting the list.
|
||||
- **R3-2 (high).** The three equations permitted negative coordinates
|
||||
on a surface shorter than its chrome, where today's
|
||||
`text_area_bottom` clamps with `.max(0.0)`. All three are now
|
||||
explicitly clamped, preserving the current helper's behavior.
|
||||
- **R3-3 (medium).** §5.1's "exact split" omitted `validate_cells`'s
|
||||
`cell.attachment.is_some()` rejection. It is now classified — and
|
||||
**shared**, with the reasoning pinned.
|
||||
|
||||
### 0.1 Round 2 (rev 2 → rev 3) — 1 blocking, 2 high, 1 medium, all closed
|
||||
|
||||
- **R2-1 (blocker).** Rev 2's "one document-bottom seam" conflated two
|
||||
boundaries that must **diverge** once a panel exists. Several sites it
|
||||
|
|
@ -53,7 +81,7 @@ geometries), Q#BP16 (pointer transport), Q#BP17 (fold projection), and
|
|||
- Both §8 open items are decided (§5.3): `BASE_DIVIDER_HEIGHT = 4.0` at
|
||||
scale 1.0, and `TEXT_TOP` stays unscaled.
|
||||
|
||||
### 0.1 Round 1 (rev 1 → rev 2) — 2 blocking, 3 high, 3 revision points, all closed
|
||||
### 0.2 Round 1 (rev 1 → rev 2) — 2 blocking, 3 high, 3 revision points, all closed
|
||||
|
||||
- **R1-1 (blocker).** Rev 1 said all 23 census reads route through
|
||||
`primary_document_window`. That contradicts Q#BP14, which routes only
|
||||
|
|
@ -326,6 +354,20 @@ currently interleaves both concerns. The exact split:
|
|||
text, `validate_selection`, and the `at_bottom == (scroll_offset ==
|
||||
0)` coupling.
|
||||
|
||||
**Attachment rejection is shared, not terminal-only.** `validate_cells`
|
||||
also rejects `cell.attachment.is_some()`
|
||||
(`pmacs-protocol/src/terminal.rs:305`), and its error text reads "A
|
||||
cell carries a frontend attachment, which terminals never use"
|
||||
(`:190-191`) — phrased as a terminal-specific fact, which is why rev 3
|
||||
missed it. **Stage 2 classifies it shared**: panels implement no
|
||||
attachment rendering, so a `PanelFrame` carrying one describes a
|
||||
surface the GPU would silently not draw. Shared rejection fails closed
|
||||
on the producer side rather than shipping an invisible cell. The error
|
||||
message is reworded away from "which terminals never use" to a
|
||||
grid-neutral phrasing when it moves. If a later stage gives panels
|
||||
attachment rendering, this rejection moves back to terminal-only as a
|
||||
deliberate, reviewed change — not by default.
|
||||
|
||||
`PanelFrame` takes the shared half plus its own presence/epoch rules
|
||||
and does **not** inherit the 512 per-axis cap (Bet B5'), so a 4K
|
||||
small-font panel wider than 512 columns is legal while the shared area
|
||||
|
|
@ -407,15 +449,20 @@ Rev 2 asked for a single document-bottom accessor. That was wrong:
|
|||
once a panel is installed, today's single value must **diverge into
|
||||
three**, because some of its consumers must not move at all.
|
||||
|
||||
```
|
||||
status_band_top = surface_height - status_band_height
|
||||
All three clamp at zero, preserving today's `text_area_bottom`
|
||||
`.max(0.0)` behavior — without the clamps a surface shorter than its
|
||||
own chrome yields negative coordinates, and the "exact formula" stops
|
||||
being exact precisely where it matters most:
|
||||
|
||||
geometry_capacity_bottom = status_band_top - reserved_divider_height
|
||||
```
|
||||
status_band_top = max(0, surface_height - status_band_height)
|
||||
|
||||
geometry_capacity_bottom = max(0, status_band_top - divider_height)
|
||||
// divider reserved even while absent
|
||||
|
||||
document_text_bottom = status_band_top
|
||||
- (installed_panel_height + divider_height
|
||||
if Present, else 0)
|
||||
document_text_bottom = max(0, status_band_top
|
||||
- installed_panel_height
|
||||
- installed_divider_height)
|
||||
```
|
||||
|
||||
`geometry_capacity_bottom` is what Q#BP15a's asymmetry already
|
||||
|
|
@ -426,20 +473,67 @@ the document renderer does not actually lose those pixels until a
|
|||
|
||||
**Today `text_area_bottom` (`pmacs-gpu/src/main.rs:8490`) is all three
|
||||
at once**, and its doc comment calls it "the single source for every
|
||||
bottom-of-text computation" (Q#S3). Its ~19 call sites split into three
|
||||
classes:
|
||||
bottom-of-text computation" (Q#S3).
|
||||
|
||||
| Class | Boundary | Sites |
|
||||
| --- | --- | --- |
|
||||
| **Status-owned** — must stay pixel-identical at the window bottom | `status_band_top` | Status-band background rect `:5908`; band tops `:6003`, `:6027`, `:6140`; status text placement `:7134`, `:7922`; global minibuffer chrome |
|
||||
| **Document-owned** — must move when a band is installed | `document_text_bottom` | Code/terminal clips `:7174`, `:7195`, `:7212`, `:7242`, `:7273`, `:7351`, `:7421`; caret visibility and code height `:4566`, `:6118`, `:6581`; document completion placement `:8077`; minimap `:8497`; visible-line estimate `:8501` |
|
||||
| **Geometry declaration** | `geometry_capacity_bottom` | The Q#BP15a conversion only |
|
||||
The census is **29 matches: 20 production call sites, 1 definition
|
||||
(`:8490`), and 8 test sites** (`:12887`, `:12937`, `:12997`, `:13109`,
|
||||
`:13793`, `:14013`, `:15306`, `:15386`). Every production site,
|
||||
classified individually against the source:
|
||||
|
||||
**Status-owned — must stay pixel-identical at the physical window
|
||||
bottom, using `status_band_top`** (8 sites):
|
||||
|
||||
| Site | What it is |
|
||||
| --- | --- |
|
||||
| `:5908` | Status-band background rect `y` |
|
||||
| `:6003` | `mb_visible_window` — rows that fit **above the band** |
|
||||
| `:6027` | `mb_dropdown_window` origin — dropdown grows up from the band |
|
||||
| `:7134` | `status_top` for the right status group |
|
||||
| `:7195` | `status_buffer` `TextBounds.top` — status text bound |
|
||||
| `:7212` | `status_left_buffer` `TextBounds.top` — status text bound |
|
||||
| `:7351` | Minibuffer **candidate glyph** clip, anchored to the dropdown's band origin |
|
||||
| `:7922` | `status_top`, second site |
|
||||
|
||||
The minibuffer is **global, bufferless chrome anchored to the status
|
||||
band** (Q#BP14b keeps `MinibufferPrompt` global), so all four of its
|
||||
sites — `:6003`, `:6027`, `:7351`, and its `status_left_buffer` bound
|
||||
`:7212` — stay status-owned. Clipping candidate glyphs at
|
||||
`document_text_bottom` would clip the dropdown against a boundary it
|
||||
does not sit above.
|
||||
|
||||
**Document-owned — must move when a band is installed, using
|
||||
`document_text_bottom`** (12 sites):
|
||||
|
||||
| Site | What it is |
|
||||
| --- | --- |
|
||||
| `:4566` | `terminal_cell_viewport` — drawable height for the cell grid |
|
||||
| `:6118` | `completion_anchor_px` — anchor visibility bottom |
|
||||
| `:6140` | `completion_dropdown_layout` — **document completion placement**; `band_top - (line_top + line_h)` is the space below the anchor line |
|
||||
| `:6581` | `code_height` |
|
||||
| `:7174` | Code text clip bottom |
|
||||
| `:7242` | Math text clip bottom |
|
||||
| `:7273` | Gutter clip bottom |
|
||||
| `:7421` | Terminal clip bottom |
|
||||
| `:8077` | `code_caret_rect_in_clip` — **caret clipping** |
|
||||
| `:8497` | Minimap drawable height |
|
||||
| `:8501` | Visible-line estimate |
|
||||
| `:8561` | `edge_scroll_direction` — **document edge scrolling** |
|
||||
|
||||
**Geometry declaration** uses `geometry_capacity_bottom`, and is the
|
||||
Q#BP15a conversion only.
|
||||
|
||||
**Sites that consume no bottom coordinate at all** and must not be
|
||||
touched: `:3175`, `:3185`, `:6601`, `:6607` size the status text
|
||||
buffers to `status_band_height` directly. Rev 2 listed them as seam
|
||||
consumers; they are not.
|
||||
|
||||
Three of these classifications are the ones a plausible implementation
|
||||
gets wrong, and each has a visible symptom: document completion
|
||||
(`:6140`) anchored to `status_band_top` **overlaps the panel**;
|
||||
minibuffer candidates (`:7351`) clipped at `document_text_bottom` are
|
||||
**cut off**; and edge scrolling (`:8561`) left on the old bottom
|
||||
**auto-scrolls from inside the panel**.
|
||||
|
||||
Each call site is classified individually. A blanket rewrite of
|
||||
`text_area_bottom` to subtract the band would move the status chrome
|
||||
with the document and is the defect this section exists to prevent.
|
||||
|
|
@ -577,18 +671,21 @@ Refinements 2B adds:
|
|||
frontends with identical metrics and different documents derive
|
||||
identical `total.cols`, and a probe returning `None` declares zero
|
||||
usable geometry rather than falling back to a document sample.
|
||||
- **A2B-4 (contrast assertion).** Installing a panel moves **every**
|
||||
document-owned consumer — code and terminal clips, caret visibility,
|
||||
document completion placement, gutter/math clipping, minimap,
|
||||
visible-line estimate, hit testing, edge scrolling — by exactly
|
||||
`installed_panel_height + divider_height`, **while the status band
|
||||
stays pixel-identical** at the physical window bottom (background
|
||||
rect, band top, and status text placement all unchanged). Both halves
|
||||
are asserted in one scenario: a uniformly wrong implementation that
|
||||
moves the status band too would pass the "everything moved" half
|
||||
alone. The geometry declaration separately reserves the divider while
|
||||
the panel is `Absent`, and the document loses no pixels until a
|
||||
`Present` is painted.
|
||||
- **A2B-4 (contrast assertion).** Installing a panel moves **all twelve
|
||||
document-owned consumers** of §5.3 by exactly
|
||||
`installed_panel_height + divider_height`, **while all eight
|
||||
status-owned sites stay pixel-identical** at the physical window
|
||||
bottom. Both halves are asserted in one scenario: a uniformly wrong
|
||||
implementation that moves the status band too passes the "everything
|
||||
moved" half alone. Three rows carry their own named symptom because
|
||||
they are the ones a plausible implementation misclassifies —
|
||||
**document completion (`:6140`) must not overlap the band**,
|
||||
**minibuffer candidates (`:7351`) must not be clipped by it**, and
|
||||
**edge scrolling (`:8561`) must not trigger from inside it**. The
|
||||
geometry declaration separately reserves the divider while the panel
|
||||
is `Absent`, and the document loses no pixels until a `Present` is
|
||||
painted. All three boundaries clamp at zero on a surface shorter than
|
||||
its chrome.
|
||||
- **A2B-5.** `panel_capable` is true only for a v21+ negotiated
|
||||
authenticated semantic session; a v20 semantic session is never
|
||||
**placed** in a side window, not merely denied the events.
|
||||
|
|
@ -612,7 +709,9 @@ slice under this framing so one-feature/one-branch/one-PR holds. **2A
|
|||
lands before 2B branches** — not stacked.
|
||||
|
||||
- **Stage 2A** — classified census routing + per-window painter
|
||||
extraction. Branch `bottom-panel-stage2a`. No protocol change.
|
||||
extraction. Branch `bottom-panel-stage2a`. No protocol change. The
|
||||
three-boundary GPU split is **2B**, not 2A: it is only observable
|
||||
once a band can be installed.
|
||||
- **Stage 2B** — v21 protocol, daemon panel projection, GPU band, and
|
||||
the negotiated `panel_capable` flip. Branch `bottom-panel-stage2b`,
|
||||
cut from `main` after 2A merges. Repeats 2A's relevant census
|
||||
|
|
|
|||
Loading…
Reference in New Issue