feat(screenshot): per-tab screenshot via xterm.js buffer rendering#14
Merged
Conversation
- Add min-height: 0 to .main-content and .terminal-container - Remove padding: 8px from .terminal-container .xterm - Fixes bottom white space and right black space in terminal Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Extract doFit helper that calls fit() then resize() - Use fitFnRef so containerRef triggers the same atomic operation - Fixes race condition between initial fit and WebSocket resize Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Split the html,body,#root CSS rule so #root gets display:flex + flex-direction:column.
Without this, .main-content { flex: 1 } had no effect since #root wasn't a flex container.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…itioning The inner div with height:100% wasn't resolving correctly to the flex container's height. Using position:absolute + inset:0 forces the div to fill the terminal container regardless of CSS height resolution. Fixes the remaining white area below the terminal canvas. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…zing The initial fit() in requestAnimationFrame runs before the flex layout is fully computed, causing TUI apps like opencode to render with incorrect dimensions. A delayed re-fit at 100ms gives the layout time to settle, matching what happens when the user manually resizes the window. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…ndering Implement Approach B from terminal layout fix plan: keep all tab Terminal components mounted but hidden, read xterm.js buffer for screenshots of non-active tabs instead of relying on ScreenCaptureKit which captures the entire Electron window. Changes: - Terminal.tsx: convert to forwardRef with useImperativeHandle exposing captureToPng(), use canvas for active tabs and buffer-based rendering for hidden/offscreen tabs - main.tsx: mount all tabs with CSS hiding, manage terminal refs, connect to daemon's /screenshot/ws WebSocket for capture requests - styles.css: make .terminal-area a flex container for proper height propagation to nested terminal containers - daemon/mod.rs: add /screenshot/ws WebSocket endpoint and request_renderer_screenshot() helper, modify screenshot_handler to try renderer first then fall back to ScreenCaptureKit - daemon_integration.rs: update DaemonState with new fields Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
🔒 门禁检查结果
Rust 测试覆盖率
前端测试覆盖率
✅ 所有检查通过,可以合入
|
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
/screenshot/wsWebSocket endpoint to daemon for renderer-based capture.terminal-areaa flex container for proper height propagationProblems Encountered & Solutions
1. Terminal area blank (0 height)
Problem: Mounting all tabs with CSS hiding (
position: absolute; left: -9999px) caused the terminal area to collapse to 0 height — no terminal content rendered.Root cause:
.terminal-arealackeddisplay: flex, so child elements withflex: 1had no flex parent to expand into.Fix: Added
display: flex; flex-direction: column;to.terminal-areainstyles.css.2. Double terminal-container nesting
Problem: main.tsx wrapper
divusedclassName="terminal-container", which nested with SandboxTerminal's owndiv.terminal-container. The CSS rule.terminal-container > div { position: absolute; inset: 0 }made the inner container absolutely positioned, removing it from flow.Root cause: CSS selector matched the wrong nesting level.
Fix: Removed
className="terminal-container"from the wrapper div, using inline flex styles instead.3. xterm.js v6 uses DOM renderer, not canvas
Problem:
captureToPng()triedterm.element.querySelector("canvas")which returnednull— xterm.js v6 renders to DOM elements, not<canvas>.Root cause: xterm.js v6 default renderer is
dom, notcanvas. No<canvas>element exists.Fix: Added buffer-based fallback: read xterm text buffer line-by-line, render characters onto an offscreen canvas with correct colors.
4. CI failure — cargo fmt
Problem: First CI run failed on Rust formatting in
daemon/mod.rs.Fix: Ran
cargo fmt --alland pushed.5. Known limitation: screenshots lack macOS window frame
Current behavior: Per-tab screenshots capture only terminal content (xterm buffer → canvas). They do NOT include the macOS window chrome (title bar, traffic lights, tab bar, status bar).
Why: xterm.js buffer can only read terminal text. Window chrome is rendered by Electron/OS, invisible to xterm.
BrowserWindow.capturePage()only captures web content, not OS chrome. Only ScreenCaptureKit can capture the full native window.Future approach: Add
--with-frameflag that triggers a fast tab-switch → ScreenCaptureKit capture → switch-back cycle (<16ms, imperceptible to user).Test Results
All 5 release test scenarios passed:
Full test report:
release_test/2026-06-01-08-03-02/test-report.mdTest plan
cargo check --all-targetspassescargo testpasses (142/143, 1 pre-existing failure)pnpm typecheckpasses🤖 Generated with Claude Code