Skip to content

fix(terminal): use a byte budget for Ghostty scrollback - #8766

Merged
juliusmarminge merged 13 commits into
pingdotgg:mainfrom
zero-two-rafaeltab:t3code/fix-ghostty-scrollback-bytes
Nov 11, 2026
Merged

juliusmarminge merged 13 commits into
pingdotgg:mainfrom
zero-two-rafaeltab:t3code/fix-ghostty-scrollback-bytes

Conversation

@zero-two-rafaeltab

@zero-two-rafaeltab zero-two-rafaeltab commented Aug 30, 2026 •

Copy link
Copy Markdown
Contributor

Problem

Ghostty interprets the pinned ABI’s max_scrollback as bytes, although T3 named the 10,000 value as rows. Running the reporter’s 12,000-line command in an attached web terminal on current main retained only about 700 history rows at the reproduced width, silently discarding older output.

Change

Use an explicit 10,000,000-byte budget for web/desktop and Android, matching the pinned Ghostty application default. Retention remains width- and content-dependent; this does not promise exactly 10,000 rows or change the ABI. A production-core/WASM regression checks retained history, and a committed numbered-output fixture reproduces the report in a real terminal.

Credit to @zero-two-rafaeltab for identifying the mismatch and contributing the fix.

Scope and approval

Fixes https://git.cafe/pingdotgg/t3code/issues/8701
GitCafe: https://git.cafe/pingdotgg/t3code/issues/8701
Triaged bug via the automated issue flow. This continues the existing contributor PR and maintainer takeover; it adds no setting, contract or persisted state. iOS configuration and the separate client/server transcript limits are unchanged.

Verification

  • Red: the production-constructor regression on main fails with expected 602 to be greater than 1000.
  • Green: 53 focused Ghostty tests across four files pass on the integrated implementation.
  • Web package typecheck exits 0. Targeted lint and formatting pass with one existing no-new-array warning.
  • The committed fixture passes bash -n, emits an ESC reset, all 12,000 numbered markers, and the final column count (12,001 newlines; approximately 240 KB).
  • The required local knip:check did not finish under VM contention; three attempts were stopped, so it is not reported as passed.
  • Published-head checks: GitCafe reports 30 successful, 0 pending and 0 failing checks on 3bbf1c3918. The four approval-held workflows were approved before this result.

Evidence

Complete evidence report and upload verification.

Issue #8701, PR #8766. Original proof matrix.

BASE 3b95e7831b (3b95e7831b11b17627dff16843bc087bfca397ec); final HEAD 3bbf1c3918 (3bbf1c3918c120dfbbe457807f3a9a4f1a760d22). This report retains all four original matched pairs and eight original recordings, then adds stamped C1 test runs, wide-dark interaction recordings and a controlled reload pair. Native and remote verification remain incomplete; W1 is a tested interaction subset, not a full native-input pass.

Environment, timestamps and procedure

Recovery began 2026-10-22 at approximately 08:19 UTC. Own T3 collaborative server-browser tab on Linux x86_64; Chrome 154.0.0.0; DPR 2; visualViewport.scale=1. Reused the lead-owned isolated dev server (vp run dev --no-seed), web 5876/server 13916, worktree /home/ubuntu/wt/issue-8701/.t3. No server launched/stopped, no source edits, commits, pushes, PR-body writes, or direct database writes. The app's normal terminal lifecycle persisted isolated app state. Pairing credentials/private logs were excluded from uploads.

Font preferences: advanced typography unset (off), code size 13, code family empty. Thus the terminal uses 13 CSS px with the default SF Mono/SFMono-Regular/Menlo/Consolas/Liberation Mono and glyph fallback stack. A browser canvas measurement using that stack gave M advance 7.80126953125 CSS px and ascent 9; the exact platform face selected by Chromium was not independently identified. Same browser and unchanged preferences across all eight retained captures. Terminal column counts are independently visible in PTY reporter output: 183 wide, 48 narrow. Fifteen visible text rows; row advance 18 CSS px inferred from renderer metrics and exported rows. No font scaling or zoom change.

CSS viewports: wide 1440×900 with canvas (0,621) 1440×279; narrow 390×844 with canvas (0,565) 390×279. Whole-surface still exports are 1280×800 wide and 780×1688 narrow (preview export scaling); videos are 2880×1800 wide and 780×1688 narrow. No cropping, registration, resizing or masks were applied to the compared stills.

Each retained run: set saved light/dark theme with follow-system off; fully navigate/reload the app; wait for terminal input; close main sidebar and notifications through their DOM controls; start recording; paste bash /tmp/issue-8701/scrollback-repro.sh with ClipboardEvent/DataTransfer into the actual Terminal input; press Enter. This is the unchanged committed reporter from HEAD, copied outside the worktree before BASE checkout. It emits RIS, 12,000 numbered markers and T3-END with tput columns. After output settles, focus the actual scrollbar, press End, capture end, press Home, capture earliest, press End again, capture return, and leave 2.5 seconds for the recording endpoint. No synthetic terminal output or injected cosmetic UI. Navigation was keyboard End/Home, not a wheel/trackpad test.

