Skip to content

fix(codex): smaller first-frame write budget + scroll-up grace for codex#117

Merged
Ark0N merged 1 commit into
Ark0N:masterfrom
aakhter:pr/cod-86-codex-frontend
Jun 10, 2026
Merged

fix(codex): smaller first-frame write budget + scroll-up grace for codex#117
Ark0N merged 1 commit into
Ark0N:masterfrom
aakhter:pr/cod-86-codex-frontend

Conversation

@aakhter

@aakhter aakhter commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

Problem

Two render annoyances in Codex sessions:

  1. Renderer stalls. Codex's TUI emits dense synchronized redraws during thinking / high-effort phases. The terminal write pipeline used a single 64KB first-frame budget for everything, and a large codex frame could block the main thread long enough to drop visible frames.
  2. Viewport snaps back while scrolling. Codex emits a high-frequency • Working (Ns) status redraw. Because the user was "at the bottom" when the redraw arrived, sticky-scroll yanked the viewport back to the bottom on every tick — so you couldn't scroll up to read earlier output during a long run.

Fix

Both changes live in terminal-ui.js's flushPendingWrites and the scroll handlers, and only affect codex (other modes keep their existing behavior):

  • First-frame budget: MAX_FRAME_BYTES is 32768 for mode === 'codex', 65536 otherwise. A smaller first frame keeps per-frame xterm/WebGL work short; the remainder defers to the next frame as before.
  • Scroll-up grace window: a manual scroll-up records a timestamp (_noteTerminalUserScroll); for USER_SCROLL_STICKY_SUPPRESS_MS (1500ms) afterward, _hasRecentUserScrollUp() is true, so flushPendingWrites skips the auto-scrollToBottom and restores the user's viewport via scrollToLine. A downward scroll back to the bottom clears the suppression immediately.

Tests

Adds test/terminal-flush-budget.test.ts — a vm-sandbox harness over terminal-ui.js (no real DOM), covering:

  • codex first-frame budget = 32KB; non-codex = 64KB
  • chunkedTerminalWrite waits for xterm's write callback before finishing a buffer load; stale buffer-load owners can't finish a newer load
  • no snap-back to bottom during codex Working redraws right after a scroll-up; viewport is restored when a redraw moves it

npm run test:ci passes locally (2727 passed, 0 failures); tsc --noEmit, eslint, prettier --check, and the frontend-syntax check are all clean.

Two render-polish fixes for codex sessions in the terminal write pipeline:

- flushPendingWrites uses a 32KB first-frame budget for codex (vs 64KB for
  other modes). Codex's TUI emits dense synchronized redraws during
  thinking/high-effort phases; a smaller first frame keeps per-frame
  xterm/WebGL stalls short and avoids multi-second main-thread blocks.

- Sticky-scroll now honours a short grace window after a manual scroll-up
  gesture (USER_SCROLL_STICKY_SUPPRESS_MS = 1500ms). High-frequency codex
  "Working (Ns)" status ticks were snapping the viewport back to the bottom
  while the user tried to read earlier output. The wheel/touch scroll
  handlers record the gesture (_noteTerminalUserScroll); flushPendingWrites
  suppresses the auto-scroll-to-bottom and restores the preserved viewport
  via scrollToLine while the grace window is active.

Adds test/terminal-flush-budget.test.ts (vm-sandbox harness over
terminal-ui.js): codex vs non-codex first-frame budget, buffer-load
ownership, and the scroll-up suppression / viewport restore.

Co-Authored-By: Saqeb Akhter <saqeb.akhter@gmail.com>

@Ark0N Ark0N left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed — no blockers, merging as-is. Verified locally on a merge with current master (post-#115/#116): tsc, eslint, prettier --check, frontend-syntax clean; targeted tests + full test:ci green (2822 passed).

Two notes for the record, neither gating:

  1. The scroll-up grace is wired globally, not codex-only as the description says. _noteTerminalUserScroll hooks the universal wheel/touch handlers and _hasRecentUserScrollUp() gates sticky-scroll for every mode — only the 32KB first-frame budget is actually codex-gated. I traced the behavior and kept it: the grace closes a real race for all modes (_wasAtBottomBeforeWrite is captured at enqueue time, so a scroll-up between enqueue and the rAF flush used to get yanked back; Ink's high-frequency redraws hit the same window). Outside the 1.5s window behavior is unchanged, since a scrolled-up viewport fails the at-bottom capture anyway.

  2. The viewport restore can no-op against xterm's async parse. terminal.write() returns before the bytes are parsed, so the viewportY !== preserveViewportY check usually sees the pre-parse state and a post-parse viewport move would be missed. Harmless — the sticky-scroll suppression is the primary guard and the restore is belt-and-braces — just don't be surprised if the restore branch rarely fires in real xterm (it fires reliably in the vm-harness tests because the mock write is synchronous).

@Ark0N Ark0N merged commit dc9c4b3 into Ark0N:master Jun 10, 2026
2 checks passed
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.

2 participants