From 2024821d67b7f6d2b58245cb2cdc720fdb352ed9 Mon Sep 17 00:00:00 2001 From: roboomp Date: Sun, 28 Jun 2026 10:40:18 +0000 Subject: [PATCH] fix(ssh): pinned expanded pending preview commit-unstable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The SSH renderer previously declared 'provisionalPendingPreview: "collapsed"', which only opted the COLLAPSED pending shape out of the transcript's stable-prefix ratchet. Once the user expanded an in-flight SSH preview (ctrl+o) and the framed block outgrew the viewport, the pending rows became ratchet-eligible and committed to native scrollback before the result render inserted the 'Output' section. The settled render then re-anchored the frame, producing two distinct stranded shapes in history: - a stale 'pending SSH: [host]' header pinned above the final '<- SSH: [host]' frame (header variant), and - the pending bottom border row reused in-place as the new 'Output' separator, with a fresh '...' footer pushed below it (footer variant). Flip 'provisionalPendingPreview' to 'true' so every pending shape — collapsed or expanded — is treated as provisional and stays out of native scrollback until the result render commits a settled frame. The 'collapsed'-only opt-out remains correct for renderers (bash, eval) whose expanded pending preview is top-anchored and survives the result render without re-anchoring. Added two contract tests asserting expanded pending SSH is commit-unstable and that bash/eval expanded pending preview is still commit-stable — keeping the opt-in renderer-scoped. Fixes #3714 --- packages/coding-agent/CHANGELOG.md | 1 + packages/coding-agent/src/tools/ssh.ts | 12 ++++-- .../test/tools/ssh-commit-stability.test.ts | 42 +++++++++++++++++++ 3 files changed, 51 insertions(+), 4 deletions(-) diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index d1f4e9eed..d0be92919 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -39,6 +39,7 @@ - Fixed reasoning streaming being locked off for OpenAI-compatible providers that stream reasoning content without advertising reasoning support in model metadata. - Fixed `/shake` and other mid-stream chat rebuilds erasing live LLM output by preserving the in-flight streaming components and pending tools. - Fixed the `time_spent` status-line segment ticking continuously during idle sessions by ensuring it only accumulates active agent execution windows and resets correctly across session switches. +- Fixed expanded pending SSH previews committing provisional rows (`⏳ SSH: [host]` header or the framed `╰──╯` footer) to native scrollback before the result render inserted the `Output` section, leaving a stale pending header above the final frame or reusing the pending footer in-place as the new `├── Output ──┤` separator. The SSH renderer now opts out of pending-preview commits in every expansion state. ([#3714](https://github.com/can1357/oh-my-pi/issues/3714)) ## [16.2.2] - 2026-06-27 diff --git a/packages/coding-agent/src/tools/ssh.ts b/packages/coding-agent/src/tools/ssh.ts index 269115567..f36c6e3d2 100644 --- a/packages/coding-agent/src/tools/ssh.ts +++ b/packages/coding-agent/src/tools/ssh.ts @@ -369,10 +369,14 @@ export const sshToolRenderer = { }); }, mergeCallAndResult: true, - // Collapsed pending preview caps the command to a viewport-sized tail window - // that shifts while args stream. Expanded output is top-anchored enough for - // the transcript to commit its settled prefix. - provisionalPendingPreview: "collapsed", + // Pending call preview can re-anchor wholesale when the final result inserts + // the `Output` section, so no pending SSH rows may commit to native + // scrollback — even when expanded. The expanded pending shape was previously + // allowed to commit, which left two visible shapes in native scrollback once + // the result settled: a stale `⏳ SSH: [host]` header above the final frame, + // and the pending `╰──╯` footer reused in-place as the new `├── Output ──┤` + // separator with a fresh footer pushed below it. + provisionalPendingPreview: true, // Partial-result chrome (pending icon and frame state) differs from the // final SSH glyph/state, so the block stays commit-unstable while // `options.isPartial` holds. Without this, a long-running SSH command's diff --git a/packages/coding-agent/test/tools/ssh-commit-stability.test.ts b/packages/coding-agent/test/tools/ssh-commit-stability.test.ts index a264bba63..bebde1cdd 100644 --- a/packages/coding-agent/test/tools/ssh-commit-stability.test.ts +++ b/packages/coding-agent/test/tools/ssh-commit-stability.test.ts @@ -46,6 +46,34 @@ describe("ssh tool block commit stability", () => { expect(component.isTranscriptBlockCommitStable()).toBe(false); }); + it("keeps the collapsed pending SSH preview commit-unstable until a result arrives", () => { + // Issue #3714: with `provisionalPendingPreview: true` the pending call + // preview is commit-unstable regardless of expansion, so neither the + // `⏳ SSH: [host]` header nor the framed bottom border can leak into + // native scrollback before the result render inserts `Output`. + const component = makeSshComponent(); + component.setArgsComplete(); + + expect(component.isTranscriptBlockFinalized()).toBe(false); + expect(component.isTranscriptBlockCommitStable()).toBe(false); + }); + + it("keeps the expanded pending SSH preview commit-unstable until a result arrives", () => { + // Issue #3714: the previous `"collapsed"` opt-out left expanded pending + // rows commit-stable. Once the box outgrew the viewport the stale + // `⏳ SSH: [host]` header and the pending `╰──╯` footer reached native + // scrollback, then the final result re-anchored the frame and stranded + // the pending rows above (header variant) or reused the footer row in + // place as `├── Output ──┤` (footer variant). Expanded MUST also be + // commit-unstable until the result render replaces the pending shape. + const component = makeSshComponent(); + component.setExpanded(true); + component.setArgsComplete(); + + expect(component.isTranscriptBlockFinalized()).toBe(false); + expect(component.isTranscriptBlockCommitStable()).toBe(false); + }); + it("flips commit-stable as soon as the SSH result settles", () => { const component = makeSshComponent(); component.updateResult(partialResult("connecting…"), true); @@ -65,4 +93,18 @@ describe("ssh tool block commit stability", () => { expect(component.isTranscriptBlockFinalized()).toBe(false); expect(component.isTranscriptBlockCommitStable()).toBe(true); }); + + it("does not opt other foreground tools out of expanded pending-preview commits", () => { + // Sanity: bash/eval still use `provisionalPendingPreview: "collapsed"`, + // so once expanded their pending preview is commit-stable. The SSH + // `true` opt-in MUST remain renderer-scoped — flipping the default + // here would block long top-anchored streams (e.g. a task call's + // context/assignment markdown) from reaching native scrollback. + const component = new ToolExecutionComponent("bash", { command: "ls" }, {}, undefined, uiStub); + component.setExpanded(true); + component.setArgsComplete(); + + expect(component.isTranscriptBlockFinalized()).toBe(false); + expect(component.isTranscriptBlockCommitStable()).toBe(true); + }); });