HEAD captures preceded BASE; checkout BASE detached, then full reload for a fresh core. Restored fix branch, fully reloaded and repeated HEAD wide-light last. Earlier exploratory wide-light recording is discarded. Retained timestamps and scrollbar values are in recovery-capture.json. All eight videos were decoded with ffmpeg; their final frames visibly contain T3-END and the shell prompt, proving return to end. Stills show Home at scrollbar position zero. This avoids the previous run's missing final frame.

Matched whole-surface comparisons

Diff0: absolute per-channel difference amplified 8×, black for identical pixels; counted when max RGB delta >0. Diff16: per-channel binary threshold >16/255, counted when max RGB delta >16. These images deliberately compare different retained line numbers at the same earliest-scroll position; they are behavioral evidence, not an assertion of visual equality.

CSS fixture Before — BASE After — final HEAD Diff0 ×8 Diff16 (>16/255)
Revision 3b95e7831b 3bbf1c3918 BASE → HEAD BASE → HEAD
wide-light; earliest 11656 → 08030 Before wide-light earliest 11656 After wide-light earliest 08030 Amplified zero-threshold difference wide-light Threshold 16 difference wide-light
wide-dark; earliest 11656 → 08030 Before wide-dark earliest 11656 After wide-dark earliest 08030 Amplified zero-threshold difference wide-dark Threshold 16 difference wide-dark
narrow-light; earliest 10715 → 00001 Before narrow-light earliest 10715 After narrow-light earliest 00001 Amplified zero-threshold difference narrow-light Threshold 16 difference narrow-light
narrow-dark; earliest 10715 → 00001 Before narrow-dark earliest 10715 After narrow-dark earliest 00001 Amplified zero-threshold difference narrow-dark Threshold 16 difference narrow-dark
Fixture BASE scrollbar max HEAD scrollbar max Diff0 pixels Diff16 pixels
wide-light 332 3958 3705 / 1024000 3037 / 1024000
wide-dark 332 3958 3812 / 1024000 3111 / 1024000
narrow-light 1273 11987 12547 / 1316640 11388 / 1316640
narrow-dark 1273 11987 12918 / 1316640 11655 / 1316640

diff-stats.json. All changed pixels are within terminal text: wide difference bounding box (101,559)–(135,792), narrow (227,1146)–(304,1669), in exported raster coordinates. Pixels above the terminal are exactly identical in every matched pair. Visible differences are earlier retained line-number digits and their antialiasing. Theme, layout, composer, terminal bounds and focused top scrollbar position match. At Home, both scrollbar thumbs hit the same minimum size; the increased range is recorded in metadata. No other pixel differences were observed in these retained stills. No clock freeze, cosmetic correction, or diff exclusion was used.

Interaction recordings and end-state evidence

Each original video shows the fresh PTY run, Home/earliest and visible return to End. The following timings replace the prior generic narration. Videos are variable-frame-rate: frames were decoded sequentially and their presentation timestamps inspected, rather than inferring time from frame number or seeking as if frame rate were constant. Entries below are first decoded frames visibly showing each state; a frame remains displayed until the next presentation timestamp. Whole-frame originals are unchanged.

Original recording Streaming output observed First settled End frame Earliest state begins Returned End begins
BASE wide-light ~1.18 s 1.30 s 4.67 s (11656) 5.51 s
HEAD wide-light ~2.27 s 2.34 s 6.19 s (08030) 7.65 s
BASE wide-dark ~2.45–2.77 s 2.92 s 6.97 s (11656) 7.11 s
HEAD wide-dark ~1.79 s 1.89 s 5.29 s (08030) 6.29 s
BASE narrow-light ~1.17 s 1.71 s 4.10 s (10715) 5.05 s
HEAD narrow-light ~0.52–0.67 s 0.75 s 4.12 s (00001) 5.24 s
BASE narrow-dark ~0.74–1.18 s 1.66 s 4.09 s (10715) 5.27 s
HEAD narrow-dark ~0.58–0.77 s 0.83 s 4.05 s (00001) 5.19 s

The BASE wide-dark earliest phase is brief (~0.14 s); its still remains the readable proof. These original recordings exercise keyboard navigation, with no native wheel/trackpad claim.

