Skip to content

Add browser screenshot clipboard actions - #4479

Merged
austinywang merged 20 commits into
mainfrom
issue-4472-browser-screenshot-context-menu
May 21, 2026
Merged

austinywang merged 20 commits into
mainfrom
issue-4472-browser-screenshot-context-menu

Conversation

@austinywang

@austinywang austinywang commented May 21, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • Adds browser context-menu actions for Screenshot Page and Screenshot Section, preserving WebKit's default menu items.
  • Adds a toolbar camera button as the first browser action icon; it copies the full rendered page to the clipboard and briefly flips to a checkmark with a copied indicator.
  • Adds an AppKit section-selection overlay with dimming, marching ants, dimensions, a compact instruction badge, Esc/Cmd+. cancel, and clipboard-only PNG/TIFF output.

Visual verification

Captured locally from the tagged Debug build:

  • Context menu with screenshot items/icons: /tmp/cmux-4472-pr-artifacts/context-menu-after-badge-build.png
  • Section selection instructions: /tmp/cmux-4472-pr-artifacts/section-instructions.png
  • Section selection flow: /tmp/cmux-4472-pr-artifacts/section-overlay-selection.png

Manual checks covered embedded browser panes, a normal page, a tall page, and a wide page. Clipboard inspection confirmed PNG/TIFF output for full-page and cropped section captures.

Test approach

  • Added behavior-level tests for the shared screenshot pipeline: full-page snapshot to pasteboard, section crop math, and cropped pasteboard writes.
  • Built and launched the tagged app with CMUX_SKIP_ZIG_BUILD=1 ./scripts/reload.sh --tag issue-4472-browser-screenshot-context-menu --launch.

Closes #4472


View with Codesmith Autofix with Codesmith
Need help on this PR? Tag @codesmith with what you need. Autofix is disabled.


Note

Medium Risk
Adds new WebKit/AppKit screenshot capture flows (full-page tiling, selection overlay, clipboard writes) that could impact browser performance/memory and user clipboard behavior if edge cases aren’t handled.

Overview
Adds browser screenshot-to-clipboard support via a new toolbar camera button (full page) and new context-menu items for Screenshot Page and Screenshot Section.

Introduces a shared screenshot pipeline that snapshots full pages (single-shot with stitched-tiles fallback), crops section selections, writes PNG/TIFF to the pasteboard, and gates concurrent captures; includes an AppKit selection overlay (dimming, marching-ants, dimension tooltip, cancel shortcuts) plus localized strings and new unit tests covering pasteboard output, crop math, tiling, and size limits.

Reviewed by Cursor Bugbot for commit 0703b6d. Bugbot is set up for automated code reviews on this repo. Configure here.


Summary by cubic

Adds browser screenshot actions: a toolbar camera for full‑page capture and context‑menu options for full page or a selected section. Copies PNG/TIFF to the clipboard with a flash and a brief “Copied” badge; clears the pasteboard first. Implements #4472.

  • New Features

    • Toolbar button and context menu: capture full page or a section; preserves default menu items; dynamic help; flash + “Copied” badge/checkmark.
    • Section overlay: click‑and‑drag selection with dimming, marching‑ants border, size readout; Esc/Cmd+. or right‑click cancels; localized instructions.
    • Capture pipeline: full page via single snapshot with stitched‑tile fallback; section crops viewport; writes PNG+TIFF; localized menu/help/error strings; tests for pasteboard clearing, formats, crop math, tiling placement, and size limits.
  • Bug Fixes

    • Serialized captures with a gate to prevent overlapping runs; added tests.
    • Bounded full‑page output size and rejected huge pages before allocation; reduced memory during stitched capture.
    • Tiling: clamped oversized tile source and bounded destination rects to prevent overflow/misalignment.
    • “Copied” indicator: isolated timer and synchronous reset; help text updates after copy.

Written for commit 0703b6d. Summary will update on new commits. Review in cubic

Summary by CodeRabbit

  • New Features
    • Full‑page screenshot to clipboard with transient "Copied" confirmation, toolbar camera button (changes to checkmark when copied), context‑menu entry, and flash feedback
    • Section screenshot via click‑and‑drag selection (shows instructions, dashed selection, Esc cancels) and context‑menu entry
  • Localization
    • Localized strings for screenshot menu items, clipboard messages, selection instructions, and multiple screenshot error messages across locales
  • Tests
    • Pipeline tests covering full‑page and section capture, cropping, and clipboard output

