fix(statusline): harden narrow-band review edges

Document and pin the GPU built-in-only narrow-band clipping policy,
including the intentional ability of a wide right group to hide the
left identity. Guard the fixed UI face ordering used by binary search,
preserve flattened provider tracebacks in *errors*, and rename the
phase-one unavailable reason to cover missing layout contexts.

Refresh the implementation verification record after the full gate
suite.

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Levi Neuwirth 2026-07-21 12:43:22 -04:00
parent 66765b3cbc
commit 1be7a30468
6 changed files with 163 additions and 28 deletions

View File

@ -118,9 +118,9 @@ next machine reads it the way you just did.
`StatuslineSegments`; authoritative-empty/snapshot symmetry; and `StatuslineSegments`; authoritative-empty/snapshot symmetry; and
atomic GPU validation, face resolution, shaping, clipping, and atomic GPU validation, face resolution, shaping, clipping, and
cache invalidation. Acceptance 1-27 is implemented. Final gates: cache invalidation. Acceptance 1-27 is implemented. Final gates:
Clippy clean; 1,617 default + 1,791 CRDT library tests; 7 default + Clippy clean; 1,619 default + 1,793 CRDT library tests; 7 default +
8 CRDT feature acceptance; 114 M4; 108 required GPU; one-invocation 8 CRDT feature acceptance; 114 M4; 109 required GPU; one-invocation
workspace sweep 2,715 passed across 78 suites (19 ignored, workspace sweep 2,718 passed across 78 suites (19 ignored,
`basedpyright` filtered); `git diff --check` clean. This branch is `basedpyright` filtered); `git diff --check` clean. This branch is
awaiting review and **must not be described as merged**. awaiting review and **must not be described as merged**.
- Roadmap: `docs/roadmap-2026-07.md` (ranked arcs). Position: - Roadmap: `docs/roadmap-2026-07.md` (ranked arcs). Position:

View File