Fixture Before video After video Before return still After return still
wide-light (7.42s / 10.35s) recovery-base-wide-light.mp4 recovery-head-wide-light.mp4 recovery-base-wide-light-return.png recovery-head-wide-light-return.png
wide-dark (9.05s / 8.88s) recovery-base-wide-dark.mp4 recovery-head-wide-dark.mp4 recovery-base-wide-dark-return.png recovery-head-wide-dark-return.png
narrow-light (7.94s / 8.08s) recovery-base-narrow-light.mp4 recovery-head-narrow-light.mp4 recovery-base-narrow-light-return.png recovery-head-narrow-light-return.png
narrow-dark (7.98s / 7.83s) recovery-base-narrow-dark.mp4 recovery-head-narrow-dark.mp4 recovery-base-narrow-dark-return.png recovery-head-narrow-dark-return.png

Inspection sheet: final decoded frame of each recording, showing T3-END and shell prompt

Supplement — stamped C1 runs and bounded interaction/reload evidence

Supplement performed 2026-10-22, 08:36:54–08:55 UTC, using the same existing isolated web 5876/server 13916. Own new T3 preview tab; no server lifecycle action, source edits, commits, pushes, PR writes or direct database fixture writes. The exact committed reporter had already been copied to /tmp/issue-8701/scrollback-repro.sh; it was pasted into the real terminal textarea through ClipboardEvent/DataTransfer and run by the existing PTY. Temporary BASE/RED checkouts were restored to clean final HEAD.

C1. Reran the existing focused test from apps/web on RED commit 5dea8693af, which contains the regression test on BASE, then final HEAD. Each transcript prints git rev-parse HEAD, the command and exit code. Command: vp test run --project unit src/terminal/ghostty/core.test.ts -t 'retains thousands of rows of scrollback'. RED: expected 602 to be greater than 1000, exit 1; HEAD: one selected test passed, exit 0 (16 unselected tests skipped by -t in each run). No implementation edits. Stamped RED transcript; stamped final-HEAD transcript.

W1 replacement pair. Same whole-surface CSS viewport 1440×900, DPR 2, scale 1; dark saved theme with follow-system off; advanced typography off, code size 13, unchanged default font stack; terminal canvas (0,621), 1440×279, 183 columns and 15 visible rows. Main sidebar, details card and notifications closed through existing controls. Native preview drag moves the actual scrollbar thumb to the center of its track. Then preview_evaluate dispatches a canvas WheelEvent with deltaY=-9000, deltaMode=0, followed by focused-scrollbar Home and End. This dispatch exercises the terminal wheel handler but is synthetic DOM input, not native wheel/trackpad input. On HEAD the thumb value traverses 3958 → 1979 → 1479 → 0 → 3958; on BASE 332 → 166 → 0 → 332. BASE reaches earliest on the wheel dispatch before Home. Drag to the exact top was not available through the locator-to-locator drag API; Home establishes earliest.

Measured decoded video timestamps, above the pair: BASE shows streaming at ~1.09–1.19 s, End at 1.25 s, dragged midpoint marker 11822 at 3.12 s, dispatched-wheel earliest 11656 at 4.48 s, returned End at 8.69 s. HEAD shows streaming at 1.68 s, End at 1.89 s, dragged midpoint marker 10009 at 11.15 s, dispatched-wheel marker 09509 at 12.23 s, Home/earliest 08030 at 13.82 s, returned End at 16.07 s. Both decoded final frames show T3-END and the existing shell prompt. Different tool latency changes elapsed time, not viewport or reporter.

Before — BASE interaction After — HEAD interaction
BASE drag + synthetic wheel + Home/End video HEAD drag + synthetic wheel + Home/End video
Before BASE final End frame After HEAD final End frame

Interaction action metadata. W1 disposition: keyboard + thumb-to-midpoint drag + synthetic-wheel-handler subset complete. Native browser wheel, physical trackpad inertia, drag-to-earliest, and corresponding gestures in narrow/light fixtures remain unverified. preview_scroll was attempted on the canvas and after hovering it, but did not change the terminal: the tool calls DOM scrollBy, not wheel input. This is a tool coverage gap, not evidence that native wheel scrolling fails.

R1 controlled reload. Each revision ran the reporter once, captured live earliest, returned End, fully navigated to the same thread URL, waited for the actual terminal input, closed the reopened main sidebar/dismissed the normal notification, and captured End then Home without issuing another PTY command. Same existing PTY and thread, no transcript reseeding after reload. The videos include normal reload splash/loading. The sidebar initially mounts the terminal at 1184 CSS px before closure restores 1440; therefore this is a real reload-plus-normal-layout-restoration fixture, not a claim of constant terminal width during mount. Both after-reload retained stills have canvas (0,621), 1440×279.

Decoded video narration: BASE live output ~1.57–1.67 s, live End 1.71 s, midpoint 11.03 s, synthetic-wheel earliest 11.56 s, live return End 24.22 s, reload begins ~24.63 s, restored End ~46.09 s, after-reload Home 11427 at 48.58 s, final End 58.90 s. HEAD live output ~2.26–2.37 s, live End 2.47 s, midpoint 11.78 s, wheel middle 12.24 s, live return End 24.80 s, reload begins ~25.08 s, restored End 46.74 s, after-reload Home 07003 at 47.24 s, final End 47.32 s. HEAD's after-reload Home is a short decoded frame (~0.08 s); its retained still is the readable assertion. The live Home action in this longer HEAD recording was too brief to encode and is not claimed as visible; the separate replacement interaction pair above supplies that proof.

