Skip to content

feat: add owned terminal snapshot with atomic live capture - #278

Draft
Alexey Zhokhov (donbeave) wants to merge 7 commits into
microsoft:mainfrom
tailrocks:goal/u02-owned-snapshot
Draft

Alexey Zhokhov (donbeave) wants to merge 7 commits into
microsoft:mainfrom
tailrocks:goal/u02-owned-snapshot

Conversation

@donbeave

@donbeave Alexey Zhokhov (donbeave) commented Oct 25, 2026 •

Copy link
Copy Markdown

Title: feat: add owned terminal snapshot with atomic live capture

Summary

Adds a public, self-contained TerminalSnapshot: a cloneable picture of the
terminal grid (cells, cursor, palette, metadata) that outlives the session it
was captured from, with validation, serialization, and scoped sub-rectangles.

The latest update on top of the original PR head:

  • Documents in_capture as an independent clipping override: disabled
    clipping keeps the real unclamped cursor position, skips grid validation,
    and yields no local coordinates, even inside the grid.
  • Adds an engine-boundary atomicity test with a concurrent resize/scroll
    writer asserting every capture stays coherent.
  • Narrows Unix-only gating in tests to PTY fixtures so constructor, Serde,
    equality, and limit tests also run on Windows.
  • Corrects the docs: snapshot size limits are input limits only; renderers
    still validate their own allocations since metrics and scale change the
    final size.

Head: cb40f257135cab8716ebb93bd23f2ee9f944f8e9
(snapshot.rs, engine.rs, tests/owned_snapshot.rs)

Testing

Focused suite, run locally with nextest (default features):

  • cargo nextest run -p tui-test-rs --test owned_snapshot:
    11 tests run, 11 passed, 0 skipped

Known limitations

  • Live-capture PTY fixtures are Unix-gated; Windows and macOS results are
    pending upstream CI on this PR.
  • Merging needs upstream maintainer review and green CI.

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>
@donbeave

Copy link
Copy Markdown
Author

@microsoft-github-policy-service agree

Reject orphan continuations and leading cells inside unfinished spans
in both try_new and deserialization, using an explicit remaining-span
counter. Ratatui adapter rejects uncovered zero-width cells with a
coordinate-bearing error.

Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
Live capture recomputed each cell width with display_width, but the
backend can lay a glyph out narrower than it measures: heart+VS16 and
the keycap sequence measure two columns yet occupy one with no spacer.
The recomputed width opened a two-column span the next real cell could
not cover, so valid live output failed with BadContinuation.

Capture now reconciles the recomputed width against the backend's own
occupancy record (the run of continuation spacers behind the cell):
collapse to the occupied columns when the backend is narrower, cover
the attested spacers when it is wider, and keep the measured width for
the clipped-edge form at a row end. Zero-width graphemes still record
width zero. Offline construction and deserialization keep the strict
relational validator unchanged.

Every capture that previously validated emits byte-identical cells; only
previously-failing captures change.
Document in_capture as an independent clipping override (validator
unchanged): false keeps the real unclamped position, skips grid
validation, and yields no local coordinates, even inside the grid.
Add an Engine-boundary atomicity test with a concurrent resize/scroll
writer asserting every capture stays coherent. Narrow Unix gating in
owned_snapshot tests to PTY fixtures so pure constructor, Serde,
equality, and limit tests run on Windows. Correct bound docs: snapshot
limits are input limits only; renderers still validate their own
allocations since metrics and scale change the final size.
Alexey Zhokhov (donbeave) added a commit to tailrocks/tui-test that referenced this pull request Oct 26, 2026
Fix cancellable acquisition: the waiter no longer blocks in
lifecycle.read() before checking cancellation or starting its deadline.
Acquisition polls try_read while checking the caller's cancellation flag
and a single request deadline that covers acquisition and observation
together; observation then runs on the remaining budget. A free lock is
taken before any caller-supplied closure runs so parking inside
cancelled (or a slow observer) still pins lifecycle writers (R8);
ordinary waits keep their poll-first order unchanged.

Fix quiet-failure finalization: wait_quiet_cancellable now threads the
session execution context into the shared internal wrapper, so timeouts
on a live child finalize like ordinary failures (TimedOut details,
failure artifact, recording marked failed and retained by OnFailure
close) while cancellation keeps the Cancelled reason. Docs state the
new capabilities (configurable quiet criteria, byte-vs-visual
observation, Session-level access), the pre-existing timeout and
cancellable execution, the TuiTestError return, and the microsoft#278 dependency
plus sampling limitation.
@donbeave Alexey Zhokhov (donbeave) changed the title Add an owned terminal snapshot API feat: add owned terminal snapshot with atomic live capture Oct 26, 2026
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

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant