Bottom-panel Stage 2B-3, part 3 of 3: the band a user can see.
Declaration. `FrontendCellGeometry` ships on attach — deliberately without
a side window, because the daemon needs columns before it can paint a
first frame and gating on panel presence would deadlock the first open —
and again on resize and on every font/scale transaction. The two triggers
differ in exactly one way: `Surface` dedups an identical `CellSize`,
`Metrics` never does, because the cells can be identical while the pixels
behind them are not. That is the case daemon-side value dedup cannot see,
and it is why the epoch is frontend-owned.
Columns come from the stable normal-face probe, never `mono_advance`'s
document-glyph fallback: that fallback would make the panel's width
depend on the first glyph of whatever file is open, so two frontends with
identical metrics and different documents would derive different totals.
A probe that returns no width declares zero usable geometry rather than
reaching for a document sample.
Receipt. `Absent` is authoritative and clears; silence retains. A frame
is validated before any state is touched, so rejection is atomic and the
previous valid frame survives it. A duplicate does no work at all — no
plan rebuild, no reshape, no redraw. `Absent` does NOT discard the
geometry declaration: the frame capacity is unchanged by a panel closing.
Paint. The divider strip and the band's cells ride the existing quad and
glyph layers, in BOTH document and terminal modes — gating the band on
`terminal_mode` would make it vanish exactly when it is hosting the
output the user asked for. The strip's painted rect IS its hover/drag hit
rect, so the icon cannot advertise a target the press would miss.
Input. The band claims gestures before either document path: divider
press starts a drag, motion sends `PanelResizeRows` only when the
requested ROW count changes, bare motion inside the band is a `Move` that
neither focuses nor claims. A drag whose epochs no longer match the
panel on screen is dropped rather than applied to its successor — the
same rule the daemon enforces on receipt, checked on both sides because
neither may depend on the other having done it.
Outbox. Four more tail-only coalescing tags. Geometry is latest-wins
because epochs need only increase, not be consecutive. Resize coalesces
over the complete event including its epochs. Panel `Down`/`Up`/wheel/
context stay lossless and ordered: repeated left `Down`s are what the
daemon reads as a multi-click.
Exhaustion latches, and the latch is load-bearing beyond dropping the
frame: an old `Present` whose epoch still matched would otherwise
resurrect a band under geometry this frontend has disowned.
One pre-existing gap found and left alone: the terminal glyph layer
paints every run in one fixed color, dropping `TextRun::color`. The
panel layer resolves its runs properly rather than mirroring that.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Lv428Fth9LRtffwJSsqH7T