Review Change Stack

@vercel

vercel Bot commented May 21, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
cmux Ready Ready Preview, Comment May 21, 2026 8:49pm
cmux-staging Building Building Preview, Comment May 21, 2026 8:49pm

@coderabbitai

coderabbitai Bot commented May 21, 2026 •

Copy link
Copy Markdown

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds right‑click "Screenshot Page" and "Screenshot Section" actions plus an address‑bar screenshot button; captures full-page snapshots (with tiled stitch fallback) or user-selected regions, crops images, writes PNG/TIFF to the clipboard, shows a transient flash/copy indicator, and includes tests, localization, and project wiring.

Changes

Browser Screenshot Capture Feature

Layer / File(s) Summary
Screenshot types, crop, and pipeline
Sources/Panels/BrowserScreenshotPipeline.swift
Defines BrowserScreenshotError, BrowserScreenshotCaptureMode, BrowserScreenshotResult, BrowserScreenshotCrop, BrowserScreenshotPasteboardWriter, and BrowserScreenshotPipeline.captureAndWrite(...) to normalize/clamp selection rectangles, produce cropped NSImage outputs, encode PNG+TIFF, clear/write the pasteboard, and return output size.
Full-page and viewport snapshot engine
Sources/Panels/BrowserScreenshotSnapshotter.swift
Snapshotter extracts web content metrics via in-page JS, validates full-page size, attempts a single full-content snapshot with a 95% acceptance threshold, and falls back to tiled scroll-and-stitch capture with helpers for tile origins, drawing, scrolling, and async snapshot bridging.
Interactive selection overlay & flash
Sources/Panels/BrowserScreenshot.swift
BrowserScreenshotSelectionOverlayView implements click-drag selection, marching-ants border, dimensions tooltip, keyboard/right-click cancellation, and a finish callback; BrowserScreenshotFlashView shows a transient capture flash.
Context menu integration into CmuxWebView
Sources/Panels/BrowserScreenshot.swift, Sources/Panels/CmuxWebView.swift
CmuxWebView.appendScreenshotContextMenuItems(to:) adds "Screenshot Page" and "Screenshot Section" items during willOpenMenu, wiring handlers that start full-page capture or present the selection overlay then write to pasteboard.
BrowserPanel screenshot API & address bar UI
Sources/Panels/BrowserPanelView.swift, Sources/Panels/BrowserScreenshot.swift
BrowserPanel.captureScreenshotPageToClipboard() delegates to the webview; BrowserPanelView adds screenshotPageButton, copied-state tracking, a 1.4s reset timer, accessibility identifier, transient "Copied" overlay, and icon tint logic; cancels timer on disappear.
Localized UI strings
Resources/Localizable.xcstrings
Adds multi-locale entries for browser.contextMenu.screenshotPage, browser.contextMenu.screenshotSection, clipboard help/confirmation (browser.screenshotPage.copy.help, browser.screenshotPage.copied, browser.screenshotPage.copied.help), browser.screenshotSection.instructions, and browser.screenshot.error.* messages.
Project build configuration
cmux.xcodeproj/project.pbxproj
Registers Panels/BrowserScreenshot.swift, Panels/BrowserScreenshotPipeline.swift, and Panels/BrowserScreenshotSnapshotter.swift in the project file and adds them to the cmux target sources and source group.
Screenshot pipeline test coverage
cmuxTests/CmuxWebViewDragRoutingTests.swift
Adds makeTestImage(...) helper and tests that verify full-page snapshot writes PNG+TIFF to a pasteboard, pasteboard clearing behavior, crop coordinate mapping, cropped-section output size, tile-placement clamping, and oversized-page validation.

Sequence Diagram(s)

sequenceDiagram
  participant PanelView as BrowserPanelView
  participant Panel as BrowserPanel
  participant WebView as CmuxWebView
  participant Pipeline as BrowserScreenshotPipeline
  participant Pasteboard as NSPasteboard
  PanelView->>Panel: trigger captureScreenshotPageToClipboard()
  Panel->>WebView: captureScreenshotPageToClipboard()
  WebView->>Pipeline: captureAndWrite(mode:snapshotProvider:pasteboard:)
  Pipeline->>Pasteboard: write PNG + TIFF
  Pipeline-->>WebView: return BrowserScreenshotResult
  WebView-->>PanelView: success -> show copied indicator / flash
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