@ -8,6 +8,10 @@ and is fully gated; awaiting review, not merged.**
Revision 3: closes review findings on authoritative-empty baseline retention Revision 3: closes review findings on authoritative-empty baseline retention
and the TUI's protected-suffix clipping boundary. and the TUI's protected-suffix clipping boundary.
The implementation review corrected the record for the GPU's
built-in-only narrow-band case: stage 3 deliberately changes the legacy
clipping edge and now pins that behavior with a headless regression test.
Revision 2: closes review findings on invalidation, terminal-control-safe Revision 2: closes review findings on invalidation, terminal-control-safe
grapheme painting, separator ownership, detached-frontend latches, and the grapheme painting, separator ownership, detached-frontend latches, and the
unknown-LSP label. Revision 1 was the initial post-#124 architecture scout. unknown-LSP label. Revision 1 was the initial post-#124 architecture scout.
@ -40,16 +44,17 @@ framed ownership boundary:
`BufferSnapshot`. `BufferSnapshot`.
- The GPU consumes v18 atomically, resolves exact dynamic faces, clips - The GPU consumes v18 atomically, resolves exact dynamic faces, clips
provider runs without wrapping or displacing the protected suffix, provider runs without wrapping or displacing the protected suffix,
and preserves its prior valid state on malformed input. deliberately right-pins over-wide built-in-only readouts, and
preserves its prior valid state on malformed input.
- `builtin/runtime/lsp.lua` registers the first pure right-side provider - `builtin/runtime/lsp.lua` registers the first pure right-side provider
from its private attachment map; the Rust tracker exposes bounded from its private attachment map; the Rust tracker exposes bounded
`init`/`ready`/`degraded`/`crashed`/`stopped`/unknown labels. `init`/`ready`/`degraded`/`crashed`/`stopped`/unknown labels.
The final gate run was sequential and clean: `cargo fmt --check`; The final gate run was sequential and clean: `cargo fmt --check`;
workspace/all-target Clippy with `-D warnings`; 1,617 default and 1,791 workspace/all-target Clippy with `-D warnings`; 1,619 default and 1,793
CRDT library tests; 7 default and 8 CRDT stage-3 acceptance tests; 114 CRDT library tests; 7 default and 8 CRDT stage-3 acceptance tests; 114
M4 acceptance tests (3 ignored, `basedpyright` filtered); 108 required M4 acceptance tests (3 ignored, `basedpyright` filtered); 109 required
GPU tests; and the one-invocation workspace sweep (2,715 passed across GPU tests; and the one-invocation workspace sweep (2,718 passed across
78 suites, 19 ignored, `basedpyright` filtered). `git diff --check` was 78 suites, 19 ignored, `basedpyright` filtered). `git diff --check` was
clean. No flaky rerun was needed. clean. No flaky rerun was needed.
@ -437,7 +442,7 @@ or no enabled providers is an O(1) fast path.
### Q#SL4 - Composition, order, separators, and narrow-window policy ### Q#SL4 - Composition, order, separators, and narrow-window policy
Current built-ins remain protected: Current built-in positions remain anchored:
- **Left:** the frontend's current active/modified/buffer-identity group, - **Left:** the frontend's current active/modified/buffer-identity group,
with its existing edge padding, then custom left segments. with its existing edge padding, then custom left segments.
@ -462,20 +467,25 @@ Priority means **survival priority when horizontal space is tight**:
- Left custom providers are ordered by `(priority descending, - Left custom providers are ordered by `(priority descending,
registration id ascending)`. Higher-priority items sit closest to the registration id ascending)`. Higher-priority items sit closest to the
protected buffer identity. Overflow clips the low-priority tail. leading-edge buffer identity. Overflow clips the low-priority tail.
- Right custom providers are displayed by `(priority ascending, - Right custom providers are displayed by `(priority ascending,
registration id ascending)`, placing higher-priority items closest to registration id ascending)`, placing higher-priority items closest to
the protected diagnostic/cursor/scroll suffix. The complete right run the protected diagnostic/cursor/scroll suffix. The complete right run
is right-aligned; overflow clips its low-priority left edge. is right-aligned; overflow clips its low-priority left edge.
- The protected built-in suffix is never discarded merely because a - The protected built-in suffix is never discarded merely because a
custom provider is long. If the built-in suffix itself cannot fit, custom provider is long. If the built-in suffix itself cannot fit,
each frontend retains today's behavior: the TUI drops the right group the TUI retains its legacy wholesale drop. The GPU deliberately
wholesale, while the GPU keeps its right edge fixed and clips its left changes its legacy narrow-band policy: before stage 3 it pinned the
edge. Custom-prefix clipping preserves the complete built-in suffix built-in group's left edge and clipped the right tail; stage 3 pins
only when that suffix fits by itself. the right edge and clips the left so the readout tail survives.
Custom-prefix clipping preserves the complete built-in suffix only
when that suffix fits by itself.
- The left group gets the space before the right group's measured - The left group gets the space before the right group's measured
origin and clips at the collision boundary. It never overwrites the origin and clips at the collision boundary, without the legacy GPU's
right group. extra 10-pixel gap. It never overwrites the right group. This anchors
buffer identity at the leading edge but does not guarantee its
survival: an over-wide right group may consume all available left
space.
This asymmetric visual ordering is intentional: priority determines This asymmetric visual ordering is intentional: priority determines
what survives, not a generic ascending sort that would protect opposite what survives, not a generic ascending sort that would protect opposite
@ -743,9 +753,14 @@ same belt as `StatusFacts`.
- Right placement uses the full shaped width without clamping its - Right placement uses the full shaped width without clamping its
origin to `TEXT_LEFT`: the run's right edge stays at the right pad, origin to `TEXT_LEFT`: the run's right edge stays at the right pad,
while a negative/left-of-surface origin clips low-priority custom while a negative/left-of-surface origin clips low-priority custom
prefixes and preserves the built-in tail. The left TextArea clips at prefixes and preserves the built-in tail. This intentionally changes
the right group's actual origin. Existing geometry bounds still keep the legacy built-in-only narrow case, which anchored the readout at
all glyphs inside the band. `TEXT_LEFT` and clipped its right tail.
- The left TextArea clips at the right group's actual origin rather
than retaining the legacy extra `STATUS_TEXT_PAD` gap. The right
group therefore owns collision priority and may fully obscure the
left buffer identity in an extremely narrow band. Existing geometry
bounds still keep all glyphs inside the band.
- `ThemeFacts` continues to invalidate both caches. FontFacts already - `ThemeFacts` continues to invalidate both caches. FontFacts already
re-metrics/re-shapes both status buffers; the new rich runs ride that re-metrics/re-shapes both status buffers; the new rich runs ride that
path without a new font transaction. path without a new font transaction.
@ -845,8 +860,11 @@ TUI, producer, and daemon/wire behavior; protocol pins stay in
`RenderState`/semantic frame paths, not direct helper-only formatting. `RenderState`/semantic frame paths, not direct helper-only formatting.
1. **Default preservation:** with no visible provider output, scratch 1. **Default preservation:** with no visible provider output, scratch
TUI cells and GPU pixels are byte-identical to the pre-stage TUI cells and ordinary non-overlapping GPU modeline/status-band
modeline/status band. The global TUI echo row is unchanged. pixels are byte-identical to the pre-stage rendering. The deliberate
GPU narrow-band exception pins an over-wide built-in readout's right
edge and clips its left edge; a built-in-only headless fixture pins
that behavior. The global TUI echo row is unchanged.
2. **Lua strict contract:** valid registration returns a handle and 2. **Lua strict contract:** valid registration returns a handle and
appears in `providers`; bad/unknown side, empty name, non-integer or appears in `providers`; bad/unknown side, empty name, non-integer or
out-of-range priority, non-function `fn`, non-modeline face out-of-range priority, non-function `fn`, non-modeline face

