Repository navigation
fix: keep explicit underline colors under block cursors - #283
Draft
Alexey Zhokhov (donbeave) wants to merge 12 commits into
Draft
Alexey Zhokhov (donbeave) wants to merge 12 commits into
Alexey Zhokhov (donbeave) wants to merge 12 commits into
Conversation
Live capture reads grid, cursor, dimensions, title, and resolved palette under one terminal-state lock. Default capture records raw visual data; highlighted capture needs an explicit option. Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
Add ResolvedLayout in render/svg.rs: live screenshots and recordings resolve their Style to it, offline snapshots resolve SnapshotRenderOptions to it, and both draw through render_svg_with_layout and GridRenderer::with_layout. RenderState gains from_parts so snapshots can supply resolved colors without a live emulator; encode gains encode_png_bytes shared by file and memory PNG output. render_svg, GridRenderer, and encode_png keep their previous behavior. Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
Public render_snapshot_svg/render_snapshot_png (+ path wrappers) draw a TerminalSnapshot through the same renderer as live screenshots, with SnapshotRenderOptions carrying explicit cell metrics, scale, padding, and chrome switches. Hidden and clipped cursors stay hidden and are recorded in RenderEvidence with dimensions and title. Tests cover U03-A through U03-E and U03-G through U03-I. Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
Resolve the cell underline color once in svg::cell_paint so live and offline rendering inherit the same rule: indexed colors resolve through the snapshot palette, RGB values pass through directly, and a missing color falls back to the resolved foreground color. The raster underline bar keeps its recorded geometry and paints the resolved color; SVG names an explicit color with text-decoration-color and keeps the previous bare output bytes otherwise. Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
RGB changes repaint the underline, indexed colors follow the captured palette entry, a missing color matches explicit foreground pixels, and a live SGR 58 capture decodes equal to its offline render. Hidden cursor throughout. Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
Author
|
@microsoft-github-policy-service agree |
R17: text-decoration-color is not an SVG presentation attribute (SVG 2 keeps it in CSS), so an explicit underline color now rides in style="text-decoration-color: #rrggbb" instead of its own attribute. When underline and strike colors differ, each decoration gets its own element (outer underline, nested line-through tspan with the foreground color), mirroring how the raster renderer paints the two bars. Cells without an explicit color keep the previous bare text-decoration bytes, including the block-cursor redraw, whose decorations follow the redrawn fill. The SVG half of U11-C now parses start tags and asserts the style property structurally instead of by substring. R18: U11-D no longer depends on which /bin/sh a machine links (POSIX printf has no \x escapes; dash prints them literally). Live content comes from a controlled Rust child that writes exact ANSI bytes plus a completion marker, and the test verifies the captured underline style, color, and hidden cursor before the image comparison. Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
R14: snapshot_colors passed default_cursor() into RenderState, so a captured red block cursor rendered green. Pass snapshot.cursor().color() instead, matching live RenderState::capture (emu.color(Cursor)). No construction change: the two agree on fresh live captures and may legitimately differ in hand-built snapshots. R15: 0.25px cells at 40x scale produced viewBox 0 0 0 0 against 40x40 PNG (SVG evidence said 10x10): ceil-then-scale vs scale-then-ceil vs unscaled viewBox rounding disagreed. Like the live Style (too small to draw a cell), offline validate now rejects logical cells under 1px at any scale via RenderError::CellTooSmall, before allocation. SVG/PNG regression tests with distinct cursor colors; fractional-metrics rejection tests at 1x and 40x.
A 3x2 grid with 10.1x20.1 cells at 2x produced three sizes (SVG doc 60.6x80.4 over a rounded 30x40 viewBox, evidence 61x81, PNG 62x82): PNG sizing ceiled before scaling while SVG/evidence used different arithmetic. Round once at the pixel-allocation boundary (ResolvedLayout::output_pixel_size) and use it for PNG pixels, SVG width/height, evidence dims, and an exact unscaled viewBox. Integer layouts format byte-identically to before.
The grid pass draws the colored underline, then the opaque block cursor covers it; the raster cursor repaint redrew only the glyph and dropped the decorations, so explicit red vs blue underlines under a visible block cursor decoded to identical PNGs (the SVG path preserved them). Share one draw_decorations helper between the grid pass and the cursor repaint: the repaint first applies the cursor-foreground rule (missing underline color follows the redrawn cell background; explicit colors stay untouched), mirroring the SVG cursor repaint, and repaints bars even for whitespace blanks. Strike-through keeps following the foreground. Gate the raster-only panel_pixel_size helper behind recording-raster so the no-default-features clippy stays clean. Regression matrix in tests/underline_color.rs (U11-E): visible and hidden cursor, underlined whitespace, RGB and indexed colors, inverse/dim fallback, independent strike color.
Alexey Zhokhov (donbeave)
added a commit
to tailrocks/tuiscotti
that referenced
this pull request
Oct 27, 2026
Migrate tuiscotti onto the tui-test engine as its terminal runtime. Upstream (`tui-test`) owns the terminal engine: child processes, emulation, sessions, input, waiting, rendering, fonts, and pixel comparison. Tuiscotti keeps the application testing layer: pure-view capture, versioned samples, frozen baseline checks, explicit approval, static reports, and the offline CLI (`check` / `accept` / `render` / `report`, exit codes 0/1/2/3). The previous built-in live engine, renderer, recorder, and second approval engine are removed, leaving the two shipping crates (`tuiscotti`, `tuiscotti-cli`). ## Provisional fork dependency The new `tui-test` APIs this PR needs are still unmerged upstream, so this branch temporarily depends on a pinned fork via `[patch]`: - `donbeave/tui-test`, branch `goal/qual-stack-r5`, pinned at `81da26fa9dc29664a897dc802be816a4432c0618` The pin is provisional. Once the upstream PRs below merge and a registry release contains the new APIs, the `[patch]` is removed in favor of the registry version. ## Upstream PRs (all 11, all open) | PR | Change | Head SHA | Status | |----|--------|----------|--------| | [microsoft/tui-test#276](microsoft/tui-test#276) | Explicit read-only snapshot policy | `fc0150160c25ea83532b18f9507f9f1a155b91e4` | Reworked + verified | | [microsoft/tui-test#277](microsoft/tui-test#277) | RGBA image comparison for pixel equality | `4fb316aa56c65cc659b4a43ee4921c45e33cecc0` | Approved as-is; dependency-only, no rework | | [microsoft/tui-test#278](microsoft/tui-test#278) | Owned terminal snapshot API | `334c474d1c621c373333e9dc24e549623a133570` | Reworked + verified | | [microsoft/tui-test#279](microsoft/tui-test#279) | Raw byte input for Rust sessions | `3b1439c313e88d0a8867dfbcdadabb28624dc5b5` | Reworked + verified | | [microsoft/tui-test#280](microsoft/tui-test#280) | Public rendering for offline snapshots | `378036846a4e85174104e0f9c153b0a0f8f84161` | Reworked + verified | | [microsoft/tui-test#281](microsoft/tui-test#281) | Configurable quiet waits and request cancellation | `103d296494f251921f8cd61d3f6ce2f7e152690d` | Reworked + verified | | [microsoft/tui-test#282](microsoft/tui-test#282) | Mode-aware programmatic input | `212ba1db26987a45537708e98c82db77c90bb734` | Reworked + verified | | [microsoft/tui-test#283](microsoft/tui-test#283) | Explicit underline colors in rendering | `fff55c122ae210efbe9a68d1ac8683b1a8856d` | Reworked + verified | | [microsoft/tui-test#284](microsoft/tui-test#284) | Explicit pinned-font rendering | `2bdaa3f8d3692514a6cb9c1ab844e880ca8d0f23` | Reworked + verified | | [microsoft/tui-test#285](microsoft/tui-test#285) | Combined and wide cell text rendering | `edf2b525d6f8a5710cd56440545437ff4f146727` | Reworked + verified | | [microsoft/tui-test#286](microsoft/tui-test#286) | Explicit child launch settings | `d5d9bbab9ac4e8a5e0562175497661b7b1a4b158` | Reworked + verified | "Reworked + verified" means the PR went through a rework round after independent review, and its per-task suites plus the integrated stack re-ran green. #277 needed no changes — its head is unchanged since review — so it is listed as a pure dependency. Upstream merge order: #278 before #280 and #281; #280 before #283 and #284; #284 before #285. #276, #277, #279, #282, and #286 are independent and can merge in any order. ## Verification (at PR head `a056f8f60bae1bf81429b575076c86aed3e6717b`) - Tests: 209/209 passing in both the default-features and no-default-features lanes. - `cargo fmt --check` clean; `cargo clippy --all-targets -D warnings` clean (default and no-default features). - Downstream CI: [run 40408516574](https://github.com/tailrocks/tuiscotti/actions/runs/40408516574) (in progress, at `a056f8f`); prior run [40390626472](https://github.com/tailrocks/tuiscotti/actions/runs/40390626472) success at `9ae97ff`. ## Known limitations - Windows/macOS runner coverage is still pending; platform-specific behavior (for example raw-byte PTY paths) is unverified there. - The upstream PRs currently report only CLA checks; Rust CI on the upstream side still needs maintainer workflow approval, so upstream-side proof rests on the recorded per-task suites plus this downstream integration. - No crates.io release contains the new APIs yet, so registry adoption is blocked until the upstream PRs merge and release. The provisional fork pin above supports merging this PR only; it is not a release path. ## Depends-on / merge order This PR depends on all eleven upstream PRs merging (in the order noted above) plus a registry release; only then does the provisional `[patch]` come out. Nothing in this PR can ship to crates.io before that. Upstream maintainers: the stack is additive-only, and the pinned fork branch tracks the exact heads listed above. --------- Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> Signed-off-by: Muse Code <muse-code@meta.com> Signed-off-by: Muse Code <muse@example.local> Co-authored-by: Muse Code <muse-code@meta.com> Co-authored-by: Muse Code <muse@example.local>
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Title: fix: keep explicit underline colors under block cursors
Depends-On: #280
Incremental diff vs #280 head:
a6adbcc...7a1c7c9
Summary
Explicit underline colors are part of the shared cell paint and are emitted
as CSS in SVG. This fixes the raster path: the grid pass drew the colored
underline, then the opaque block cursor covered it, and the cursor repaint
redrew only the glyph — so red vs blue underlines under a visible block
cursor decoded to identical PNGs.
The grid pass and the cursor repaint now share one decoration helper. The
repaint applies the cursor-foreground rule first (a missing underline color
follows the redrawn cell background; explicit colors stay untouched,
mirroring the SVG repaint) and repaints bars even for whitespace blanks.
Also inherits the offline cursor-color, subpixel-cell, and fractional-size
fixes from #280, and gates a raster-only helper so the
no-default-features clippy stays clean.
Head:
7a1c7c989527b95cf01b69638696ab8a4d81169fTesting
Focused suite, run locally with nextest:
cargo nextest run -p tui-test-rs --features recording-font-jetbrains-mono-styles --test underline_color:11 tests run, 11 passed, 0 skipped
(3 pass without the raster feature.)
Known limitations
recording-rasterfont features; Windows andmacOS results are pending upstream CI on this PR.