fix: UTF-8 boundary safety, scroll-to-bottom on reattach, tmux/dtach session persistence#9
Open
shockstricken wants to merge 1 commit into
Open
Conversation
…istence
Three independent fixes for stability and display issues users see when
working in the web terminal:
1. UTF-8 byte-boundary safety in the pty pipeline (server.ts)
node-pty was emitting chunks as strings, which meant a multi-byte
codepoint landing on a chunk boundary got split mid-byte and forwarded
to the WebSocket as malformed UTF-8. xterm.js then rendered the smeared
border / wrong-width character glitch users reported around emoji,
box-drawing chars, and CJK text. Switching node-pty to encoding:null
gives us raw Buffer chunks and routing them through a per-session
TextDecoder with {stream:true} buffers any trailing incomplete bytes
until the next chunk arrives. The string sent over the WebSocket now
always contains only complete codepoints.
2. Scroll-to-bottom on tab focus and reconnect (index.ts)
Reopening a tab — or reconnecting after a disconnect — left the
viewport wherever the user had last scrolled, which often meant the
chat / shell pane appeared frozen with the prompt off-screen. show(),
attachTo(), and the 'ready' (reconnected) handler now each call
terminal.scrollToBottom() after the fit, so the user always lands at
the current prompt without losing scrollback history.
3. Optional tmux / dtach session persistence (server.ts)
New WEB_TERMINAL_SESSION_BACKEND env var. When set to 'tmux' the
pty wraps the shell in tmux new-session -A -s <name>; when set to
'dtach' it wraps in dtach -A. Either way, browser refresh closes
the WebSocket but the shell — and any foreground program like
claude / codex / gemini — survives, and the next connect reattaches.
Default 'none' preserves the original behavior.
Companion env vars WEB_TERMINAL_SESSION_NAME and
WEB_TERMINAL_DTACH_SOCKET tune the session identifier and socket path.
README documents the new env vars under a "Session persistence" section.
|
The tmux/dtach persistence option is the right direction for long-running Claude/Codex/Gemini sessions, but I would test one layer above "reattach works". Opening two tabs and refreshing one proves the process survives. The user-facing contract also needs to prove the reattached terminal is still the live write target:
Faryo has the same invariant from the mobile/browser side: the control surface is only complete when the original live tmux-backed session advances, not when the UI reconnects or the bridge accepts input. Reference GIF: https://github.com/Snailflyer/faryo/releases/download/v1.0.7/faryo-public-redacted-same-session-handoff-walkthrough-20260528-0120.gif |
CoderLuii
added a commit
to CoderLuii/HolyClaude
that referenced
this pull request
Jun 18, 2026
Context: HolyClaude #40 reported unreadable CloudCLI Web Terminal output with black squares, missing letters, and broken box drawing in the full image. What changed: Patched the pinned CloudCLI web-terminal plugin during the Docker build so PTY output uses raw UTF-8 byte decoding before xterm.js receives it. Expanded the default terminal font fallback stack and added a per-browser WebGL disable toggle for renderer-specific glyph failures. Added regression coverage for the patch script and documented the update path. Verification: node --check scripts/patch-cloudcli-web-terminal-rendering.mjs node --test tests/patch_cloudcli_web_terminal_rendering.test.mjs node --test tests/patch_cloudcli_disable_self_update.test.mjs python tests/test_notify.py npm install and npm run build against cloudcli-plugin-terminal 2bb28540ff5fda84972f99489f976551b8a552e8 after applying the patch git diff --cached --check Release: Tag and GitHub release should be v1.3.5. Docker tag workflow must publish latest, slim, 1.3.5, and 1.3.5-slim before issue #40 is closed. Constraint: Local Docker Desktop daemon was unavailable and the docker validation VM was unreachable from this session. Rejected: Add fonts-noto-cjk by default | large image-size cost without proof that missing container fonts are the root cause. Rejected: Vendor a fork of cloudcli-plugin-terminal | a fail-closed source patch keeps upstream provenance and is easier to drop after upstream accepts the fix. Confidence: medium Scope-risk: moderate Directive: Do not remove this patch until upstream cloudcli-plugin-terminal carries equivalent UTF-8 stream decoding and terminal renderer fallback behavior. Tested: Patch script syntax, patch idempotence, anchor-drift failure, existing self-update regression, notify tests, patched upstream plugin build. Not-tested: Local Docker full/slim builds and browser runtime smoke because no Docker daemon or validation host was reachable. Related: #40 Related: cloudcli-ai/cloudcli-plugin-terminal#9
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Three independent fixes for stability and display issues observed when running the web-terminal plugin behind a CloudCLI host. All are opt-in or backward-compatible.
1. UTF-8 byte-boundary safety in the pty pipeline (
src/server.ts)node-ptywas emittingonDatachunks as strings. When the underlying read crossed the middle of a multi-byte codepoint (common with emoji, box-drawing borders, CJK), the chunk was split mid-byte and forwarded as malformed UTF-8.xterm.jsthen rendered the well-known smeared-border / wrong-width-character glitch.This switches
node-ptytoencoding: null(rawBufferchunks) and routes them through a per-sessionTextDecoderwith{stream: true}. Trailing incomplete bytes are buffered until the next chunk, so what we send over the WebSocket always contains only complete codepoints.2. Scroll-to-bottom on tab show, attach, and reconnect (
src/index.ts)Reopening a tab — or reconnecting after a disconnect — left the viewport wherever the user had last scrolled, so the prompt was often off-screen and the tab appeared frozen.
show(),attachTo(), and the'ready'(reconnected) message handler now each callterminal.scrollToBottom()after the fit, so the user always lands at the current prompt without losing scrollback history.3. Optional tmux / dtach session persistence (
src/server.ts)New env var
WEB_TERMINAL_SESSION_BACKEND:Companion vars `WEB_TERMINAL_SESSION_NAME` and `WEB_TERMINAL_DTACH_SOCKET` tune the session/socket identifier.
This lets hosts that ship `tmux` or `dtach` give users long-running `claude` / `codex` / `gemini` sessions that survive browser refresh and intermittent network drops without any client-side changes.
Test plan
Notes