Before — BASE controlled reload After — HEAD controlled reload
BASE live output → reload without reseeding video HEAD live output → reload without reseeding video
Before BASE earliest after reload: marker 11427 After HEAD earliest after reload: marker 07003
Diff0 ×8 Diff16 (>16/255)
Controlled reload zero-threshold amplified difference Controlled reload threshold-16 difference

After-reload scrollbar maxima: BASE 561; HEAD 4985; Home value zero in both. Whole-surface still diff: 3652 pixels at >0, 3049 at >16, out of 1,024,000; bounding box (101,559)–(134,791). Differences are terminal line-number digits and antialiasing; all pixels above the terminal are identical. No cropping, resizing, masks or registration. Reload diff statistics; reload action and artifact metadata.

R1 is verified only for this local web full reload and normal sidebar restoration. Retained output is bounded: earliest after reload 11427 → 07003, not marker 00001. The default server replay of about 5,000 history lines, live engine byte budget and client transcript cap remain separate limits. This does not prove full 12,000-line replay, native remount, remote disconnect/reconnect or transport recovery. BASE and HEAD final decoded frames both show T3-END cols=183 and the shell prompt: BASE reload final frame; HEAD reload final frame.

Sequentially decoded frame inspection sheets for the eight original videos: wide-light, wide-dark, narrow-light, narrow-dark. These are labeled analysis crops only; the original full-surface videos and stills above remain the evidence.

Other evidence and full matrix disposition

Lead's earlier 1280×800 comparison reported oldest marker 11285 before versus 6189 fixed. It is a separate geometry and must not be substituted into the four matched fixtures above: current-main-before.png, current-main-after.png. Existing focused red test reports retained history 602, failing >1000; green run reports 53 tests passed across four files: red-test.log, green-test.log. Those earlier logs were retained; the focused SHA-stamped rerun above supersedes them for C1 provenance. No knip success is claimed: lead stopped its stalled retry at 08:22 UTC to reduce memory pressure.

Matrix row Disposition
C1 shared engine regression VERIFIED: SHA-stamped focused RED failure (602, exit 1) → final HEAD pass (exit 0).
C2 config/scope Source checked: shared WASM core and Android JNI pass 10,000,000 bytes to max_scrollback. No iOS source change.
W1 local web SUBSET COMPLETE: four matched still/keyboard fixtures plus wide-dark thumb-to-midpoint drag and synthetic DOM wheel-handler pair. Native wheel/trackpad, drag-to-earliest and narrow/light gesture coverage remain UNVERIFIED.
W2 hosted web/remote UNVERIFIED: no authorized remote/relay/tunnel fixture available. Local web is not proof of remote mode.
D1 Electron wide UNVERIFIED: native Electron session/display infrastructure unavailable in this run.
D2 Electron narrow/resize UNVERIFIED: same native infrastructure gap.
A1 native Android portrait UNVERIFIED: built client/device unavailable. Mobile-width web is not native Android evidence.
A2 native Android rotation UNVERIFIED: no native device or rotation run.
I1 iOS UNCHANGED by final diff; native regression control not executed.
R1 replay/remount VERIFIED SUBSET: local full reload of same PTY without reseeding, wide-dark, normal sidebar restoration; 11427 → 07003. Native remount, remote reconnect and complete-run replay remain UNVERIFIED.

Live engine capacity, 512 KiB client transcript and server replay (~5,000 history lines by default) remain separate limits. The wide fixture still truncates at 08030; the fix increases the byte budget and does not guarantee 10,000 retained rows at every width. Narrow retains marker 00001 for this fixture. No promise of preserving the entire run through replay/remount.

Unverified surfaces: cannot-verify android, cannot-verify desktop; hosted remote, physical wheel/trackpad input, and separate client-only remount were not exercised.

Models: Claude Opus 5.5 and GPT-6.1-Sol through Claude Code/Codex harnesses in T3 Code.

The pinned Ghostty ABI treats max_scrollback as bytes, but the current 10,000-byte value collapses an 80-column terminal to its minimum allocation.

Add a focused vendored-WASM regression that records the failing retention contract without promising an exact row count.

Generated with GPT-5.6-Sol via Codex harness.
@coderabbitai

coderabbitai Bot commented Aug 30, 2026 •

Copy link
Copy Markdown
Contributor

Explain Change →

No actionable comments were generated in the recent review.

Walkthrough

The web and Android terminal implementations now use a 10,000,000-byte scrollback limit instead of a 10,000-row limit. A web test checks that Ghostty retains more than 1,000 rows beyond the visible range.

Changes: Scrollback limit

Layer / File(s) Summary
Set and verify the byte-based limit
apps/web/src/terminal/ghostty/core.ts, apps/mobile/modules/t3-terminal/android/src/main/cpp/t3_terminal_jni.cpp, apps/web/src/terminal/ghostty/core.test.ts
The web and Android implementations pass a 10,000,000-byte limit as max_scrollback. The web test writes 1,200 lines and checks that the retained scrollback exceeds 1,000 rows beyond the visible range.

Estimated code review effort: 2 (Simple) | ~10 minutes

Change: Bug fix

Possibly related issues

Possibly related PRs

  • pingdotgg/t3code#30151: The PR changes the same Ghostty scrollback configuration and adds a web regression test for increased retention.

Suggested reviewers: juliusmarminge, stienswout

Merge Risk: Minimal · up to b67ba

Scrollback now uses a byte budget rather than a row count. No actionable merge-blocking risk was identified.

Pre-merge checks: Passed 4

Check name Status Explanation
Title check Passed The title clearly and concisely describes the main change: replacing the row-based Ghostty scrollback setting with a byte budget.
Description check Passed The description includes all required sections. It explains the problem, change, scope, issue reference, verification results, limitations, and unverified surfaces in detail.
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.
Finishing Touches

Generate unit tests (beta)

  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts
Recent review info

Run configuration

Commits

Reviewing files that changed from the base of the PR and between 362ef9a and b67ba30.

Files selected for processing: 3

  • apps/mobile/modules/t3-terminal/android/src/main/cpp/t3_terminal_jni.cpp
  • apps/web/src/terminal/ghostty/core.test.ts
  • apps/web/src/terminal/ghostty/core.ts

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

@github-actions github-actions Bot added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:S 10-29 changed lines (additions + deletions). labels Aug 30, 2026
@rafaeltab

Copy link
Copy Markdown

PR made by my agent under my supervision. I am doing a manual check of this PR and the linked PR on web, desktop and android before publishing.

T3 names the Ghostty scrollback setting as rows and passes 10,000, although the pinned ABI interprets it as bytes.

Rename the constant for bytes and use a 32 MiB budget for T3 embedded terminals, whose scrollback remains uncompressed.

Generated with GPT-5.6-Sol via Codex harness.
Android also names and configures Ghostty scrollback as 10,000 rows even though the pinned native ABI consumes a byte limit.

Align the native constant with web by naming bytes and passing the same 32 MiB uncompressed budget.

Generated with GPT-5.6-Sol via Codex harness.
@zero-two-rafaeltab
zero-two-rafaeltab force-pushed the t3code/fix-ghostty-scrollback-bytes branch from de1c7a4 to 2c4734c Compare August 30, 2026 21:45
@rafaeltab

Copy link
Copy Markdown

Manually verified that this is working on:
web (manjaro linux with zen browser)
desktop (manjaro linux)
android

@zero-two-rafaeltab
zero-two-rafaeltab marked this pull request as ready for review August 30, 2026 22:41
@macroscopeapp

macroscopeapp Bot commented Aug 30, 2026 •

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Needs Human Approval

Macroscope's review found this PR requires human approval — The change increases the default Ghostty scrollback budget to 10 MB on web and Android, retaining more terminal output with a larger potential memory footprint. A human must confirm this default retention and memory tradeoff.

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

@juliusmarminge juliusmarminge added the macroscope-review Opt PRs made by unvouched contributors in for Macroscope review. Vouched contributors auto-reviews label Sep 8, 2026
@shivamhwp

Copy link
Copy Markdown
Collaborator

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

The new ABI test writes 32 * 1024 * 1024 directly into its own options. Reverting the production core constant to 10,000 therefore leaves this test green. Please add the history-retention regression through GhosttyTerminalCore.create and write, so it exercises the budget the app actually configures. Keep the ABI check if it provides separate value. The distinction between a byte budget and a guaranteed line count should remain explicit.

The direct ABI regression proves Ghostty retention behavior but supplies its own byte budget, so it cannot catch a regression in the app constructor.

Exercise the real GhosttyTerminalCore creation and write path with a smaller 1,200-line case, while preserving the direct ABI coverage.

Generated with GPT-5.6-Sol via Codex harness.
@zero-two-rafaeltab

Copy link
Copy Markdown
Contributor Author

Heads-up, just in case it wasn't clear commit f186281 fixed shivamhwp's comment.

@maria-rcks

Copy link
Copy Markdown
Collaborator

Note

Written by claude-opus-5-5 on behalf of Maria

Hi! We are cleaning up open PRs, and this one appears to have been created with an older model (gpt-5.6-codex). If this change is really important, we recommend rebuilding the PR with a newer model if possible.

@maria-rcks maria-rcks closed this Oct 11, 2026
@maria-rcks