View File

@ -4156,7 +4156,7 @@ impl State {
/// Compose the left group. Minibuffer, isearch, and transient /// Compose the left group. Minibuffer, isearch, and transient
/// messages suppress custom left segments; ordinary buffer identity /// messages suppress custom left segments; ordinary buffer identity
/// stays protected at the leading edge. /// starts at the leading edge but may be fully clipped by the right group.
fn compose_status_left_runs(&self) -> Vec<(String, Color)> { fn compose_status_left_runs(&self) -> Vec<(String, Color)> {
if let Some(minibuffer) = self.minibuffer.as_ref() { if let Some(minibuffer) = self.minibuffer.as_ref() {
return vec![( return vec![(
@ -10007,6 +10007,64 @@ mod tests {
); );
} }
#[test]
fn built_in_only_overwide_readout_clips_left_and_keeps_its_right_tail_pinned() {
let (narrow_width, wide_width, height) = (96, 500, 260);
let Some(mut narrow) = headless_or_skip(narrow_width, height, "text") else {
return;
};
let Some(mut wide) = headless_or_skip(wide_width, height, "text") else {
return;
};
for state in [&mut narrow, &mut wide] {
let buffer_id = BufferId::next();
state.current_buffer_id = Some(buffer_id);
state.status_facts = Some(status_facts(buffer_id, None));
state.own_cursor = Some(OwnCursor { buffer_id, byte: 0 });
}
let narrow_frame = narrow.render_offscreen();
let wide_frame = wide.render_offscreen();
assert!(
narrow.statusline_segments.is_none() && wide.statusline_segments.is_none(),
"fixture must exercise the built-in-only legacy surface"
);
let narrow_status_width = narrow
.status_buffer
.layout_runs()
.map(|run| run.line_w)
.fold(0.0_f32, f32::max);
let wide_status_width = wide
.status_buffer
.layout_runs()
.map(|run| run.line_w)
.fold(0.0_f32, f32::max);
assert!(
(narrow_status_width - wide_status_width).abs() < 0.01,
"surface width must not reshape the no-wrap readout"
);
assert!(
narrow_width as f32 - STATUS_TEXT_PAD - narrow_status_width < 0.0,
"fixture must force the built-in readout past the left edge"
);
assert!(
wide_width as f32 - STATUS_TEXT_PAD - wide_status_width > 0.0,
"comparison surface must fit the complete built-in readout"
);
let band_top = text_area_bottom(height, narrow.fm).floor() as u32;
let pinned_tail_width = 80;
for y in band_top..height {
for offset in 0..pinned_tail_width {
assert_eq!(
px_at(&narrow_frame, narrow_width, narrow_width - 1 - offset, y),
px_at(&wide_frame, wide_width, wide_width - 1 - offset, y),
"built-in readout tail moved at right-edge offset {offset}, y={y}"
);
}
}
}
#[test] #[test]
fn overwide_status_runs_never_wrap_and_keep_the_suffix_pinned() { fn overwide_status_runs_never_wrap_and_keep_the_suffix_pinned() {
let (width, height) = (800, 300); let (width, height) = (800, 300);

View File

@ -5601,7 +5601,7 @@ fn log_hook_error(lua: &Lua, hook_name: &str, err: &crate::hook::HookCallbackErr
/// this function owns only the repository-standard durable sink and window /// this function owns only the repository-standard durable sink and window
/// invalidation. /// invalidation.
pub(crate) fn log_statusline_provider_error(lua: &Lua, failure: &StatuslineProviderFailure) { pub(crate) fn log_statusline_provider_error(lua: &Lua, failure: &StatuslineProviderFailure) {
let message = crate::statusline::sanitize_provider_text(&failure.message); let message = crate::statusline::sanitize_provider_error_text(&failure.message);
let line = format!( let line = format!(
"[statusline:{}] provider registered at {} failed for {:?}/{:?}/{:?}/active={}: {}\n", "[statusline:{}] provider registered at {} failed for {:?}/{:?}/{:?}/active={}: {}\n",
failure.provider_name, failure.provider_name,

View File

@ -2381,6 +2381,14 @@ mod tests {
state.core.borrow().active_window().buffer_id state.core.borrow().active_window().buffer_id
} }
#[test]
fn fixed_ui_face_inventory_is_strictly_sorted() {
assert!(
UI_FACES.windows(2).all(|pair| pair[0] < pair[1]),
"theme_facts_msg uses binary_search; duplicates or unsorted insertions misclassify faces"
);
}
#[test] #[test]
fn line_numbers_emitted_on_toggle_then_suppressed() { fn line_numbers_emitted_on_toggle_then_suppressed() {
// UX gutter (protocol v13): the daemon ships the per-window gutter // UX gutter (protocol v13): the daemon ships the per-window gutter

View File

@ -155,8 +155,8 @@ pub struct StatuslineWindowSegments {
/// Why phase 1 intentionally produced no message. /// Why phase 1 intentionally produced no message.
#[derive(Copy, Clone, Debug, Eq, PartialEq)] #[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum StatuslineNoMessageReason { pub enum StatuslineNoMessageReason {
/// The frontend/view no longer exists. /// The target frontend, view, or one of its layout windows no longer exists.
FrontendUnavailable, ContextUnavailable,
/// A layout window points at a buffer that has been removed. /// A layout window points at a buffer that has been removed.
BufferUnavailable, BufferUnavailable,
/// The semantic frontend's daemon window does not match its declared /// The semantic frontend's daemon window does not match its declared
@ -612,13 +612,13 @@ fn capture_target_contexts(
let view = core let view = core
.views .views
.get(&frontend_id) .get(&frontend_id)
.ok_or(StatuslineNoMessageReason::FrontendUnavailable)?; .ok_or(StatuslineNoMessageReason::ContextUnavailable)?;
let mut contexts = Vec::new(); let mut contexts = Vec::new();
for window_id in view.layout.iter_ids() { for window_id in view.layout.iter_ids() {
let window = core let window = core
.windows .windows
.get(&window_id) .get(&window_id)
.ok_or(StatuslineNoMessageReason::FrontendUnavailable)?; .ok_or(StatuslineNoMessageReason::ContextUnavailable)?;
if buffers.get(window.buffer_id).is_err() { if buffers.get(window.buffer_id).is_err() {
return Err(StatuslineNoMessageReason::BufferUnavailable); return Err(StatuslineNoMessageReason::BufferUnavailable);
} }
@ -638,11 +638,11 @@ fn capture_target_contexts(
let view = core let view = core
.views .views
.get(&frontend_id) .get(&frontend_id)
.ok_or(StatuslineNoMessageReason::FrontendUnavailable)?; .ok_or(StatuslineNoMessageReason::ContextUnavailable)?;
let window = core let window = core
.windows .windows
.get(&view.active) .get(&view.active)
.ok_or(StatuslineNoMessageReason::FrontendUnavailable)?; .ok_or(StatuslineNoMessageReason::ContextUnavailable)?;
if buffers.get(window.buffer_id).is_err() { if buffers.get(window.buffer_id).is_err() {
return Err(StatuslineNoMessageReason::BufferUnavailable); return Err(StatuslineNoMessageReason::BufferUnavailable);
} }
@ -748,6 +748,15 @@ pub fn sanitize_provider_text(text: &str) -> String {
output output
} }
/// Flatten a provider failure into one durable `*errors*` entry while
/// retaining multi-line traceback content.
#[must_use]
pub(crate) fn sanitize_provider_error_text(text: &str) -> String {
text.chars()
.map(|ch| if ch.is_control() { ' ' } else { ch })
.collect()
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
@ -763,6 +772,48 @@ mod tests {
(host, core, statusline) (host, core, statusline)
} }
#[test]
fn provider_output_and_error_log_have_distinct_newline_policies() {
let multiline = "boom\nstack\ttrace\r\0";
assert_eq!(sanitize_provider_text(multiline), "boom");
assert_eq!(
sanitize_provider_error_text(multiline),
"boom stack trace "
);
let (host, core, registry) = harness();
host.lua()
.load(
r"
pmacs.statusline.register {
name='trace', side='left',
fn=function() error('boom\nstack detail') end,
}
",
)
.exec()
.unwrap();
let evaluation = evaluate_statusline(
host.lua(),
&core,
&registry,
StatuslineEvaluationTarget::Grid {
frontend_id: FrontendId::LOCAL,
},
);
assert_eq!(evaluation.new_failures.len(), 1);
let errors = host.errors_buffer_text();
assert!(
errors.contains("boom stack detail") && errors.contains("stack traceback"),
"flattened provider traceback must retain every line: {errors:?}"
);
assert_eq!(
errors.lines().count(),
1,
"one failure run must remain one durable error entry"
);
}
#[test] #[test]
fn registry_epochs_track_layout_and_distinct_enabled_faces() { fn registry_epochs_track_layout_and_distinct_enabled_faces() {
let lua = Lua::new(); let lua = Lua::new();