Skip to content

Screen::unsupported() reports the four SGR parameters the attribute shadow implements #320

Description

@vyncint

TodayScreen::unsupported() is documented as listing "every sequence the emulator did not implement… so a test can tell a plausible-looking wrong grid from a right one". Measured against 0.10.1 from crates.io, driving a real ratatui application (taskboard, in vyncint/termlens-demo), one screen reports both of these at the same instant:

badge.blink = true
unsupported() = ["^[[9m", "^[[29m", "^[[5m", "^[[25m", "^[[59m"]

So SGR 5 is named as unimplemented on the same Screen whose Style::blink correctly reports the cell as blinking. The same holds for SGR 9/29 against Style::strikethroughtermlens-demo's hard.rs::done_titles_are_struck_through_as_well_as_dimmed and the_overdue_badge_blinks_and_plain_red_does_not have asserted both attributes successfully since 0.4.

Why this is wrong rather than merely noisy5, 25, 9 and 29 are exactly the four parameters emu/shadow.rs exists to recover. They reach Callbacks::unhandled_csi because vt100 drops them, and the second parser then puts them on the cell anyway. unsupported() is reporting the backend's gap rather than the emulator's, and the emulator's is what the accessor promises. DESIGN.md §4 makes the same distinction: what the backend does not do, termlens does in front of it.

The consequence is precisely the one the accessor was added to prevent, inverted: a user checking unsupported() before trusting a masked-password or blink assertion is told the sequence was dropped, and concludes a correct assertion is unreliable. It also makes assert!(screen.unsupported().is_empty()) — the natural "did my app emit anything this harness cannot see?" check — impossible to write for any application that uses blink or strikethrough.

^[[59m (underline colour: default) in the same list is correct and should stay: nothing models underline colour.

Reproduction — against published 0.10.1, no path dependency:

let t = /* spawn an app that emits SGR 5 and SGR 9 */;
let s = t.screen();
let (row, col) = s.find("! Handle SIGWINCH").expect("a blinking cell");
assert!(s.cell(row, col).unwrap().style().blink);          // passes
assert!(!s.unsupported().iter().any(|q| &**q == "^[[5m")); // fails

Fix — the tracker already filters the shapes it handles itself before recording (TRACKED_PRIVATE_MODES, the CSI/ESC/OSC lists in emu/unhandled.rs). The SGR parameters the shadow recovers belong in that filter: an SGR whose parameters are entirely within {5, 6, 8, 9, 25, 28, 29} is implemented and should not be recorded. Careful with mixed sequences — ^[[1;5;31m carries a parameter the shadow does not carry, and dropping the whole sequence from the record would hide a real gap; deciding per parameter rather than per sequence is the safer shape, and worth a test either way.

Done when

  • A screen whose cells report blink/conceal/strikethrough does not name the SGR that set them in unsupported().
  • ^[[59m and other genuinely unmodelled SGRs are still reported.
  • A mixed sequence carrying both a recovered and an unrecovered parameter is still reported, and a test says which.

Found by termlens-demo while upgrading it from 0.6.1 to 0.10.1 — the testing tier's purpose is exactly this: a claim measured against the published crate by an application the crate's own suite does not contain.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

      Milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions