Skip to content

fix(web): paint synchronized terminal updates atomically - #9867

Merged
juliusmarminge merged 4 commits into
mainfrom
audit/ghostty-synchronized-output
Oct 20, 2026
Merged

juliusmarminge merged 4 commits into
mainfrom
audit/ghostty-synchronized-output

Conversation

@juliusmarminge

@juliusmarminge juliusmarminge commented Sep 5, 2026 •

Copy link
Copy Markdown
Member

Terminal programs can split a synchronized-output update across PTY chunks. The renderer previously painted a partial new frame and cleared dirty rows before the closing marker. It now defers snapshots, paint and canvas backing-store changes while Ghostty mode 2026 is open, then paints the complete frame.

Each unterminated group gets one 500 ms recovery deadline. Additional output does not extend it. When a write closes one group and opens another, Ghostty’s actual mode transition cancels the first deadline so it cannot prematurely paint the second group. Hidden, zero-sized, disposed and reset surfaces cancel pending deadlines; actual grid resize keeps Ghostty’s behavior of ending synchronization immediately.

Maintainer takeover of this PR, preserving Wout Stiens’ original extraction credit. Merged current main into the existing branch and preserved the newer font/cursor/visibility lifecycle. Discarded the original core-only assertion tests and consolidated surface coverage from 11 added cases to eight using the existing real Surface/WASM fixture.

Validation: 105 focused core and surface tests pass (97 main tests plus eight takeover cases). The close/reopen regression failed against the original timer gate and passes after the fix. Web typecheck and formatting pass; targeted lint reports one existing new Array warning in unchanged grapheme code. 566 native split-sequence observations matched byte-by-byte Ghostty behavior. Ordinary writes keep one allocation and one VT feed. A synthetic escape-heavy 1 MiB write inside an already-open group measured about 69 ms vs 46 ms before; observing real transitions adds WASM calls only while that pending group remains open.

Independent full-diff Opus 5.5 and GPT-6 Sol reviews found no remaining code blockers. Both require human policy approval and fresh integrated-client proof before merging.

Current-head browser proof is missing. The previous screenshots/recordings were for the old implementation; they are not verification of this rebuilt head. T3’s Browser panel cannot start on this Linux host because required shared libraries are missing, so no fresh capture was obtained. Real Neovim/lazygit, macOS, Windows, iOS, Android and Electron desktop remain unverified. This changes the shared web terminal used by web and desktop; native mobile is unchanged. Local and remote PTY output uses this same rendering path.

Human review is required for the 500 ms recovery duration and the decision to end an unterminated group. This PR fixes partial frame painting; it does not establish that all reported terminal freezes are fixed. No merge is requested.

Models: tests by GPT-6 Astra and implementation by GPT-6.1 Sol through Codex in T3 Code; adversarial reviews by Claude Opus 5.5 and GPT-6 Sol through T3 delegation.

Extract the web synchronization gate from #9027. Add lifecycle and real WASM regression coverage without its streaming, replay, native, or theme changes.

Co-authored-by: Wout Stiens <71498452+StiensWout@users.noreply.github.com>
@github-actions github-actions Bot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:M 30-99 changed lines (additions + deletions). labels Sep 5, 2026
@github-actions

github-actions Bot commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

Thread transfer impact

✅ Thread transfer remains within every enforced ceiling.

Provider Metric Main baseline This PR Impact PR ceiling
Codex Total thread wire 13.5 KiB 13.5 KiB −6 B (−0.0%) 15.1 KiB ✅
Codex Thread snapshot wire 7.0 KiB 7.0 KiB −2 B (−0.0%) 7.3 KiB ✅
Codex Live turn WebSocket wire 6.5 KiB 6.5 KiB −4 B (−0.1%) 7.8 KiB ✅
Codex Live turn WebSocket decoded 57.0 KiB 57.0 KiB 0 B (0.0%) 66.4 KiB ✅
Codex Live turn messages 8 8 0 (0.0%) 21 ✅
Claude Total thread wire 13.8 KiB 13.5 KiB −235 B (−1.7%) 15.1 KiB ✅
Claude Thread snapshot wire 7.0 KiB 7.0 KiB 0 B (0.0%) 7.3 KiB ✅
Claude Live turn WebSocket wire 6.7 KiB 6.5 KiB −235 B (−3.4%) 7.8 KiB ✅
Claude Live turn WebSocket decoded 59.3 KiB 57.8 KiB −1.5 KiB (−2.5%) 66.4 KiB ✅
Claude Live turn messages 10 8 −2 (−20.0%) 21 ✅

Baseline: ce4712d · PR result: 9aacd20 · Source CI: success

Scenario and decoded snapshot size

10 historical turns, 5 command tools per turn, 878.9 KiB retained MCP result per historical turn, and a 1.05 MiB retained result in the measured turn.

  • Codex decoded thread snapshot: 113.8 KiB
  • Claude decoded thread snapshot: 114.5 KiB

Updated in place by a trusted workflow. PR artifacts are strictly validated and never executed.

@macroscopeapp

macroscopeapp Bot commented Sep 5, 2026 •

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Needs Human Approval

Macroscope's review found this PR requires human approval — The PR changes the shared terminal rendering pipeline by suppressing paints and canvas updates during synchronized output, adding escape-aware processing, and forcibly recovering after a 500 ms deadline. Its timing and rendering-state behavior merits human review despite strong focused test coverage.

No code changes detected at dbca234. Prior analysis still applies.

You can add or adjust custom eligibility rules. Learn more.

@juliusmarminge

Copy link
Copy Markdown
Member Author

Current-client verification on 84b99f3f, with the exact two production-file changes from 9aacd204 integrated for the after run.

The disposable fixture runs in the real integrated terminal. Each synchronized group writes FIRST, waits 160 ms, writes SECOND, then closes the group. Before the patch, the recording contains FIRST 02 / SECOND 01. The after recording shows the previous complete frame retained until the next pair is available. This is a controlled terminal-output reproduction in Chromium 152, not a native nvim/lazygit session or proof for every source of flicker in the linked reports.

Before:

Before: terminal displays mismatched frame numbers during a synchronized update

Before recording

After:

After: terminal retains a complete pair of frame numbers

After recording

Recordings use the existing live browser tab, sampled at 20 fps with wall-clock timing preserved. The fixture writes only to its terminal and restores the alternate-screen and cursor modes on exit. No provider turn was started.

The full four-file change and focused tests have been reviewed. This remains human-held: the 500 ms recovery deadline for an unterminated synchronized update is a behavior decision, not a value established by this fixture.

Audited by GPT 6 Astra via Codex in T3 Code.

@shivamhwp

Copy link
Copy Markdown
Collaborator

Note: GPT-6 on behalf of shivam (@shivamhwp).

The synchronized-output timer can expire a new group using the previous group's deadline. Open group A, then 450 ms later deliver A's closing CSI ? 2026 l and B's opening CSI ? 2026 h in the same PTY write. deferSynchronizedRender() only observes that mode 2026 is still active, so it retains A's timer. At 500 ms it ends B, only 50 ms after B began, and paints B's first row above A's second row.

Track the closed-and-reopened group even when both markers arrive in one write, and give the new group its own deadline. Preserve the existing rule that additional output within one unterminated group cannot extend its deadline. The human decision about the 500 ms recovery policy remains separate, and #7790 should remain open for its other reported variants.

@juliusmarminge juliusmarminge added the macroscope-review Opt PRs made by unvouched contributors in for Macroscope review. Vouched contributors auto-reviews label Oct 1, 2026 — with ChatGPT Codex Connector
@juliusmarminge

Copy link
Copy Markdown
Member Author

Taking over this one. I will merge current main into the existing branch, preserve the newer terminal lifecycle, minimize the regression coverage, and address the reported close/reopen deadline bug. The 500 ms recovery policy remains for human review; no merge will be performed.

@juliusmarminge juliusmarminge self-assigned this Oct 19, 2026
@github-actions github-actions Bot added size:L 100-499 changed lines (additions + deletions). and removed size:M 30-99 changed lines (additions + deletions). labels Oct 19, 2026
@juliusmarminge