Copy link
Copy Markdown
Collaborator

Note

Written by claude-opus-5-5 on behalf of Maria

Reopening, this was closed by mistake. Sorry for the noise!

@maria-rcks maria-rcks reopened this Oct 11, 2026
@juliusmarminge juliusmarminge self-assigned this Oct 19, 2026
@juliusmarminge

Copy link
Copy Markdown
Member

Taking over this one. I'll rebuild it to the repo's current conventions, trim the tests to the minimum, and push to this branch.

juliusmarminge and others added 4 commits October 19, 2026 01:32
The pinned libghostty-vt reads max_scrollback as bytes, so the old 10,000
kept about 600 rows at 80 columns. Use Ghostty's own 10 MB default on web,
desktop and Android, and keep one regression test against the shipped WASM.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Keep the 10,000,000-byte budget matching the pinned Ghostty application default rather than the proposed 32 MiB. The existing regression proves improved retention; this byte budget does not guarantee a row count.

Remove unmeasured resize-performance and approximate row-count claims from the comments.
@saphid

saphid commented Oct 20, 2026

Copy link
Copy Markdown
Contributor

I independently checked the focused web scrollback fix on BASE 9077e818 before running the reviewed HEAD 3a18c58f.

With the production GhosttyTerminalCore and pinned Ghostty WASM at 80 columns × 10 rows, the same 1,200-line fixture retained 602 rows on BASE (the >1,000-row assertion failed) and 1,191 on HEAD (passed). No full test-suite claim: this directly exercises the production core/WASM through a small runner.

I then captured the production core and canvas renderer in a real browser with the same 12,000 numbered lines, dimensions, and scroll-to-top operation. BASE retained 800 rows, starting at line 11192; HEAD retained 9,046 rows, starting at line 02946. This proves improved retention in this fixture, not a guarantee of exactly 10,000 lines.

Before — BASE

BASE scrollback

After — HEAD

HEAD scrollback

The complete three-file source review found no demonstrated blocker in the unit correction. The measured small-run peak RSS was 70,980 KiB on BASE and 71,964 KiB on HEAD; that does not establish a strict process-memory bound or the effect of many simultaneous terminals.

Still unverified: native Android, an installed desktop client, real PTY/server replay, and constrained-device or many-terminal memory behavior. The larger per-terminal default still has the existing maintainer-approval gate. This is scoped web reproduction evidence, not full multi-surface or merge-readiness approval. It follows the verification guidance.

Capture method, chronology, and limitations. Screenshot assets were downloaded and byte-verified after upload.

@juliusmarminge juliusmarminge added the bugfix Fix PR opened by an issue lead; enters the PR loop label Oct 22, 2026 — with GitCafe Production
@juliusmarminge juliusmarminge added the pr:needs-julius Needs Julius: route gate, unverifiable surface, or blocked label Oct 22, 2026
juliusmarminge and others added 3 commits October 22, 2026 06:42
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Brings in pingdotgg#8766's Ghostty scrollback byte budget fix by zero-two-rafaeltab.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions github-actions Bot added size:XXL 1,000+ changed lines (additions + deletions). and removed size:S 10-29 changed lines (additions + deletions). labels Oct 22, 2026
@juliusmarminge juliusmarminge changed the title fix(terminal): mitigate limited Ghostty scrollback fix(terminal): use a byte budget for Ghostty scrollback Oct 22, 2026

Copy link
Copy Markdown
Member

The 3,333-file aggregate is the old-head → new-head push range, which incorporates 941 main commits. The current-main PR comparison has merge-base 3b95e7831b and changes four files, +50/−4, at 3bbf1c3918. GitHub's advertised synthetic merge has the same four-file delta against its first parent. Macroscope configuration, release infrastructure and static-analysis suppressions are absent from that PR delta.

The existing contributor history is preserved. Two fresh reviews found no code defects; the focused tests and real-app evidence are in Verification. Android and Electron runtime checks remain unverified. Functional CI is also unverified: GitCafe reports ten successful metadata/bot contexts but no CI runs for this head.

…rollback-bytes

# Conflicts:
#	apps/web/src/terminal/ghostty/core.ts
AI-assisted; verified with the tests and tools listed above.
@github-actions github-actions Bot added size:S 10-29 changed lines (additions + deletions). 📱 Native Change Changes the native fingerprint; merging blocks production OTAs until a new store build ships. and removed size:XXL 1,000+ changed lines (additions + deletions). labels Nov 11, 2026
@juliusmarminge

Copy link
Copy Markdown
Member

Evidence

In the real web app, run the 12,000 numbered-line reproduction, wait for T3-END, then jump to the oldest retained output with Ctrl+Shift+Home. Repeat in a fresh terminal at a narrow width. Both sides use fresh synthetic state, Chromium, light theme, and matching viewports. Recordings start after shell setup.

