grid: re-intern the pen on every sweep that compacts the style table #423

Merged
natsukium merged 4 commits from grid/gc-remaps-pen into main 2026-09-25 10:12:32 +09:00
Owner

Closes #419

TableGc::maybe_sweep took a bare ScreenBuffer and dropped the compacted flag of its style sweep. The daemon's parse core swept grid.screen_mut() through it, so after a compaction Grid::pen_style kept an id that now named another style or none at all: text printed next lost its truecolor colors, underline color and ISO protection until the program set its SGR again.

Change

  • maybe_sweep takes &mut impl Sweepable, a sealed trait implemented for Grid (sweeps through Grid::gc_styles / gc_sizings, which re-intern the pen and remap the open sizing-run handle) and for ScreenBuffer (the client shadow, which holds no handle outside its cells). The sweep methods take a token only felis-grid can construct.
  • Grid::screen_mut is removed and the ScreenBuffer sweeps are pub(crate), so no driver outside felis-grid can sweep a grid's screen without its handles.
  • Audit: pen_style is the only StyleId held outside cells. SavedCursor (DECSC and the inactive screen's slot) stores Attributes and re-interns on restore; current_sizing_handle is remapped by Grid::gc_sizings. has_iso_protected stays consistent: compact recomputes it and the pen re-intern ORs the pen's flag back in.
  • The grid-and-cells explanation now says the pen is re-interned, not rooted.

Tests

  • table_gc/tests.rs: text printed after a compacting maybe_sweep keeps the pen set before it (truecolor fg/bg, curly underline with its color, ISO protection honored by EL) on the primary and alternate screen, after DECSC before the sweep, and for the primary pen saved across ?1049h; plus a proptest comparing a grid swept at random points against an unswept one over interleaved pen / print / sweep / DECSC / DECRC / alt-screen steps. The regression test and the proptest fail when Grid sweeps its screen bare.
  • pool.rs: the daemon's ParseCore::maybe_gc_tables path keeps Rgb(0,16,0) on text printed after the sweep.

just check passes locally.

Closes #419 `TableGc::maybe_sweep` took a bare `ScreenBuffer` and dropped the `compacted` flag of its style sweep. The daemon's parse core swept `grid.screen_mut()` through it, so after a compaction `Grid::pen_style` kept an id that now named another style or none at all: text printed next lost its truecolor colors, underline color and ISO protection until the program set its SGR again. ## Change - `maybe_sweep` takes `&mut impl Sweepable`, a sealed trait implemented for `Grid` (sweeps through `Grid::gc_styles` / `gc_sizings`, which re-intern the pen and remap the open sizing-run handle) and for `ScreenBuffer` (the client shadow, which holds no handle outside its cells). The sweep methods take a token only felis-grid can construct. - `Grid::screen_mut` is removed and the `ScreenBuffer` sweeps are `pub(crate)`, so no driver outside felis-grid can sweep a grid's screen without its handles. - Audit: `pen_style` is the only `StyleId` held outside cells. `SavedCursor` (DECSC and the inactive screen's slot) stores `Attributes` and re-interns on restore; `current_sizing_handle` is remapped by `Grid::gc_sizings`. `has_iso_protected` stays consistent: `compact` recomputes it and the pen re-intern ORs the pen's flag back in. - The grid-and-cells explanation now says the pen is re-interned, not rooted. ## Tests - `table_gc/tests.rs`: text printed after a compacting `maybe_sweep` keeps the pen set before it (truecolor fg/bg, curly underline with its color, ISO protection honored by EL) on the primary and alternate screen, after DECSC before the sweep, and for the primary pen saved across `?1049h`; plus a proptest comparing a grid swept at random points against an unswept one over interleaved pen / print / sweep / DECSC / DECRC / alt-screen steps. The regression test and the proptest fail when `Grid` sweeps its screen bare. - `pool.rs`: the daemon's `ParseCore::maybe_gc_tables` path keeps `Rgb(0,16,0)` on text printed after the sweep. `just check` passes locally.
The daemon's parse core and the client's shadow both sweep through
TableGc::maybe_sweep, which took a bare ScreenBuffer and dropped the
"compacted" flag its style sweep returned. On the daemon path that left
Grid::pen_style holding an id the compaction had reassigned or cut off,
so the next printed text resolved to another style or to the default
pen: a long truecolor session lost its colors, underline color and ISO
protection until the program set its SGR again. Only Grid::gc_styles
re-interned the pen, and nothing forced the driver to go through it.

maybe_sweep now takes a sealed Sweepable owner instead of a screen.
Grid sweeps through its own gc_styles / gc_sizings, which re-establish
the pen memo and the open sizing-run handle; ScreenBuffer stays
sweepable for the shadow, which holds no handle outside its cells.
Grid::screen_mut is gone and the ScreenBuffer sweeps are crate-private,
so code outside felis-grid cannot sweep a grid's screen without its
handles. Patching the daemon's one call site was rejected: the next
driver would have the same hole.

The saved cursors (DECSC and the inactive screen's slot) hold Attributes
rather than ids and re-intern on restore, so they need no remap; the
new tests pin that too, on both screens.

Assisted-by: Claude Code
Sealing Sweepable stopped outside implementations but left its sweep
methods public, so code outside felis-grid could still compact a bare
ScreenBuffer's tables while holding a StyleId or sizing handle into
them. The sweeps now take a token type that only felis-grid can name.

Assisted-by: Claude Code
docs: say the style sweep re-interns the pen rather than rooting it
Some checks failed
fuzz / cargo fuzz smoke (per target) (pull_request) Successful in 4m50s
pr / wire schema is compatible with the base (pull_request) Successful in 54s
bench / Criterion full-suite snapshot (pull_request) Has been skipped
fuzz / cargo fuzz nightly long-run (pull_request) Has been skipped
bench / Criterion regression gate (pull_request) Successful in 4m37s
darwin / detect relevant changes (pull_request) Successful in 4s
darwin / darwin docs-only no-op (pull_request) Has been skipped
darwin / build felis (aarch64-darwin) (pull_request) Successful in 55s
darwin / publish felis (aarch64-darwin) (pull_request) Has been skipped
pr / nix flake check (pull_request) Successful in 2m53s
pr / cargo build / clippy / test / deny (pull_request) Successful in 5m30s
pr / frontend smoke (x86_64-linux) (pull_request) Successful in 4m8s
pr / publish felis (x86_64-linux) (pull_request) Has been skipped
windows / detect relevant changes (pull_request) Successful in 3s
windows / windows docs-only no-op (pull_request) Has been skipped
windows / cargo nextest (Windows) (pull_request) Successful in 3m35s
windows / frontend smoke (Windows) (pull_request) Successful in 1m27s
windows / package felis (x86_64-pc-windows-msvc) (pull_request) Has been skipped
windows / cargo clippy (Windows cross) (pull_request) Has been cancelled
b67a2de2e6
The style-interning section listed the pen_style memo among the sweep's
roots, but the sweep marks only cells and re-interns the pen after a
compaction. The difference is the one the pen fix turns on: a pen set
but not yet printed is on no cell, so a driver that skips the re-intern
leaves it stale.

Assisted-by: Claude Code
Merge remote-tracking branch 'origin/main' into grid/gc-remaps-pen
All checks were successful
bench / Criterion full-suite snapshot (pull_request) Has been skipped
fuzz / cargo fuzz nightly long-run (pull_request) Has been skipped
bench / Criterion regression gate (pull_request) Successful in 2m23s
darwin / detect relevant changes (pull_request) Successful in 4s
darwin / darwin docs-only no-op (pull_request) Has been skipped
darwin / build felis (aarch64-darwin) (pull_request) Successful in 53s
darwin / publish felis (aarch64-darwin) (pull_request) Has been skipped
fuzz / cargo fuzz smoke (per target) (pull_request) Successful in 1m7s
pr / nix flake check (pull_request) Successful in 1m12s
pr / wire schema is compatible with the base (pull_request) Successful in 12s
pr / cargo build / clippy / test / deny (pull_request) Successful in 1m39s
pr / frontend smoke (x86_64-linux) (pull_request) Successful in 3m21s
pr / publish felis (x86_64-linux) (pull_request) Has been skipped
windows / detect relevant changes (pull_request) Successful in 4s
windows / windows docs-only no-op (pull_request) Has been skipped
windows / cargo nextest (Windows) (pull_request) Successful in 8m12s
windows / cargo clippy (Windows cross) (pull_request) Successful in 12s
windows / frontend smoke (Windows) (pull_request) Successful in 3m4s
windows / package felis (x86_64-pc-windows-msvc) (pull_request) Has been skipped
8af0437a5a
natsukium deleted branch grid/gc-remaps-pen 2026-09-25 10:12:32 +09:00
Commenting is not possible because the repository is archived.
No description provided.