Skip to content

Drop juspay/xterm.js fork — both fixes are upstream#979

Merged
srid merged 3 commits into
masterfrom
chore/xterm-upstream-master
May 26, 2026
Merged

Drop juspay/xterm.js fork — both fixes are upstream#979
srid merged 3 commits into
masterfrom
chore/xterm-upstream-master

Conversation

@srid

@srid srid commented May 26, 2026

Copy link
Copy Markdown
Member

Both Kolu-maintained xterm.js patches landed upstream, so pnpm.overrides no longer needs to point at the juspay/xterm.js fork. The override now pins the per-commit npm betas auto-published from xtermjs/xterm.js@master, giving us a release channel that is byte-for-byte the unreleased upstream master.

Patch status

Fork patch Upstream PR State Landed
CursorBlinkStateManager + _pausedResizeTask registration xtermjs/xterm.js#5817 merged 2026-04-21
WeakRef in IntersectionObserver (variant A) xtermjs/xterm.js#5821 closed → superseded by #5831 (clear observer on dispose) 2026-05-26

Both fixes ship in upstream master commit afaed108, which is the source of @xterm/xterm@6.1.0-beta.225 and @xterm/addon-webgl@0.20.0-beta.224.

pnpm.overrides diff

-"@xterm/xterm":       "github:juspay/xterm.js#fix/kolu-xterm-fixes-built",
-"@xterm/addon-webgl": "github:juspay/xterm.js#fix/kolu-xterm-fixes-built&path:/addons/addon-webgl"
+"@xterm/xterm":       "6.1.0-beta.225",
+"@xterm/addon-webgl": "0.20.0-beta.224"

The two packages are co-versioned upstream (addon-webgl@beta.224's peerDep is @xterm/xterm: ^6.1.0-beta.225), so they always move in lockstep. When xterm 6.1.0 stable releases, the override collapses to a plain ^6.1.0 declaration and these pnpm.overrides entries can be dropped entirely.

Side updates

  • The blog post website/src/content/blog/xtermjs-perf.md showed the old fork URL in present tense — now describes the move to upstream betas.
  • default.nix pnpmDeps.hash refreshed to match the regenerated pnpm-lock.yaml.

Try it locally

nix run github:juspay/kolu/chore/xterm-upstream-master

Generated by /do on Claude Code (model claude-opus-4-7).

srid added 3 commits May 26, 2026 15:47
Both fork fixes are merged upstream:
  - #5817 (CursorBlinkStateManager dispose leak) — merged 2026-04-21
  - #5821 (WeakRef variant) — closed, superseded by #5831
    (observer-nullify on dispose), merged 2026-05-26

Swap pnpm.overrides:
  @xterm/xterm        github:juspay/xterm.js#fix/kolu-xterm-fixes-built
                   -> 6.1.0-beta.225
  @xterm/addon-webgl  same fork URL + path:/addons/addon-webgl
                   -> 0.20.0-beta.224

Both betas are built from xtermjs/xterm.js master commit afaed108,
so this is "build off master" without the fork's prebuilt-lib detour.
Address hickey finding F1: the published blog post still showed the
juspay/xterm.js fork github URL as the present-tense consumption
mechanism. Update to reflect the override now pinning npm betas built
from xtermjs/xterm.js master (#5817 merged, #5821 superseded by #5831).
pnpm-lock.yaml regenerated when overrides moved from the juspay/xterm.js
fork URL to npm betas. The fetchPnpmDeps pre-image follows.
@srid

srid commented May 26, 2026

Copy link
Copy Markdown
Member Author

Hickey/Lowy Analysis

# Lens Finding Disposition
1 Hickey xtermjs-perf blog post showed stale fork URL in present tense Fixed in this PR

Hickey rationale

One concern: dependency resolution. The diff swaps pnpm.overrides from a fork GitHub URL to upstream beta versions, plus the autogenerated lockfile regeneration.

F1 (Fragmentation): One domain fact ("how Kolu consumes xterm") is represented in two places. package.json is updated to the upstream betas, but website/src/content/blog/xtermjs-perf.md (around lines 221–228) still describes the juspay fork as the current consumption mechanism and shows the old override URL in a fenced code block. The post even contains a now-fulfilled forward-looking sentence: "When upstream merges, the override collapses to a plain version bump." Coherence between the post and reality now depends on the reader inferring the post is historical — a convention, not a constraint.

A related stale line at docs/perf-investigations/memory-learnings.md lines 406–409 reads as past-tense investigation narrative throughout that file, so it is lower urgency than the blog post's active code snippet.

No other findings. The package.json and pnpm-lock.yaml change itself is structurally clean — the diff removes the non-standard git-URL override mechanism and returns the two packages to a single, uniform semver resolution path.

Lowy rationale

No structural volatility findings. The diff is two value substitutions inside the existing encapsulation receptacle (pnpm.overrides). No new module, interface, or service is introduced.

The fork was a temporary parallel value, not a second boundary — collapsing it back to upstream is the correct shape: same receptacle, cleaner value.

  • pnpm.overrides as receptacle for "which xterm build Kolu runs" — a real, named volatility axis (activity volatility: the how of satisfying "get a patched xterm" varies; the consumption sequence does not).
  • Symmetry check: @xterm/xterm and @xterm/addon-webgl move together to consecutive upstream betas (225 / 224). The packages are co-versioned upstream and must move in lockstep — pinning both is symmetric and correct.
  • Layering check: pnpm.overrides is infrastructure-layer pinning; consumer workspaces carry loose semver ranges (^6.0.0, ^0.19.0). The override resolves those to a specific beta — override layer is below and more pinned than workspace declarations. No layering inversion.

Disposition: No-op. Ship as-is from the volatility-decomposition perspective.

@srid

srid commented May 26, 2026

Copy link
Copy Markdown
Member Author

Evidence

This PR has no Kolu-side code change — it swaps the @xterm/xterm and @xterm/addon-webgl source from a fork branch to the per-commit npm betas built from xtermjs/xterm.js@master. The behavior delta lives entirely inside xterm.js (138 upstream commits, headline perf work plus the alternative IntersectionObserver dispose fix). A static screenshot of a rendered terminal would add no signal beyond what e2e already exercises.

The empirical evidence is ci::e2e passing on both platforms against the new xterm:

Lane Duration Log
ci::e2e@aarch64-darwin 2m 27s .ci/e536cf8/aarch64-darwin/ci::e2e.log
ci::e2e@x86_64-linux 1m 54s .ci/e536cf8/x86_64-linux/ci::e2e.log

These suites drive a real Kolu binary in Chromium and exercise the surfaces most sensitive to an xterm swap: terminal rendering (themes, theme shuffle, theme per terminal), command palette, clipboard, file-drop into the terminal, kitty keyboard protocol, image addon, and dock/workspace switching. All scenarios passed against @xterm/xterm@6.1.0-beta.225 + @xterm/addon-webgl@0.20.0-beta.224.

@srid

srid commented May 26, 2026

Copy link
Copy Markdown
Member Author

/do results

Step Status Duration Verification
sync 1s git fetch ok; forge=github; noGit=false
research 3m 53s Confirmed both fork patches landed upstream (#5817 + #5831 supersedes #5821); current upstream master shipped as @xterm/xterm@6.1.0-beta.225 and @xterm/addon-webgl@0.20.0-beta.224.
branch 8s On feature branch chore/xterm-upstream-master, created from origin/master.
implement 41s Two pnpm.overrides lines swapped fork URLs for pinned betas; pnpm install refreshed the lockfile.
check 49s just check exit 0 — pnpm typecheck clean across all 22 workspaces.
docs 19s README + packages/surface/README.md reference xterm architecturally — no fork/version mentions to update.
fmt 10s just fmt clean — no rewrites.
commit 25s Primary commit 5f07cf1a pushed; branch tracking origin.
hickey+lowy 2m 47s Hickey F1: stale fork URL in xtermjs-perf.md → fixed as 25dfb790. Lowy: No-op (override receptacle is the correct boundary).
police 1m 9s Skipped — diff is config + docs only; no JS/TS code patterns for /code-police to review.
test 1m 1s just test-unit: 469 passed across opencode, codex, git, claude-code, client, server.
create-pr 1m 58s Draft PR #979 + Hickey/Lowy analysis comment.
ci 7m 59s All 26 checks green on HEAD e536cf84. First run flaked ci::unit@aarch64-darwin (SIGKILL in 4s before tests ran); single-node retry passed cleanly.
evidence 51s Evidence comment ties to ci::e2e on both platforms — terminal rendering exercised live against the new xterm.
done 0s
Total 22m 35s

Slowest step: ci (7m 59s, 35% of total)

Optimization suggestions

  • ci dominated and got doubled by a darwin flake. ci::unit@aarch64-darwin SIGKILLed in 4s on the first attempt with only packages/integrations/io test:unit: Failed in the log — process tree torn down before any test produced output. If this is a recurring sincereintent shape it might be worth raising as its own host-health issue; for now the retry pattern (justci run ci::unit@aarch64-darwin) closes the status without re-running the whole pipeline. For follow-up pushes on this branch, /do --from ci-only skips straight to CI.
  • research was the second-longest step (3m 53s) — most of it was walking the juspay/xterm.js PR timeline / upstream xtermjs/xterm.js PR cross-references. Future PRs that bump xterm could shorten this by linking the upstream PR (e.g. Refs xtermjs/xterm.js#5831) directly in the task description.
  • hickey+lowy (2m 47s) is at the floor for parallel sub-agent spin-up — both ran in parallel; only one structural finding emerged (the stale blog snippet), which is the kind of doc-coherence issue that's easier to fix once with hickey than to remember to update by hand.

Workflow completed at $(date -u +%Y-%m-%dT%H:%M:%SZ).

@srid

srid commented May 26, 2026

Copy link
Copy Markdown
Member Author

Perf measurement: PR vs master on the canonical 30× canvas-maximize reproducer

Headline: indistinguishable. Per-30-toggle heap growth on a quiet 7-terminal session is within 1.5% between the two arms — well inside measurement noise. Both dispose-leak fixes (the fork's WeakRef variant of #5821 and upstream's observer-nullify #5831) are functionally equivalent at this scope.

Setup

Two nix-built kolu binaries running side-by-side under isolated KOLU_STATE_DIRs:

  • PR armchore/xterm-upstream-master HEAD e536cf84, @xterm/xterm@6.1.0-beta.225 + @xterm/addon-webgl@0.20.0-beta.224, port 42937.
  • master armnix run github:juspay/kolu/master, juspay/xterm.js#fix/kolu-xterm-fixes-built, port 35107.

Each arm: 7 idle ~/ bash terminals (no agents — quiet-session rule), the active terminal's Maximize/Restore button driven 30 times via chrome-devtools MCP at ~120 ms/click. Baseline and post heap snapshots captured via Chrome's HeapProfiler.takeHeapSnapshot (forces major GC before saving).

Results — N=2 (both arms cold-reloaded before measurement)

Bucket PR (upstream) master (fork) Δ (master − PR)
Total Δ heap +1,940,607 B +1,970,490 B +29,883 (1.5%)
code (V8 JIT) +1,357,416 B +1,364,420 B +7,004 (0.5%)
native +54,375 B +81,730 B +27,355 (50%)
array +328,388 B +327,600 B −788 (−0.2%)
closure +58,116 B +58,280 B +164 (0.3%)
object +77,188 B +75,384 B −1,804 (−2.3%)
JSArrayBufferData +31 / +49,568 B +31 / +49,568 B 0 (identical)
30-toggle wall 5,724 ms 5,754 ms +30 (0.5%)

The JSArrayBufferData row is the diagnostic one — those are xterm BufferLine Uint32Array backings, the exact retainer chain the dispose-leak fixes target. Both arms allocate +31 fresh ArrayBuffers per 30 toggles (one per dispose cycle) and release them before the next iteration. WeakRef and observer-nullify drop the load equally when measured against the canonical reproducer.

N=1 was misleading — methodology note

The first iteration showed master growing ~2.6× more than PR (+1,653 KB vs +623 KB). That gap turned out to be a confounding warmup artifact: the PR arm had a 5-toggle dry-run during methodology validation, so its baseline snapshot was already past the V8 JIT churn (FeedbackVector / LoadHandler / code:* allocations) that the master arm absorbed during its measured 30 toggles. Reloading both arms cold and re-measuring (N=2 above) collapsed the gap to ~30 KB.

The lesson is the perf-diagnose skill's existing one: facts over guesses, and one snapshot isn't a fact yet.

What this measurement doesn't cover

The 30× canvas-maximize toggle exercises the mount/dispose lifecycle, not the parse/render hot loop. The headline upstream-master perf work — #5825 CSI fast-path (+42% throughput), #5864 BufferLine translateToString cache, #5902 decoration-at-cell cache, #5867 const-enum conversions — all live in code paths a static-toggle test never hits.

To measure those, the right reproducer is a heavy-stdout stream (multi-MB cat large.log, a find /nix/store-class dump, or yes | head -c 100M) with a performance trace capturing parser time per byte. That's a separate measurement — not in scope for this PR's perf signoff, but worth filing if the upstream claims need empirical confirmation downstream.

Conclusion

Memory-wise, the swap is safe to ship. No regression on the canonical dispose-leak reproducer; the upstream observer-nullify behaves identically to the fork's WeakRef. Toggle wall-clock is unchanged (≈5.7 s / 30 toggles on both). The wider parse-throughput / render-hotpath claims attached to the 138-commit upstream delta would land or fail under a different reproducer this PR doesn't unblock or block.

@srid srid marked this pull request as ready for review May 26, 2026 21:06
@srid srid merged commit 9c07890 into master May 26, 2026
26 checks passed
@srid srid deleted the chore/xterm-upstream-master branch May 26, 2026 21:06
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.

1 participant