Environment Before After
Commit 362ef9a88a b67ba3000c
Web, 1280×800 Before: oldest marker 11285 After: oldest marker 06189
Wide interaction Before recording After recording
Web, 680×800 Before: oldest marker 10681 After: oldest marker 00713
Narrow interaction Before recording After recording

Head retains substantially earlier output at both widths; retention remains width-dependent. scenarios:prove exited 0: fails on base, passes on head.

Scenario source

AI-assisted; verified with the tests and tools listed above.

Not shown:

  • Android's native terminal change was not exercised; this proof covers the web client.
  • Electron was not launched separately; desktop-shell behavior was not verified.
  • Reload retention and other content types were not exercised; captures cover the supplied numbered ASCII output.

@juliusmarminge
juliusmarminge merged commit dab06c0 into pingdotgg:main Nov 11, 2026
35 checks passed
github-actions Bot added a commit to omarcresp/t3code-flake that referenced this pull request Nov 11, 2026
**v0.1.10-nightly.20261111.4529** · 54 changes from 29 contributors

- **Find in browser.** Search pages in the desktop integrated browser with Cmd+F or Ctrl+F.
- **GitLab overview threads.** Resolve merge request overview threads from Summary and Timeline.
- **Faster worktree updates.** Checkpoint captures and status refreshes avoid rereading every file in newly created worktrees.
- **Imported session history.** Imported agent sessions now show their existing history instead of opening as empty threads.
- **SSH reconnect prompts.** Expired SSH verification prompts no longer stack up and force an app restart.
- **Large reasoning traces.** Opening a thread with a large collapsed reasoning trace no longer freezes the app.
- **Terminal scrollback.** Terminal history retains substantially more output instead of silently discarding older lines.

### Plus 45 more bug fixes and performance improvements.

<details>
<summary>Full changelog</summary>

## What's Changed
* fix(server): new worktrees refresh their index once, so captures and status stop re-hashing every file by @josephv123 in pingdotgg/t3code#21341
* fix(usage): Grok and Cursor usage limits show pace and the even-spend line by @wookiist in pingdotgg/t3code#21897
* fix(web): retain diff selection across navigation by @aleo113 in pingdotgg/t3code#22347
* fix(mobile): subscription widget no longer expires on a window that already reset by @juliusmarminge in pingdotgg/t3code#22993
* fix(opencode): verify CLI updates when loopback server is still running by @intrepid37 in pingdotgg/t3code#22839
* fix(usage): Claude subagent output is no longer undercounted by @rfrost-xyz in pingdotgg/t3code#20276
* fix(server): a remote update points the service unit at the runtime it committed by @Marve10s in pingdotgg/t3code#19652
* fix(server): a limited provider's queue stays held after another provider's turn succeeds by @Marve10s in pingdotgg/t3code#22425
* fix(shared): keep spreadsheet shorthand as inline code by @juliusmarminge in pingdotgg/t3code#23203
* fix(web): offer retry while an environment reconnects by @juliusmarminge in pingdotgg/t3code#23194
* fix(server): custom Codex providers no longer report native quota by @fixfon in pingdotgg/t3code#18622
* fix(desktop): Cmd+F now searches the page in the integrated browser by @armanarutiunov in pingdotgg/t3code#22960
* fix(desktop): an SSH password prompt that ended leaves the dialog instead of stacking by @Umais-Adeed in pingdotgg/t3code#34192
* fix(web): render Claude insight blocks as a callout by @macodev00 in pingdotgg/t3code#22239
* fix(scheduler): show short server timezone on fixed-time labels by @shawk08033 in pingdotgg/t3code#23111
* fix(web): focus the primary action when confirmation dialogs open by @rgrvlsk in pingdotgg/t3code#18187
* fix(web): Changes row matches the diff comparison by @whoisaldo in pingdotgg/t3code#19553
* fix(server): imported ACP sessions show their native history by @only21mil in pingdotgg/t3code#22155
* fix(preview): always return evaluated results by value by @juliusmarminge in pingdotgg/t3code#22948
* fix(provider-core): a model reply of "title" no longer replaces the thread title by @juliusmarminge in pingdotgg/t3code#25743
* fix(desktop,web): Open in runs locally for a background service sharing the desktop's T3 home by @KinanLak in pingdotgg/t3code#21661
* fix(web): question answers submit while normal sends are blocked by @t3dotgg in pingdotgg/t3code#22874
* fix(server): cancelling a background-only delegated task reports it interrupted by @only21mil in pingdotgg/t3code#21676
* fix(web): fall back to a readable server for shared repositories by @sanjaibalajee in pingdotgg/t3code#21530
* fix(web): composer follows an optionless thread instead of the settings default by @f4llenz in pingdotgg/t3code#16546
* fix(web): don't save retention age until committed by @macodev00 in pingdotgg/t3code#22208
* fix(server): one dropped probe no longer lists a live linked peer as unreachable by @juliusmarminge in pingdotgg/t3code#34208
* fix(server): preview automation reports ambiguous selectors instead of calling them invalid by @juliusmarminge in pingdotgg/t3code#34205
* fix(opencode): a context overflow that OpenCode compacts no longer fails the turn by @juliusmarminge in pingdotgg/t3code#26743
* fix(web): Ctrl + "+" zooms on keyboard layouts with a dedicated plus key by @juliusmarminge in pingdotgg/t3code#26408
* fix(ci): release version bump push retries when main moves by @t3dotgg in pingdotgg/t3code#34326
* fix(server): CLI commands no longer migrate the database of a running server by @juliusmarminge in pingdotgg/t3code#26763
* fix(web): keep saved default model when changing effort by @macodev00 in pingdotgg/t3code#22193
* fix(web): reveal expanded work rows hidden behind the composer by @ln-bodicherla in pingdotgg/t3code#21968
* fix(composer): steer instead of queueing during automatic compaction by @NikitaMGrimm in pingdotgg/t3code#24138
* feat(gitlab): resolve overview threads from Summary and Timeline by @TonybynMp4 in pingdotgg/t3code#30575
* fix(web): pull request search keeps partial-word matches after GitHub answers by @juliusmarminge in pingdotgg/t3code#26992
* fix(server): Claude subagents no longer show the parent's effort by @juansuaya17 in pingdotgg/t3code#17042
* fix(server): runs cancelled by a server restart say why in the thread by @juliusmarminge in pingdotgg/t3code#27192
* fix(client-runtime): server updates stop waiting 32 minutes when the host returns without this update's outcome by @juliusmarminge in pingdotgg/t3code#27275
* fix(web): Stop shows a pending state and repeat clicks no longer resume by @voida6 in pingdotgg/t3code#20809
* fix(web): collapsed reasoning previews no longer process the whole block by @juliusmarminge in pingdotgg/t3code#23001
* fix(server): storage cleanup skips stale worktree paths under a regular file by @juliusmarminge in pingdotgg/t3code#26594
* fix(terminal): use a byte budget for Ghostty scrollback by @zero-two-rafaeltab in pingdotgg/t3code#8766
* fix(server): storage cleanup keeps worktrees whose file listing exceeds the inspection limit by @juliusmarminge in pingdotgg/t3code#26593
* fix(server): delete temporary ACP health probe sessions by @juliusmarminge in pingdotgg/t3code#25397
* perf(client): streamed text no longer republishes thread queue and checkpoints by @juliusmarminge in pingdotgg/t3code#27425
* fix(web): convert composer list markers completed later by @juliusmarminge in pingdotgg/t3code#22997
* test: remove tests that repeat another case or restate the source by @shivamhwp in pingdotgg/t3code#34170
* fix(server): drop stale queued pull-request watch wakes by @Sinan53x in pingdotgg/t3code#21239
* fix(web): preview recordings stay decodable after a viewport resize by @MansTomb in pingdotgg/t3code#10495
* fix(server): only offer claude update for native Claude installs by @AI091 in pingdotgg/t3code#14170
* fix(server): tell outside agents which MCP tools need a thread and that t3_thread_list takes allProjects by @juliusmarminge in pingdotgg/t3code#26525

## New Contributors
* @wookiist made their first contribution in pingdotgg/t3code#21897
* @aleo113 made their first contribution in pingdotgg/t3code#22347
* @intrepid37 made their first contribution in pingdotgg/t3code#22839
* @rfrost-xyz made their first contribution in pingdotgg/t3code#20276
* @armanarutiunov made their first contribution in pingdotgg/t3code#22960
* @KinanLak made their first contribution in pingdotgg/t3code#21661
* @sanjaibalajee made their first contribution in pingdotgg/t3code#21530
* @ln-bodicherla made their first contribution in pingdotgg/t3code#21968
* @juansuaya17 made their first contribution in pingdotgg/t3code#17042
* @zero-two-rafaeltab made their first contribution in pingdotgg/t3code#8766
* @MansTomb made their first contribution in pingdotgg/t3code#10495
* @AI091 made their first contribution in pingdotgg/t3code#14170

**Full Changelog**: pingdotgg/t3code@v0.1.9-nightly.20261111.4515...v0.1.10-nightly.20261111.4529

</details>


Upstream release: https://github.com/pingdotgg/t3code/releases/tag/v0.1.10-nightly.20261111.4529
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bugfix Fix PR opened by an issue lead; enters the PR loop macroscope-review Opt PRs made by unvouched contributors in for Macroscope review. Vouched contributors auto-reviews 📱 Native Change Changes the native fingerprint; merging blocks production OTAs until a new store build ships. pr:needs-julius Needs Julius: route gate, unverifiable surface, or blocked size:S 10-29 changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants