Skip to content

fix: clear scrolled row cells in wasm patch#180

Merged
ThomasK33 merged 1 commit into
mainfrom
implement-issue-138
Jun 26, 2026
Merged

fix: clear scrolled row cells in wasm patch#180
ThomasK33 merged 1 commit into
mainfrom
implement-issue-138

Conversation

@ThomasK33

@ThomasK33 ThomasK33 commented Jun 26, 2026

Copy link
Copy Markdown
Member

Summary

Fixes #138.

This PR updates the Ghostty WASM integration patch so cursorDownScroll() always clears cells for a newly grown row, even when the cursor background is the default value after ESC[0m. It also resets the recycled row's row-level metadata after clearing while preserving the backing cell offset and dirty state; the later non-default-background fill path remains conditional and intact.

It adds focused regression coverage that repeatedly writes SGR/truecolor/256-color reset-heavy output, includes a soft-wrap metadata seed, scrolls enough to exercise row growth/reuse, and asserts that viewport rows do not expose stale merged cell data or stale blank-row wrap metadata.

Coordination note: #134 is the related draft/reference PR for the same issue. This branch keeps the replacement diff minimal and links that prior work to avoid duplicate-review confusion.

Validation

  • bun install — passed after installing local dependencies.
  • bun run build:wasm — passed; generated local ignored ghostty-vt.wasm for tests.
  • bun test lib/viewport-corruption.test.ts — passed: 1 pass, 0 fail, 3723 expects.
  • bun run fmt — passed.
  • bun run lint — passed.
  • bun run typecheck — passed.
  • bun test — passed: 332 pass, 0 fail, 4487 expects.
  • bun run build — passed.
  • git -C ghostty apply --check ../patches/ghostty-wasm-api.patch — passed.
  • Final git status --short / git status --short ghostty — clean tracked tree and clean Ghostty submodule.
  • PR CI checks on commit d101961a86 — passed: fmt, lint, type check, test, build.

Workflow verifier findings

The issue implementation workflow converged after 2 verifier runs. Final verifier summary:

Verifier run 2 passed for #138. The implementation diff is limited to patches/ghostty-wasm-api.patch and lib/viewport-corruption.test.ts. The patch adds the intended Screen.zig hunk that clears newly grown cursorDownScroll() row cells unconditionally, while leaving the later non-default-background fill path conditional. The focused regression test exercises reset-heavy SGR/ESC[0m scrolling output, blank/newline regions, viewport/getLine consistency, marker uniqueness, and a stable-viewport invariant.

Post-workflow Codex review found two in-scope issues, both addressed:

  • Full-line markers are now fixed width (R00L000) and matched with /R\d{2}L\d{3}/g.
  • Recycled row metadata is reset after clearing cells, and the regression test asserts blank scrolled-in rows are not reported as wrapped.

Latest Codex result: “Didn't find any major issues.”

Non-blocking finding:

Dogfooding

The workflow dogfooded the interactive demo with SGR-heavy scrolling output after installing demo dependencies and running the demo on an alternate port. It captured screenshots and recordings under /tmp/issue138-dogfood/ with no browser console errors and no visible stale row merges.

Screenshot upload to GitHub was attempted with gh image, but this headless workspace has no browser user_session cookie and GH_SESSION_TOKEN is unset, so the images could not be embedded in this PR body from the worker environment.


📋 Implementation Plan

Implementation Plan: #138

Issue summary and verified context

  • Live issue: coder/ghostty-web#138"Stale cell data visible on new rows after scrolling with default cursor style". State: open. Labels: triage:done, bug, accepted.
  • Reported behavior: repeated escape-heavy terminal output followed by scrolling can reveal stale cell data in newly grown rows, visible as transient horizontal row merges or old text fragments.
  • Verified root-cause direction from the issue, triage comment, and repo investigation: ghostty/src/terminal/Screen.zig cursorDownScroll() clears newly grown row cells only when self.cursor.style.bg_color != .none. After ESC[0m, the cursor background is default/none, so row memory produced by pages.grow() can retain stale cells when the row is not fully overwritten.
  • Related existing PR: coder/ghostty-web#134 — open, mergeable, review required. It contains the same fix direction plus draft regression tests. Treat this as the canonical reference, not as unrelated prior art.
  • Current repo mechanics verified:
    • The Ghostty submodule lives under ghostty/.
    • The committed integration patch is patches/ghostty-wasm-api.patch; builds apply this patch to the submodule, compile WASM, then revert the submodule patch.
    • ghostty-vt.wasm is generated in the repo root by bun run build:wasm / ./scripts/build-wasm.sh and is currently ignored by git. Do not commit generated WASM unless the tracking policy changes.
    • Package scripts include build:wasm, build:lib, build, test, typecheck, fmt, lint, and demo:dev.

Scope boundaries

In scope:

  1. Make newly grown rows in cursorDownScroll() clear their cells regardless of cursor background style.
  2. Preserve existing non-default-background fill/styling behavior after the clear.
  3. Add a lean regression test that fails against the old WASM behavior and passes after the patch.
  4. Rebuild local generated WASM for validation, but avoid committing ignored build artifacts.
  5. Coordinate with existing PR fix: always clear new row cells during scroll to prevent stale data #134 to avoid duplicate or confusing review work.

Out of scope:

  • Renderer rewrites, scrollback redesign, selection changes, keyboard/input changes, or broader Ghostty upgrades.
  • Wholesale adoption of diagnostic/bulk tests if one focused regression test covers the bug.
  • Fixing unrelated flakes or environment problems discovered during validation. If a genuine out-of-scope issue appears, first search existing GitHub issues; comment with new evidence on a match, or create a needs-triage issue if no match exists, then return to Stale cell data visible on new rows after scrolling with default cursor style #138.

Coordination with existing PR #134

  1. At implementation start, re-check PR fix: always clear new row cells during scroll to prevent stale data #134 status and diff:

    gh pr view 134 --repo coder/ghostty-web --json state,mergeable,reviewDecision,files,url
    gh pr diff 134 --repo coder/ghostty-web --patch
  2. If PR fix: always clear new row cells during scroll to prevent stale data #134 has already merged by implementation time, do not open a new PR for the same fix. Pull latest main, validate that Stale cell data visible on new rows after scrolling with default cursor style #138 is fixed, and report the result.

  3. Preferred path while PR fix: always clear new row cells during scroll to prevent stale data #134 is open: use it as the reference implementation and either help land/update it if the implementer has permission, or reproduce its minimal fix on a repo-owned branch if maintainers need a fresh PR.

  4. If PR fix: always clear new row cells during scroll to prevent stale data #134 is closed/stale or a replacement/superseding PR is opened, link both the issue and the prior PR clearly:

  5. Do not create an uncoordinated duplicate PR while fix: always clear new row cells during scroll to prevent stale data #134 remains active.

Implementation steps

Phase 1 — Preflight and clean build inputs

  1. Install dependencies if needed:

    bun install
    git submodule update --init --recursive
  2. Ensure the Ghostty submodule starts clean before applying the WASM patch:

    cd ghostty
    git checkout -- .
    git clean -fd
    cd ..
    git status --short
  3. Confirm the Zig toolchain path. Prefer a non-global invocation when the zig shim is not configured:

    mise exec zig@0.15.2 -- zig version

    If zig is already configured in the environment, direct bun run build:wasm is fine. Otherwise, run build commands through mise exec zig@0.15.2 -- ....

Phase 2 — Add a focused failing regression test first

  1. Add one focused production regression test file, preferably adapted from PR fix: always clear new row cells during scroll to prevent stale data #134's lib/viewport-corruption.test.ts.

  2. The test should explicitly exercise all bug triggers:

    • repeated output with SGR/color sequences and ESC[0m resets;
    • enough scrolling to force page growth or page memory reuse;
    • rows that are not fully overwritten, such as blank/newline-only regions;
    • inspection through the public/near-public terminal viewport paths used by rendering/selection.
  3. Core assertion: no viewport row should contain markers from more than one logical output line/run. A marker pattern such as R02L05 lets the test catch stale horizontal merges deterministically:

    const markers = text.match(/R\d{2}L\d{2}/g) ?? [];
    expect(new Set(markers).size).toBeLessThanOrEqual(1);
  4. Also keep a small consistency assertion that getViewport() and getLine() agree for visible rows after scrolling, if that is already present in the adapted PR fix: always clear new row cells during scroll to prevent stale data #134 test.

  5. Run the new test before applying the patch to confirm it is red against a known baseline WASM. Because ghostty-vt.wasm is ignored and can be stale, rebuild the baseline first, then run the targeted test:

    mise exec zig@0.15.2 -- bun run build:wasm
    bun test lib/viewport-corruption.test.ts

    If validating red/green after partially applying the fix, revert the patch change, rebuild WASM, confirm the test fails, then reapply the patch and rebuild.

  6. If the adapted viewport-corruption test does not fail before the fix or does not clearly cover the ESC[0m default-background path, do not add both PR fix: always clear new row cells during scroll to prevent stale data #134 test files wholesale. Instead, extract the smallest failing case/payload from PR fix: always clear new row cells during scroll to prevent stale data #134's lib/viewport-row-merge.test.ts into the focused test file.

Phase 3 — Patch cursorDownScroll() through patches/ghostty-wasm-api.patch

  1. Add a src/terminal/Screen.zig diff block to patches/ghostty-wasm-api.patch.

  2. Minimal intended source change: make the new-row clearCells(...) call unconditional in cursorDownScroll().

  3. Preserve the later conditional background fill/memset for self.cursor.style.bg_color != .none; that behavior ensures styled rows still receive the active background.

  4. The patch shape should be equivalent to:

    -        // Clear the new row so it gets our bg color. We only do this
    -        // if we have a bg color at all.
    -        if (self.cursor.style.bg_color != .none) {
    +        // Always clear the new row's cells. When pages.grow() extends an
    +        // existing page, the new row's cell memory may contain stale data
    +        // from previously erased rows. Without clearing, these stale cells
    +        // become visible when the row isn't fully overwritten, such as after
    +        // bare CRLF output with the default cursor style.
    +        {
                 const page: *Page = &page_pin.node.data;
                 self.clearCells(
                     page,
  5. Keep the change surgical. Do not edit renderer code, terminal APIs, scrollback data structures, or unrelated Ghostty source.

Phase 4 — Rebuild generated WASM locally and keep the submodule clean

  1. Rebuild the local WASM artifact from the updated patch:

    mise exec zig@0.15.2 -- bun run build:wasm

    If the environment already has Zig configured, this can be shortened to:

    bun run build:wasm
  2. Verify the build script reverted the temporary patch inside ghostty/ and did not leave the submodule dirty:

    git status --short ghostty patches/ghostty-wasm-api.patch ghostty-vt.wasm
  3. Expected tracked changes after implementation should be limited to:

    • patches/ghostty-wasm-api.patch
    • the focused regression test file under lib/

    ghostty-vt.wasm is expected to exist locally for tests but should remain ignored/untracked.

Validation and quality gates

Automated checks

Run these gates in order and fix failures before claiming completion:

  1. Targeted regression test:

    bun test lib/viewport-corruption.test.ts
  2. Broader test suite:

    bun test
  3. Static checks:

    bun run fmt
    bun run lint
    bun run typecheck
  4. Build checks:

    bun run build

    If plain zig is not configured, use the mise-safe form instead:

    mise exec zig@0.15.2 -- bun run build
  5. Final cleanliness check:

    git status --short
    git status --short ghostty

Notes:

  • bun run build cleans dist/, rebuilds WASM, builds the library, and copies WASM into dist/.
  • If bun test cannot find /ghostty-vt.wasm, run bun run build:wasm first and ensure the generated root ghostty-vt.wasm exists.
  • If patch application fails, reset the submodule with cd ghostty && git checkout -- . && git clean -fd, then retry the build.

Acceptance criteria

The implementation is acceptable when all of these are true:

  • patches/ghostty-wasm-api.patch applies a minimal Screen.zig change that clears newly grown rows unconditionally in cursorDownScroll().
  • The later non-default-background fill behavior remains conditional and intact.
  • A focused regression test demonstrates no stale row merges after repeated ESC[0m reset-heavy scrolling output.
  • The focused regression test fails before the patch or is otherwise proven to cover the pre-fix failure mode, then passes after rebuilding WASM with the patch.
  • Full formatting, lint, typecheck, test, and build commands pass.
  • The Ghostty submodule is clean after validation.
  • No ignored/generated WASM artifacts are accidentally committed unless maintainers explicitly change the artifact policy.
  • Any replacement PR coordinates with PR fix: always clear new row cells during scroll to prevent stale data #134 and links Fixes #138.

Dogfooding and reviewable evidence

Because this bug is visual and transient, automated tests are necessary but not sufficient for reviewer confidence. Produce visible evidence in addition to logs.

  1. Start the combined demo/PTY + Vite dev server:

    bun run demo:dev

    If that workflow is unavailable in the local environment, use the demo package equivalent:

    cd demo && bun run dev

    Root bun run dev starts Vite only; use it only for supplemental static-page checks such as http://localhost:8000/demo/colors-demo.html, not for full interactive PTY dogfooding.

  2. Open the interactive demo in a browser:

    http://localhost:8000/demo/
    
  3. Generate SGR-heavy scrolling output inside the terminal:

    bash demo/showcase.sh

    If the script path is not available from the shell's current directory, locate/copy an equivalent ANSI/256-color/truecolor stress payload that repeatedly emits ESC[0m and scrolls.

  4. Visually inspect blank/newly created rows during and after scrolling. Expected result: no horizontal stale text fragments or row merges appear.

  5. Capture reviewable evidence:

    • one screenshot after the 256-color palette section;
    • one screenshot after the truecolor gradient or style-heavy section;
    • a short screen recording/GIF of repeated scrolling output showing no transient row merges.
  6. Attach screenshots/recording to the PR or final report. If using Mux tooling, use attach_file for screenshots so reviewers can inspect exact evidence.

PR/report guidance for the implementer

  • Keep the diff small: patch file plus one focused regression test file unless a minimal extracted second case is required to prove the bug.
  • Include validation logs in the PR body.
  • Include dogfooding screenshots/recording evidence in a collapsible section.
  • Mention PR fix: always clear new row cells during scroll to prevent stale data #134 explicitly to avoid duplicate review confusion.
  • If a new PR is opened, include the required generated footer and the plan in a collapsible details block per repository workflow preferences.

Advisor review status


Generated with mux • Model: openai:gpt-5.5 • Thinking: xhigh

@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0edb141664

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread lib/viewport-corruption.test.ts Outdated
@ThomasK33 ThomasK33 force-pushed the implement-issue-138 branch from 0edb141 to 9062250 Compare June 26, 2026 12:45
@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 906225096b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread patches/ghostty-wasm-api.patch
Change-Id: Ifa0250b9cb8a386486651f67342363ab429b11b3
Signed-off-by: Thomas Kosiewski <tk@coder.com>
@ThomasK33 ThomasK33 force-pushed the implement-issue-138 branch from 9062250 to d101961 Compare June 26, 2026 12:58
@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. What shall we delve into next?

Reviewed commit: d101961a86

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@ThomasK33 ThomasK33 merged commit bec9e16 into main Jun 26, 2026
5 checks passed
@ThomasK33 ThomasK33 deleted the implement-issue-138 branch June 26, 2026 16:25
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.

Stale cell data visible on new rows after scrolling with default cursor style

2 participants