🐰 I clicked the menu, the camera did sing,
I drew a box, then the flash took wing.
The image hopped to the clipboard with cheer,
Paste and behold — the screenshot is here. 🥕📸

🚥 Pre-merge checks | ✅ 16 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (16 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Add browser screenshot clipboard actions' clearly and concisely summarizes the main feature addition—browser screenshot functionality copied to clipboard.
Linked Issues check ✅ Passed All core requirements from issue #4472 are met: context menu items for 'Screenshot Page' and 'Screenshot Section' are added [BrowserScreenshot.swift], the toolbar camera button copies full page to clipboard [BrowserPanelView.swift], section selection overlay with marching ants/dimming/Esc cancel is implemented [BrowserScreenshot.swift], full-page snapshot with stitched fallback and PNG/TIFF pasteboard write are implemented [BrowserScreenshotPipeline.swift, BrowserScreenshotSnapshotter.swift], and behavior tests cover pasteboard output and crop math [CmuxWebViewDragRoutingTests.swift].
Out of Scope Changes check ✅ Passed All changes are directly scoped to implementing browser screenshot functionality: localization strings, UI components (toolbar button, selection overlay), screenshot pipeline/snapshotter, context menu integration, and tests. No unrelated refactoring or unrelated feature additions detected.
Cmux Swift Actor Isolation ✅ Passed All NSView subclasses properly marked @MainActor. Public async methods on CmuxWebView and BrowserPanel correctly isolated. Timer callback uses proper MainActor.assumeIsolated pattern.
Cmux Swift Blocking Runtime ✅ Passed No blocking synchronization violations. Timer for UI feedback (1.4s copy indication) is allowed; properly isolated and has codebase precedent.
Cmux No Hacky Sleeps ✅ Passed This check applies only to non-Swift runtime changes (TypeScript, JavaScript, shell scripts). The PR changes only Swift files, localization strings, and build config—all outside this check's scope.
Cmux Swift Concurrency ✅ Passed PR introduces no DispatchQueue, Combine, or completion-handler patterns; all Task usages cross AppKit/Foundation/SwiftUI callback boundaries (allowed); public APIs modernized to async throws.
Cmux Swift @Concurrent ✅ Passed All async functions are @MainActor-isolated for intentional UI-bound work (WKWebView snapshots, image rendering). No @concurrent misuse, missing annotations, or actor isolation violations detected.
Cmux Swift File And Package Boundaries ✅ Passed Three new files comply: BrowserScreenshot (406, AppKit), BrowserScreenshotPipeline (176), BrowserScreenshotSnapshotter (307) with clear responsibilities. 77 lines to existing files under threshold.
Cmux Swift Logging ✅ Passed All logging statements in app code are debug-only cmuxDebugLog calls guarded by #if DEBUG blocks, fully complying with swift-logging.md. No prohibited logging (print, NSLog, etc.) found.
Cmux User-Facing Error Privacy ✅ Passed All 12 user-facing strings follow privacy guidelines: no vendor names, internal provider details, credentials, or raw errors exposed. Properly localized with safe defaults.
Cmux Full Internationalization ✅ Passed All user-facing strings in the screenshot feature use String(localized:) API with matching translations in Localizable.xcstrings covering all 20 supported locales.
Cmux Swiftui State Layout ✅ Passed @State declarations for local transient UI state with mutations only in event handlers/lifecycle callbacks, no render-time mutations, and AppKit files contain no SwiftUI patterns.
Cmux Architecture Rethink ✅ Passed Timer-based UI feedback (copied state, border animation) are legitimate platform callbacks with clear ownership, proper cleanup, and no symptom patches or architectural violations.
Cmux Swift Auxiliary Window Close Shortcuts ✅ Passed PR adds two NSView subclasses as subviews to CmuxWebView, not standalone windows. No NSWindow/NSPanel/NSWindowController/SwiftUI Window declarations. Complies with auxiliary window rule.
Description check ✅ Passed PR description includes summary of changes, visual verification artifacts, and test approach, but lacks comprehensive demo video link and incomplete checklist.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch issue-4472-browser-screenshot-context-menu

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai 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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@Resources/Localizable.xcstrings`:
- Around line 11004-11071: Add full locale blocks for the four new localization
keys (browser.screenshotPage.copy.help, browser.screenshotPage.copied,
browser.screenshotPage.copied.help, browser.screenshotSection.instructions) in
Resources/Localizable.xcstrings so they include entries for every locale already
present in that catalog (not just en/ja); for each locale add the same
"stringUnit" structure with "extractionState": "manual" and a "stringUnit"
containing "state" and "value" (use the translated text if available, otherwise
set the locale's "state" to an appropriate placeholder like "needs-translation"
and a fallback value) to match the existing locale blocks format used throughout
the file.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: a846992b-e2b6-4ce0-91cb-c5725c659d8e

📥 Commits

Reviewing files that changed from the base of the PR and between 296060b and f94a3ab.

📒 Files selected for processing (6)
  • Resources/Localizable.xcstrings
  • Sources/Panels/BrowserPanelView.swift
  • Sources/Panels/BrowserScreenshot.swift
  • Sources/Panels/CmuxWebView.swift
  • cmux.xcodeproj/project.pbxproj
  • cmuxTests/CmuxWebViewDragRoutingTests.swift

Comment thread Resources/Localizable.xcstrings
@greptile-apps

greptile-apps Bot commented May 21, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

Implements browser screenshot-to-clipboard via a new toolbar camera button (full-page capture with a 1.4 s "Copied" badge) and two context-menu items ("Screenshot Page" / "Screenshot Section"), backed by a three-layer architecture split across BrowserScreenshotPipeline, BrowserScreenshotSnapshotter, and BrowserScreenshot (AppKit overlay + wiring). Previous review concerns — Task.sleep in the toolbar badge reset, the 810-line file, and partial translations — are all fully resolved in this version.

  • Capture pipeline: full-page capture attempts a single WKSnapshotConfiguration snapshot first and falls back to viewport-tiled stitching if the returned image is undersized; both paths guard against pages over 100 MP before bitmap allocation, restore scroll position on error, and write PNG + TIFF to a cleared pasteboard.
  • Section overlay: BrowserScreenshotSelectionOverlayView renders a dimmed AppKit overlay with marching-ants border and dimension tooltip; cancel is handled via Esc, Cmd+., and right-click; a BrowserScreenshotCaptureGate serializes concurrent capture attempts at the webView level while a separate SwiftUI boolean gate disables the toolbar button.
  • Localization: all 12 new string keys are translated across all 20 supported locales; behavior-level unit tests cover pasteboard formats, crop math, tile placement clamping, gate re-entrancy, and the 100 MP size limit.

Confidence Score: 5/5

Safe to merge. All previously flagged issues are resolved and no new defects were found.

The capture pipeline, tile stitching, crop math, capture gate, pasteboard writer, AppKit overlay, and toolbar badge all look correct. The timer-based Copied indicator is properly invalidated on disappear. The tiled fallback restores scroll position before re-throwing any error. Every new string key carries full translations across all 20 supported locales. Behavior tests cover the critical paths including gate re-entrancy, crop coordinate math, tile-placement clamping, and the 100 MP rejection guard. No actor isolation mistakes, no blocking primitives, and all three new files are well within the per-file line budget.

No files require special attention.

Important Files Changed

Filename Overview
Sources/Panels/BrowserScreenshot.swift New AppKit overlay (BrowserScreenshotSelectionOverlayView), flash view, and CmuxWebView/BrowserPanel extensions wiring screenshot actions; 437 lines, well within the 800-line limit, single coherent responsibility.
Sources/Panels/BrowserScreenshotPipeline.swift Pure data-layer pipeline: capture gate, crop math, pasteboard writer, and captureAndWrite orchestrator; all independently testable and fully covered by the new tests.
Sources/Panels/BrowserScreenshotSnapshotter.swift WKWebView snapshotting: single full-page attempt with stitched-tile fallback, scroll-position restore on error, and a pixel-count cap; tile placement math is correct and unit-tested.
Sources/Panels/BrowserPanelView.swift Adds toolbar camera button with cancellable Timer for the Copied badge; timer invalidation on onDisappear and double-gate (SwiftUI flag + webView gate) are both correct.
Resources/Localizable.xcstrings Adds 12 new keys; all 12 are fully translated across all 20 supported locales (en, ar, bs, da, de, es, fr, it, ja, km, ko, nb, pl, pt-BR, ru, th, tr, uk, zh-Hans, zh-Hant).
cmuxTests/CmuxWebViewDragRoutingTests.swift New BrowserScreenshotPipelineTests covering pasteboard clearing, PNG/TIFF output, capture gate re-entrancy, crop math (view-to-image scaling and non-zero origin), tile placement clamping, and max-size rejection.
Sources/Panels/CmuxWebView.swift One-line addition hooking appendScreenshotContextMenuItems into the existing context-menu population path; no regressions to the surrounding menu assembly.
cmux.xcodeproj/project.pbxproj Registers the three new Swift files in the app target; no unusual build settings or phase changes.

Sequence Diagram

sequenceDiagram
    participant U as User
    participant BPV as BrowserPanelView
    participant CMW as CmuxWebView
    participant Gate as CaptureGate
    participant Pipeline as ScreenshotPipeline
    participant Snapper as WebViewSnapshotter
    participant PB as NSPasteboard

    Note over U,PB: Full-page toolbar button flow
    U->>BPV: Click camera button
    BPV->>BPV: Set captureInProgress true
    BPV->>CMW: captureScreenshotPageToClipboard
    CMW->>Gate: run operation
    Gate->>Pipeline: captureAndWrite fullPage
    Pipeline->>Snapper: captureFullPage
    Snapper->>Snapper: webContentMetrics via JS
    Snapper->>Snapper: validateFullPageSize max 100MP
    alt single snapshot covers 95pct of content
        Snapper-->>Pipeline: NSImage
    else tiled fallback
        loop each tile position
            Snapper->>CMW: scrollTo via JS with rAF wait
            Snapper->>CMW: takeSnapshot viewport
            CMW-->>Snapper: tile NSImage
            Snapper->>Snapper: drawTile into output bitmap
        end
        Snapper->>CMW: restoreScrollOffset
        Snapper-->>Pipeline: stitched NSImage
    end
    Pipeline->>PB: clearContents then write PNG and TIFF
    Pipeline-->>CMW: BrowserScreenshotResult
    CMW->>CMW: BrowserScreenshotFlash show
    CMW-->>BPV: didCopy true
    BPV->>BPV: showCopiedIndicator Timer 1.4s
    BPV->>BPV: reset captureInProgress on timer fire

    Note over U,PB: Section context-menu flow
    U->>CMW: contextMenuScreenshotSection
    CMW->>CMW: beginScreenshotSectionSelection
    CMW->>CMW: add SelectionOverlayView crosshair
    U->>CMW: drag to select region then mouseUp
    CMW->>Gate: run captureAndWrite section
    Gate->>Pipeline: captureAndWrite mode section
    Pipeline->>Snapper: captureVisibleViewport
    Snapper-->>Pipeline: viewport NSImage
    Pipeline->>Pipeline: BrowserScreenshotCrop scale and clamp
    Pipeline->>PB: clearContents then write cropped PNG and TIFF
    CMW->>CMW: BrowserScreenshotFlash show
Loading

Reviews (12): Last reviewed commit: "fix: address screenshot review feedback" | Re-trigger Greptile

Comment thread Sources/Panels/BrowserPanelView.swift
Comment thread Sources/Panels/BrowserScreenshot.swift Outdated

@cubic-dev-ai cubic-dev-ai 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.

1 issue found across 6 files

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread Sources/Panels/BrowserScreenshot.swift Outdated

@coderabbitai coderabbitai 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.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@Sources/Panels/BrowserScreenshotPipeline.swift`:
- Around line 11-24: Replace the hard-coded English messages in the
errorDescription computed property with localized lookups using
String(localized:defaultValue:) (or your app's localization helper) for each
case (.emptySnapshot, .invalidSelection, .invalidImageRepresentation,
.pasteboardWriteFailed, .webContentMetricsUnavailable); update the
.emptySnapshot text to a generic message that does not mention "WebKit" (e.g.,
"No screenshot was returned") and add corresponding keys and default values to
the app's string catalog so the localized lookups resolve at runtime, keeping
the same enum and property names (errorDescription) intact.
- Around line 111-115: The code in BrowserScreenshotPipeline.write clears the
system pasteboard before attempting to write the new NSPasteboardItem, which can
erase the user's clipboard if pasteboard.writeObjects([item]) fails; instead, do
not call pasteboard.clearContents() up-front — create the item via
pasteboardItem(for:), attempt pasteboard.writeObjects([item]) first, and only
modify/clear the pasteboard if the write succeeds (or simply rely on
writeObjects replacing contents on success); update the logic in static func
write(_ image: NSImage, to pasteboard: NSPasteboard = .general) to try writing
the item and throw BrowserScreenshotError.pasteboardWriteFailed on failure
without clearing existing clipboard contents, referencing pasteboardItem(for:)
and write(_:to:) to locate the change.

In `@Sources/Panels/BrowserScreenshotSnapshotter.swift`:
- Around line 58-88: Instead of buffering every captured tile into tiles,
allocate the destination bitmap once (sized to contentSize) and composite each
tile into it immediately after capture to avoid OOM: create a destination
NSImage/CGContext/NSBitmapImageRep before the loops, remove the tiles array and
in the nested loops call scroll(webView,to:), await
captureVisibleViewport(from:), draw the returned tile into the destination at
origin NSPoint(x:y:) (and release the tile afterwards), and track whether any
tile was drawn; preserve the existing error handling pattern (captureError
variable and restoring scroll via try? await scroll(webView, to:
metrics.scrollOffset)), then if captureError is set throw it, and if no tiles
were drawn throw BrowserScreenshotError.emptySnapshot, finally return the
composited destination image instead of calling stitchedImage(...). Ensure you
still reference tileOrigins(...), scroll(webView,to:),
captureVisibleViewport(from:), metrics.scrollOffset, and
BrowserScreenshotError.emptySnapshot.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 26a3840c-5591-4bda-9b6c-54c4542519fe

📥 Commits

Reviewing files that changed from the base of the PR and between 69bef98 and 9a84fcc.

📒 Files selected for processing (5)
  • Sources/Panels/BrowserPanelView.swift
  • Sources/Panels/BrowserScreenshot.swift
  • Sources/Panels/BrowserScreenshotPipeline.swift
  • Sources/Panels/BrowserScreenshotSnapshotter.swift
  • cmux.xcodeproj/project.pbxproj
💤 Files with no reviewable changes (1)
  • Sources/Panels/BrowserScreenshot.swift

Comment thread Sources/Panels/BrowserScreenshotPipeline.swift
Comment thread Sources/Panels/BrowserScreenshotPipeline.swift
Comment thread Sources/Panels/BrowserScreenshotSnapshotter.swift Outdated

@coderabbitai coderabbitai 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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@Sources/Panels/BrowserScreenshotSnapshotter.swift`:
- Around line 150-152: Drawing uses the full viewport size as the source rect
which can exceed the actual tile.size and cause scaling; update the tile.draw
call in BrowserScreenshotSnapshotter (where tile.draw(in: destination, from:
NSRect(origin: .zero, size: tile.size)) is used) to compute a bounded source
rect that uses the minimum of tile.size and the reported viewportSize (for both
width and height) so the source rect never exceeds tile.size, preventing
unintended scaling and preserving pixel alignment.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: c26f2bfc-fb34-4759-a501-2c3f374b0fba

📥 Commits

Reviewing files that changed from the base of the PR and between 9a84fcc and c0e84ea.

📒 Files selected for processing (1)
  • Sources/Panels/BrowserScreenshotSnapshotter.swift

Comment thread Sources/Panels/BrowserScreenshotSnapshotter.swift Outdated

@coderabbitai coderabbitai 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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@Resources/Localizable.xcstrings`:
- Around line 11641-11765: The five new localization keys
(browser.screenshot.error.emptySnapshot,
browser.screenshot.error.invalidImageRepresentation,
browser.screenshot.error.invalidSelection,
browser.screenshot.error.pasteboardWriteFailed,
browser.screenshot.error.webContentMetricsUnavailable) contain copied English
marked as "translated" for mature locales (de, es, fr, it, ko, nb, pt-BR, ru,
uk, zh-Hans, zh-Hant); update each of those locale entries to either supply
proper native translations for the respective key or switch their stringUnit
state to the project’s non-final workflow (e.g., clear the value and mark as
untranslated/placeholder per repo convention) so that no copied English is
marked as translated for these keys. Ensure you update the exact string keys
listed above in the Localizable.xcstrings entries and keep other locales
unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 782d11e9-54d8-4cbe-93fc-12993917e966

📥 Commits

Reviewing files that changed from the base of the PR and between c0e84ea and abc0211.

📒 Files selected for processing (2)
  • Resources/Localizable.xcstrings
  • Sources/Panels/BrowserScreenshotPipeline.swift

Comment thread Resources/Localizable.xcstrings
Comment thread Sources/Panels/BrowserScreenshotSnapshotter.swift

Copy link
Copy Markdown
Contributor

Addressed the remaining Greptile timer actor-isolation note in e5c781b33 by moving the copied-indicator @State reset into an explicit Task { @MainActor in ... }. I did not rerun a local dev build per request; CI will validate the pushed branch.

@cubic-dev-ai cubic-dev-ai 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.

1 issue found across 1 file (changes from recent commits).

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Comment thread Sources/Panels/BrowserPanelView.swift Outdated
Comment thread Sources/Panels/BrowserScreenshotSnapshotter.swift
Comment thread Sources/Panels/BrowserScreenshotPipeline.swift
@cubic-dev-ai

cubic-dev-ai Bot commented May 21, 2026

Copy link
Copy Markdown

You're iterating quickly on this pull request. To help protect your rate limits, cubic has paused automatic reviews on new pushes for now—when you're ready for another review, comment @cubic-dev-ai review.

Copy link
Copy Markdown
Contributor

@coderabbitai review

@coderabbitai

coderabbitai Bot commented May 21, 2026

Copy link
Copy Markdown
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai 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.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
Sources/Panels/BrowserScreenshotSnapshotter.swift (1)

159-172: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Always append the last tile origin when any remainder is still uncovered.

The > 0.5 check drops the final origin when the page exceeds the viewport by less than half a point, which clips the right/bottom edge instead of capturing the remaining strip.

🩹 Proposed fix
-        if origins.last.map({ abs($0 - last) > 0.5 }) ?? true {
+        if origins.last.map({ abs($0 - last) > CGFloat.ulpOfOne }) ?? true {
             origins.append(last)
         }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Sources/Panels/BrowserScreenshotSnapshotter.swift` around lines 159 - 172,
tileOrigins currently skips appending the final origin when the remaining
uncovered strip is <= 0.5 points, which can clip the edge; in the
tileOrigins(contentLength:viewportLength:) function replace the tolerance check
(abs($0 - last) > 0.5) with a definitive check that the last origin differs from
the computed last (e.g. append last when origins.isEmpty || origins.last! < last
or origins.last != last) so the final tile origin is always added whenever any
remainder remains uncovered.
cmuxTests/CmuxWebViewDragRoutingTests.swift (1)

259-259: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Consider extracting BrowserScreenshotPipelineTests to its own file.

This file is named CmuxWebViewDragRoutingTests.swift but now hosts an unrelated BrowserScreenshotPipelineTests class covering the screenshot pipeline. Splitting it into cmuxTests/BrowserScreenshotPipelineTests.swift keeps each test file aligned with a single feature/responsibility and improves discoverability when navigating from the production sources (Sources/Panels/BrowserScreenshotPipeline.swift, BrowserScreenshotSnapshotter.swift).

As per coding guidelines: "Do not mix UI rendering, state ownership, persistence, networking, parsing, subprocess/socket protocol, and platform bridge code in one Swift file" — the same single-responsibility principle applies here to keep drag-routing vs. screenshot pipeline test surfaces separated.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmuxTests/CmuxWebViewDragRoutingTests.swift` at line 259, The
BrowserScreenshotPipelineTests class is unrelated to CmuxWebViewDragRoutingTests
and should be moved to its own test file: create a new
cmuxTests/BrowserScreenshotPipelineTests.swift, paste the final class
BrowserScreenshotPipelineTests { ... } into it, add the necessary imports
(import XCTest and any test helpers used by BrowserScreenshotPipelineTests),
adjust any file-scoped helpers or test utilities referenced by the class (move
or make them internal/public as needed), and remove the
BrowserScreenshotPipelineTests declaration from
CmuxWebViewDragRoutingTests.swift so each file only contains its single
responsibility test suite.
♻️ Duplicate comments (1)
Sources/Panels/BrowserScreenshotPipeline.swift (1)

129-133: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Don't erase the current clipboard before the new screenshot is accepted.

clearContents() runs before the pasteboard write is known to succeed, so a failed writeObjects([item]) still wipes the user's existing clipboard contents.

🩹 Proposed fix
 static func write(_ image: NSImage, to pasteboard: NSPasteboard = .general) throws {
     let item = try pasteboardItem(for: image)
-    pasteboard.clearContents()
     guard pasteboard.writeObjects([item]) else {
         throw BrowserScreenshotError.pasteboardWriteFailed
     }
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Sources/Panels/BrowserScreenshotPipeline.swift` around lines 129 - 133, The
pasteboard.clearContents() call in static func write(_:to:) wipes the clipboard
before we know pasteboard.writeObjects([item]) succeeded; remove the pre-emptive
clearContents() (or move it after a successful write) so we only modify the
user's clipboard when writeObjects returns true, keeping the existing clipboard
intact when writeObjects fails; locate the write function and the
pasteboardItem(for:) call and ensure failure still throws
BrowserScreenshotError.pasteboardWriteFailed without having cleared the
pasteboard first.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@cmuxTests/CmuxWebViewDragRoutingTests.swift`:
- Around line 386-403: The test only asserts an oversized width case; add a
symmetric negative-case that calls
BrowserScreenshotCaptureBounds.validateFullPageSize with an oversized height
(e.g., NSSize(width: 10_000, height: 10_001)) and assert it throws
BrowserScreenshotError.captureAreaTooLarge in the same pattern as the existing
XCTAssertThrowsError block so both axes are validated; locate the test method
testFullPageCaptureBoundsRejectsHugePageBeforeBitmapAllocation and add the
second XCTAssertThrowsError that mirrors the width check but swaps the oversized
dimension to height.

---

Outside diff comments:
In `@cmuxTests/CmuxWebViewDragRoutingTests.swift`:
- Line 259: The BrowserScreenshotPipelineTests class is unrelated to
CmuxWebViewDragRoutingTests and should be moved to its own test file: create a
new cmuxTests/BrowserScreenshotPipelineTests.swift, paste the final class
BrowserScreenshotPipelineTests { ... } into it, add the necessary imports
(import XCTest and any test helpers used by BrowserScreenshotPipelineTests),
adjust any file-scoped helpers or test utilities referenced by the class (move
or make them internal/public as needed), and remove the
BrowserScreenshotPipelineTests declaration from
CmuxWebViewDragRoutingTests.swift so each file only contains its single
responsibility test suite.

In `@Sources/Panels/BrowserScreenshotSnapshotter.swift`:
- Around line 159-172: tileOrigins currently skips appending the final origin
when the remaining uncovered strip is <= 0.5 points, which can clip the edge; in
the tileOrigins(contentLength:viewportLength:) function replace the tolerance
check (abs($0 - last) > 0.5) with a definitive check that the last origin
differs from the computed last (e.g. append last when origins.isEmpty ||
origins.last! < last or origins.last != last) so the final tile origin is always
added whenever any remainder remains uncovered.

---

Duplicate comments:
In `@Sources/Panels/BrowserScreenshotPipeline.swift`:
- Around line 129-133: The pasteboard.clearContents() call in static func
write(_:to:) wipes the clipboard before we know pasteboard.writeObjects([item])
succeeded; remove the pre-emptive clearContents() (or move it after a successful
write) so we only modify the user's clipboard when writeObjects returns true,
keeping the existing clipboard intact when writeObjects fails; locate the write
function and the pasteboardItem(for:) call and ensure failure still throws
BrowserScreenshotError.pasteboardWriteFailed without having cleared the
pasteboard first.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 3725e54f-f0ed-4ba9-8729-71971e0b2733

📥 Commits

Reviewing files that changed from the base of the PR and between c0e84ea and 535e455.

📒 Files selected for processing (5)
  • Resources/Localizable.xcstrings
  • Sources/Panels/BrowserPanelView.swift
  • Sources/Panels/BrowserScreenshotPipeline.swift
  • Sources/Panels/BrowserScreenshotSnapshotter.swift
  • cmuxTests/CmuxWebViewDragRoutingTests.swift

Comment thread cmuxTests/CmuxWebViewDragRoutingTests.swift
Comment thread Sources/Panels/BrowserPanelView.swift

@cursor cursor 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.

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 5923b10. Configure here.

Comment thread Sources/Panels/BrowserScreenshotPipeline.swift
Comment thread Resources/Localizable.xcstrings
@austinywang
austinywang merged commit 41c4666 into main May 21, 2026
22 checks passed
@austinywang austinywang mentioned this pull request May 22, 2026
@coderabbitai coderabbitai Bot mentioned this pull request May 22, 2026

This branch was successfully deployed

1 active deployment
Preview – cmux — 0703b6d2 Deployed May 21, 2026 by vercel[bot]
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.

Browser pane: right-click 'Screenshot page' / 'Screenshot section' that copy to clipboard

2 participants