Copy link
Copy Markdown
Member Author

Maintainer rebuild pushed with current main merged. The same-write close/reopen deadline regression now fails before and passes after; all 105 focused core/surface tests and web typecheck pass. Fresh independent Opus 5.5 and GPT-6 Sol full-diff reviews found no code blockers. The body records measured ANSI-heavy overhead and missing current-head live proof. Human approval of the 500 ms recovery policy remains required; this is unmerged.

@coderabbitai

coderabbitai Bot commented Oct 19, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Path: .coderabbit.config.ts
  • Review profile: CHILL
  • Plan: Team
  • Run ID: 0b75dc1d-8b91-46ae-b3b6-d297394cd2e9
📥 Commits

Reviewing files that changed from the base of the PR and between 5d76d5a and dbca234.

📒 Files selected for processing: 1
  • apps/web/src/terminal/ghostty/surface.ts

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 0 remain after this review.

📝 Walkthrough

Ghostty terminal rendering now waits while synchronized output is active. The surface defers canvas configuration and rendering until output ends or a 500 ms recovery timeout expires. Tests cover split markers, timeout recovery, resize behavior, and cancellation cases.

Changes: Synchronized Output Rendering

Layer / File(s) Summary
Track synchronized-output transitions
apps/web/src/terminal/ghostty/core.ts
GhosttyTerminalCore.write can detect when synchronized output ends and invoke a callback. New methods query and end mode 2026.
Defer and recover surface rendering
apps/web/src/terminal/ghostty/surface.ts, apps/web/src/terminal/ghostty/surface.test.ts
The surface defers rendering and canvas configuration while synchronized output is active. It ends an unterminated update after 500 ms and applies pending canvas configuration when rendering resumes. Tests cover split markers, recovery deadlines, resize, reset, hiding, zero-sized mounts, and disposal.

Priority: ➖ Normal

Estimated code review effort: 3 (Moderate) | ~20 minutes

Change: Bug fix

Sequence Diagram(s)

sequenceDiagram
  participant GhosttyTerminalSurface
  participant GhosttyTerminalCore
  participant RecoveryTimer
  GhosttyTerminalSurface->>GhosttyTerminalCore: write(data, callback)
  GhosttyTerminalCore-->>GhosttyTerminalSurface: synchronized-output completion
  GhosttyTerminalSurface->>RecoveryTimer: schedule 500 ms recovery
  RecoveryTimer->>GhosttyTerminalCore: endSynchronizedOutput()
  RecoveryTimer->>GhosttyTerminalSurface: renderFrame()
Loading

Suggested reviewers: stienswout

Merge Risk: ⚪ Minimal · up to dbca2

No code blocker is established. The 500 ms recovery policy still requires human approval before merge.

Pre-merge checks: Passed 4
Check name Status Explanation
Title check Passed The title clearly identifies the web terminal fix and its main behavior: atomic painting of synchronized terminal updates.
Description check Passed The description covers the problem, implementation, scope, tests, observed results, and known verification gaps. It does not provide a triaged issue or explicit approval link, and it documents that cu…
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
Full details: Description check

Explanation

The description covers the problem, implementation, scope, tests, observed results, and known verification gaps. It does not provide a triaged issue or explicit approval link, and it documents that current-head browser evidence is unavailable, but the overall description is complete and transparent.

✨ Finishing Touches
📝 Generate docstrings
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Comment @coderabbitai help to get the list of available commands.

@juliusmarminge
juliusmarminge merged commit 26338ea into main Oct 20, 2026
28 checks passed
@juliusmarminge
juliusmarminge deleted the audit/ghostty-synchronized-output branch October 20, 2026 01:35
Pipyakas pushed a commit to Pipyakas/t3code that referenced this pull request Oct 21, 2026
)

Co-authored-by: Wout Stiens <71498452+StiensWout@users.noreply.github.com>
(cherry picked from commit 26338ea)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

macroscope-review Opt PRs made by unvouched contributors in for Macroscope review. Vouched contributors auto-reviews size:L 100-499 changed lines (additions + deletions). vouch:trusted PR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants