Skip to content

feat(screenshot): --with-frame, relative paths, tab switching, readiness wait#26

Merged
Shadow-Azure merged 17 commits into
mainfrom
feat/screenshot-with-frame
Jun 6, 2026
Merged

feat(screenshot): --with-frame, relative paths, tab switching, readiness wait#26
Shadow-Azure merged 17 commits into
mainfrom
feat/screenshot-with-frame

Conversation

@Shadow-Azure

@Shadow-Azure Shadow-Azure commented Jun 5, 2026

Copy link
Copy Markdown
Owner

Summary

This PR解决了一个核心问题:screenshot 命令默认需要 Screen Recording 权限,对用户不友好。同时修复了截图相关的多个 bug,并优化了启动体验。

解决的问题

  1. 截图默认需要 Screen Recording 权限 — 默认 screenshot 命令使用 SCK 截图,需要系统授权
  2. -o 参数不支持相对路径 — 嵌套目录会报错
  3. --with-frame 截图时 tab 未切换 — 多 tab 场景下截图显示的是错误的 tab 内容
  4. cli-box start 启动后立即截图失败 — Electron 渲染器 WebSocket 未连接时截图会报错

解决方案

1. SCK 可选化 (--with-frame)

场景 之前 之后
cli-box screenshot (CLI tab) Renderer → SCK fallback Renderer only
cli-box screenshot --with-frame N/A SCK directly
cli-box screenshot (app mode) SCK fallback Error: "use --with-frame"
  • 默认截图使用 Electron renderer canvas(xterm.js),不需要 Screen Recording 权限
  • 新增 --with-frame flag 显式使用 ScreenCaptureKit 截取完整窗口(需要权限)
  • 移除 entitlements.plist 中的 screen-capture 声明

2. 相对路径支持

screenshot -o subdir/test.png 现在会自动创建嵌套目录(create_dir_all)。

3. Tab 切换协议

新增 WebSocket 协议 switch_tab_request/switch_tab_response

  • Daemon 收到 --with-frame 请求时,先通过 WebSocket 通知 renderer 切换到目标 tab
  • Renderer 通过 IPC 调用 window.sandbox.switchTab() 重新定位 WebContentsView
  • 切换完成后返回确认,再进行 SCK 截图

4. 启动就绪等待 (/readyz)

新增 daemon 级别 /readyz 端点,报告 renderer WebSocket 连接状态:

  • cli-box start 启动新 Electron 时,轮询 /readyz 直到 renderer 连接
  • 终端显示动画点:"正在启动... 完成"
  • 60 秒超时,超时后打印警告但不报错(优雅降级)
  • Electron 已运行时跳过等待

Changes

File Change
cli-box-cli/src/main.rs Add --with-frame flag, relative path support, readiness polling
cli-box-cli/src/client.rs Add daemon_readiness(), pass with_frame param
cli-box-core/daemon/mod.rs Add /readyz endpoint, switch_tab_request/response protocol, screenshot_with_frame()
cli-box-core/tests/daemon_integration.rs Add readyz and --with-frame integration tests
electron-app/src/renderer/main.tsx Handle switch_tab_request, call switchTab IPC
entitlements.plist Remove screen-capture entitlement

Test plan

  • cargo test --all — 268+ tests pass
  • cargo clippy --all-targets -- -D warnings — no warnings
  • pnpm typecheck && pnpm test:unit — 36 tests pass
  • sh test.sh — all E2E tests pass
  • Release test: opencode, claude, zsh 全部通过
  • --with-frame 截图显示完整窗口框架
  • 相对路径 -o subdir/test.png 正常工作
  • 多 tab 截图各自独立
  • 启动时显示动画点,renderer 连接后退出

🤖 Generated with Claude Code

Default screenshot now uses Electron renderer canvas only — no Screen
Recording permission required. Add --with-frame flag to explicitly use
ScreenCaptureKit for full window frame capture.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Jun 5, 2026

Copy link
Copy Markdown

🔒 门禁检查结果

检查项 状态
Rust 格式化 ✅ success
Rust Clippy ✅ success
Rust 测试 & 覆盖率 ✅ success
前端测试 & 覆盖率 ✅ success
Playwright E2E ✅ success
统一测试 (test.sh) ✅ success
安全检查 ✅ success

Rust 测试覆盖率

指标 覆盖率
行覆盖率 69.9%
分支覆盖率 0.0%
模块 行覆盖率
crates.cli-box-core.src.process █████████████░░░░░░░ 65.7%
crates.cli-box-core.src.capture ████████████████████ 100.0%
crates.cli-box-core.src.sandbox ███████████████████░ 98.8%
crates.cli-box-core.src.server █████████████████░░░ 87.7%
crates.cli-box-core.src.daemon ███████████░░░░░░░░░ 56.4%
crates.cli-box-core.src.automation ███████████░░░░░░░░░ 58.8%
crates.cli-box-core.src ███████████░░░░░░░░░ 55.3%
crates.cli-box-core.src.instance ███████████████████░ 99.1%

详细报告见 Rust 覆盖率 artifact

前端测试覆盖率

指标 覆盖率
行覆盖率 ████████████████░░░░ 82.3%
分支覆盖率 68.9%
函数覆盖率 80.0%
语句覆盖率 80.0%
文件 行覆盖率
src/tests/mocks/websocket.ts ██████████████████░░ 88.0%
src/tests/mocks/xterm.ts ████████████████████ 100.0%
src/renderer/api.ts ██████████████░░░░░░ 68.2%
src/renderer/tabState.ts ████████████████████ 100.0%

详细报告见前端覆盖率 artifact

✅ 所有检查通过,可以合入

点击 Squash and merge 合并此PR

ZN-Ice and others added 15 commits June 5, 2026 13:29
- Remove dead screenshot_fallback function
- Add stale window ID handling to screenshot_with_frame
- Add tests for with_frame=true query param path
- Add test for default screenshot rejection without with_frame

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Rust IT: verify with_frame query param routing (CI-runnable)
- Shell E2E: full CLI flow test (local macOS only, skips CI)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Add screenshot --with-frame implementation plan
- Update release README build timestamp
- Add --with-frame test scenario to release_test.md

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…tabs

Bug 1: `screenshot -o subdir/shot.png` failed with ENOENT when parent
directory didn't exist. Fix: call create_dir_all on parent before writing.

Bug 2: `screenshot --with-frame` captured whatever tab was visible in the
Electron window instead of the target sandbox's tab. Fix: add
switch_tab_request/switch_tab_response messages to the screenshot WebSocket
protocol. The daemon now sends a switch_tab_request before SCK capture,
and the renderer switches the active tab before responding.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Renderer switch_tab_request handler now calls window.sandbox.switchTab()
  via IPC to reposition WebContentsView, not just setActiveTabId
- Fix screenshot_with_frame test assertions to accept 404 (WindowNotFound)
  as valid SCK failure mode alongside 500 (previously failed with
  screencapturekit feature enabled)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…wly spawned

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Add 3s per-request timeout to daemon_readiness() to prevent stalling
- Log readyz check errors at trace level for debugging

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Add doc comment explaining daemon /readyz always returns 200 (polling endpoint)
- Use fixed-width padding for animated dots instead of trailing spaces

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@Shadow-Azure Shadow-Azure changed the title feat(screenshot): make ScreenCaptureKit opt-in via --with-frame flag feat(screenshot): --with-frame, relative paths, tab switching, readiness wait Jun 6, 2026
- Complete 7-phase development workflow (branch → brainstorm → test design → plan → execute → review → test → CI)
- Superpowers skill mapping per phase
- Testing layers: UT / IT / E2E / Release test
- Key constraints: no auto-merge, CLI-first testing, screenshot verification

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@Shadow-Azure
Shadow-Azure merged commit 4fb27d8 into main Jun 6, 2026
9 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.

1 participant