Skip to content

Fix Retina scaling in design mode annotation crops - #8831

Merged
austinywang merged 8 commits into
mainfrom
issue-8827-draw-crop-zoom
Jul 24, 2026
Merged

austinywang merged 8 commits into
mainfrom
issue-8827-draw-crop-zoom

Conversation

@austinywang

@austinywang austinywang commented Jul 24, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • render screenshot crops into an explicit bitmap whose pixel dimensions match the selected snapshot coordinates
  • remove the ambient display backing-scale dependency from annotation and section crop exports
  • add a wired regression test that checks encoded PNG pixel dimensions rather than only logical NSImage.size

Root cause

BrowserScreenshotCrop.croppedImage used NSImage.lockFocus(), which creates its backing representation at the current display scale. On Retina, a 200×100 crop therefore encoded as 400×200 pixels and looked magnified at 1:1 inspection. The viewport-to-view and view-to-image rectangle transforms were already correct.

Testing

  • swiftc -typecheck Sources/Panels/BrowserScreenshotPipeline.swift
  • deterministic AppKit probe: 200×100 selected coordinates encoded as 400×200 before the fix and 200×100 after it
  • ./scripts/check-pbxproj.sh
  • ./scripts/lint-pbxproj-test-wiring.sh
  • git diff --check
  • no local xcodebuild or UI tests, per issue instructions; CI is the test-target gate

The requested Swift file-length budget command was attempted, but current main no longer contains scripts/swift_file_length_budget.py or .github/swift-file-length-budget.tsv after the budget was removed in #8125. Both touched Swift files remain below 500 lines, and no warning-budget file changed.

Localization

No user-facing strings or localized surfaces changed.

Demo Video

Deferred until required CI is green and explicit approval is given for the requested cloud reload/build command.

Closes #8827


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.


Summary by cubic

Fix Retina scaling in design‑mode annotation crops by rendering selections into a 1:1 NSBitmapImageRep, so exported PNGs match the selected pixels. During capture, annotations stay over the live page with a transparent outline only; fixes #8827.

  • Bug Fixes
    • Render crops at native pixel scale using a 1:1 bitmap; mark crop rendering @MainActor.
    • Guard bitmap/context creation failures when cropping.
    • Remove base64 image roundtrip: drop imageURL from completeAnnotationCapture, switch runtime to transparent outlines and hover styling, and update webview tests.
    • Add deterministic Retina tests that verify PNG pixel dimensions and sampled colors.
    • Update GhosttySurfaceConfigABITests for new fields and remove a redundant await in mobile diagnostic logging.

Written for commit bd964ce. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • Bug Fixes

    • Improved screenshot cropping by using an explicit bitmap rendering approach for consistent integral-crop scaling.
    • Cropping now fails with clearer errors when image representations or rendering contexts can’t be created.
    • Updated mobile activation logging to capture the active diagnostic logger directly.
  • Tests

    • Added screenshot-cropping tests that validate PNG output dimensions and deterministic backing-scale behavior.
    • Updated ABI/layout stability tests for Ghostty surface config to match the new struct memory layout.

@austinywang

Copy link
Copy Markdown
Contributor Author

@codex review
@coderabbitai review
@greptile-apps review
@cubic-dev-ai review

@coderabbitai

coderabbitai Bot commented Jul 24, 2026 •

Copy link
Copy Markdown

@austinywang, I’ll review the changes in #8831.

✅ Action performed

Review finished.

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.

@chatgpt-codex-connector

Copy link
Copy Markdown

To use Codex here, create a Codex account and connect to github.

@cubic-dev-ai

cubic-dev-ai Bot commented Jul 24, 2026

Copy link
Copy Markdown

@codex review
@coderabbitai review
@greptile-apps review
@cubic-dev-ai review

@austinywang I can't start this review because your workspace has reached its free monthly review limit. cubic has reviewed 241,631 of the 240,000 allowed lines of code this month. Reviews resume on 1 August 2026 (in 8 days). Paid plans include much higher monthly review limits. Upgrade now to resume reviews.

To help optimise your usage, you can tune cubic to get the most out of your usage limits:

Learn more →

@coderabbitai

coderabbitai Bot commented Jul 24, 2026 •

Copy link
Copy Markdown

Review Change Stack

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

Changes

Screenshot cropping now uses explicit bitmap rendering and includes deterministic pixel-dimension coverage. ABI layout assertions and mobile activation logger capture are also updated.

Screenshot Crop Rendering

Layer / File(s) Summary
Bitmap-backed crop rendering
Sources/Panels/BrowserScreenshotPipeline.swift
croppedImage is main-actor isolated, renders into a bitmap, throws for bitmap or context creation failures, and returns the rendered image.
Crop dimension validation
cmuxTests/BrowserScreenshotCropTests.swift, cmux.xcodeproj/project.pbxproj
The test is registered, creates bitmap-backed input, controls rasterization backing scale, and validates a 200×100 PNG result.

Compatibility and Runtime Updates

Layer / File(s) Summary
ABI layout assertions
Packages/macOS/CmuxTerminalCore/Tests/CmuxTerminalCoreTests/GhosttySurfaceConfigABITests.swift
Expected size, stride, alignment, field offsets, and callback-related fields are updated for ghostty_surface_config_s.
Activation logger capture
Sources/Mobile/MobileHostIrohRuntime+Activation.swift
The transport handler captures diagnosticLog directly from self instead of awaiting it.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related issues

Possibly related PRs

🚥 Pre-merge checks | ✅ 24 | ❌ 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 (24 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Cmux Swift Actor Isolation ✅ Passed The new @MainActor crop helper is only used from @MainActor UI code, and the diagnosticLog edit merely removes a redundant await inside an existing MainActor runtime.
Cmux Swift Blocking Runtime ✅ Passed No new blocking waits, sleeps, sync dispatch, or locks were introduced in production Swift; the only code delta is a synchronous diagnostic-log read, and the test swizzling is allowed test-only sca...
Cmux Browser Automation Off-Main ✅ Passed Changed code only updates screenshot crop rendering/tests; browser.screenshot already routes on the socket worker, and new crop drawing is @MainActor.
Cmux Expensive Synchronous Load ✅ Passed Diff touches screenshot rendering and a stored @MainActor logger; no agent-history load, JSON/JSONL parse, or broad scan was added or moved onto an interactive path.
Cmux Cache Substitution Correctness ✅ Passed Only change is removing an unnecessary await when reading in-memory diagnosticLog; no persistence/history/undo/snapshot cache substitution is involved.
Cmux No Hacky Sleeps ✅ Passed No non-Swift runtime/script changes add sleeps or timers; the only non-Swift diff is pbxproj wiring, which contains no delay logic.
Cmux Algorithmic Complexity ✅ Passed No new scalable collection scans were introduced; the Swift changes are constant-size bitmap rendering and a synchronous property access, with existing Set/map logic unchanged.
Cmux Swift Concurrency ✅ Passed No new legacy async patterns were introduced; the changes are AppKit/XCTest-boundary code and a synchronous logger capture, with no DispatchQueue, Combine, completion-handler, or fire-and-forget Ta...
Cmux Swift @Concurrent ✅ Passed PASS: The only Swift diff removes an unnecessary await on a @MainActor property access inside an actor-isolated closure; no @concurrent misuse or missing hop.
Cmux Swift Package Boundaries ✅ Passed BrowserScreenshotCrop is AppKit-specific app glue in Sources/Panels, used by browser UI flows and tests only; the pure browser domain already lives in CmuxBrowser.
Cmux Swiftpm Lockfiles ✅ Passed Only Sources/Mobile/MobileHostIrohRuntime+Activation.swift changed; no Package.swift, .gitignore, workflow, or Xcode package-reference files were modified.
Cmux Swift Logging ✅ Passed No added production console/file logging or file-scoped Logger changes were found; the only logging-related edits are test-only Issue.record calls and a sync diagnosticLog capture.
Cmux User-Facing Error Privacy ✅ Passed Changed user-facing screenshot errors are generic, and mobile Iroh failures are internal logs with private error interpolation; no sensitive provider data exposed.
Cmux Full Internationalization ✅ Passed The only diff is a debug/diagnostic log await removal, which the rule explicitly allows and adds no user-facing text or locale changes.
Cmux Swiftui State Layout ✅ Passed Changed files are AppKit/test/wiring code and a mobile runtime cleanup; no new SwiftUI state, GeometryReader, lazy-row store refs, or render-time state writes appear.
Cmux Architecture Rethink ✅ Passed The screenshot fix is a small localized AppKit bridge; the only extra state/synchronization is test-only swizzling, with no new timing hacks, duplicate wiring, or split ownership.
Cmux Swift Auxiliary Window Close Shortcuts ✅ Passed Diff only touches screenshot crop logic, tests, ABI layout, and logging; no NSWindow/NSPanel/WindowGroup or close-shortcut registration changes.
Cmux Source Artifacts ✅ Passed The only changed path is a normal source file; no logs, caches, build output, or other artifact paths were added.
Cmux No Test Or Debug Seam In Production Source ✅ Passed PASS: the production diff only updates crop rendering and a redundant await; no new DEBUG/test-only members or widened test seams appear in Sources/.
Cmux No Ambient Global State ✅ Passed PASS: PR only mutates existing type methods; no new top-level funcs/vars or singleton state in production code. New swizzle helpers stay confined to the test suite.
Title check ✅ Passed The title clearly summarizes the main change: fixing Retina scaling in annotation crops.
Description check ✅ Passed The description covers summary, root cause, testing, localization, and demo video, with only the review trigger and checklist sections missing.
✨ 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-8827-draw-crop-zoom

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.

@chatgpt-codex-connector

Copy link
Copy Markdown

To use Codex here, create a Codex account and connect to github.

@greptile-apps

greptile-apps Bot commented Jul 24, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes a Retina-display scaling bug in design-mode annotation crops. BrowserScreenshotCrop.croppedImage previously used NSImage.lockFocus(), which scaled the backing bitmap to the current display's backing factor, so a 200×100 crop encoded as 400×200 pixels on Retina. The fix replaces it with an explicit NSBitmapImageRep whose pixel dimensions match the selection coordinates exactly, and adds @MainActor isolation to match the function's AppKit thread contract.

  • Core fix (BrowserScreenshotPipeline.swift): croppedImage now creates an explicit NSBitmapImageRep(pixelsWide: Int(cropRect.width), pixelsHigh: Int(cropRect.height)), draws into it via NSGraphicsContext, and wraps the result in an NSImage. The @MainActor annotation (previously flagged as missing in earlier review) is now present.
  • Overlay simplification (BrowserDesignModeRuntime.js, BrowserDesignModeController+Capture.swift): The imageURL base64 round-trip through JS is removed; regions now render as transparent dashed outlines while the screenshot is stored natively in screenshotStore for agent context.
  • Regression test (BrowserScreenshotCropTests.swift): A new deterministic test installs a process-wide NSImage.lockFocus swizzle (forwarding all unrelated threads to AppKit unchanged) to prove the old 2× path would have produced 400×200, while the new explicit-bitmap path always produces 200×100 regardless of host display scale factor.

Confidence Score: 5/5

Safe to merge — the crop now writes into an explicit 1:1 pixel bitmap, the @mainactor annotation is in place, and all three previously flagged issues (actor isolation, color space, duplicate interpolation hint) have been addressed.

The production change is narrow and well-understood: one function replaced a display-scale-dependent lockFocus path with an explicit NSBitmapImageRep whose pixel dimensions are directly derived from the selection coordinates. The @mainactor annotation now matches the function's actual thread requirements. The JS side consistently drops the imageURL field across runtime, call site, and test. The regression test is deterministic across display scales. No new actor isolation gaps, blocking primitives, global state, or test seams in production sources were introduced.

No files require special attention. cmuxTests/BrowserScreenshotCropTests.swift performs a process-wide NSImage method swizzle; the @suite(.serialized) + thread-dictionary forwarding makes this safe, but reviewers should be aware of the mechanism if other tests begin using NSImage.lockFocus.

Important Files Changed

Filename Overview
Sources/Panels/BrowserScreenshotPipeline.swift Core fix: replaces NSImage.lockFocus with an explicit NSBitmapImageRep for 1:1 pixel-to-point crop; adds @mainactor annotation; uses .calibratedRGB and single-channel imageInterpolation. Logic is correct and well-scoped.
Sources/Panels/BrowserDesignModeController+Capture.swift Removes imageURL from the JS completeAnnotationCapture call; screenshot is still saved to screenshotStore for agent context. Error handling and operation-revision guards are unchanged and correct.
cmuxTests/BrowserScreenshotCropTests.swift New regression test; uses process-scoped NSImage.lockFocus swizzle (forwards unrelated threads to AppKit) to make the pre-fix code deterministically emit 400x200 pixels, and verifies the new code emits 200x100. @suite(.serialized) + @mainactor scope the swizzle safely. Pixel-color spot checks validate crop geometry.
Packages/macOS/CmuxBrowser/Sources/CmuxBrowser/Resources/BrowserDesignModeRuntime.js Renames annotation cards to transparent outlines, removes imageURL field from region references and completeAnnotationCapture validation, and simplifies hover shadow. Changes are internally consistent.
webviews/src/browser-design-mode-runtime.test.ts Test suite updated to remove imageURL arguments, assert transparent outlines, and verify hover/unhover boxShadow resets to none. Changes are consistent with the JS runtime changes.
Packages/macOS/CmuxTerminalCore/Tests/CmuxTerminalCoreTests/GhosttySurfaceConfigABITests.swift ABI layout test updated for ghostty_surface_config_s struct growth (120->168 bytes); two new fields pty_tee_cb and pty_tee_userdata added. Routine lockstep update with the upstream C struct change.
cmux.xcodeproj/project.pbxproj Wires BrowserScreenshotCropTests.swift into the cmuxTests target. No other structural changes.

Sequence Diagram

sequenceDiagram
    participant UA as User (Annotation Draw)
    participant BDM as BrowserDesignModeController
    participant Pipeline as BrowserScreenshotCrop
    participant Store as screenshotStore
    participant JS as BrowserDesignModeRuntime.js

    UA->>BDM: receiveAnnotationCaptureRequestData
    BDM->>BDM: "captureAnnotation (Task @MainActor)"
    BDM->>BDM: captureStableAnnotation → NSImage
    BDM->>Pipeline: "croppedImage(@MainActor) NSBitmapImageRep(pixelsWide, pixelsHigh) → 1:1 pixel crop"
    Pipeline-->>BDM: NSImage (correct pixel dimensions)
    BDM->>Store: save(pngData) → screenshotURL
    BDM->>JS: completeAnnotationCapture(id, x, y, w, h, scrollX, scrollY, vpW, vpH)
    Note over JS: No imageURL — renders transparent dashed outline only
    JS-->>BDM: snapshot
    BDM->>BDM: "apply(snapshot), phase = .captured"
Loading

Reviews (12): Last reviewed commit: "test(browser): verify cropped annotation..." | Re-trigger Greptile

Comment thread cmuxTests/BrowserScreenshotCropTests.swift
@greptile-apps

greptile-apps Bot commented Jul 24, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes a Retina display bug in BrowserScreenshotCrop.croppedImage where NSImage.lockFocus() was creating a backing bitmap at the current display scale (2× on Retina), causing a 200×100 crop to be stored and exported as 400×200 pixels. The fix replaces lockFocus()/unlockFocus() with an explicit NSBitmapImageRep whose pixel dimensions are pinned to the logical crop dimensions, then draws into an NSGraphicsContext backed by that bitmap.

  • Production fix (BrowserScreenshotPipeline.swift): Replaces the lockFocus()-based crop path with an explicit NSBitmapImageRep/NSGraphicsContext path, preserving the existing coordinate-transform logic untouched.
  • Regression test + project wiring: Adds a @MainActor Swift Testing suite that constructs a 400×300 source bitmap, crops at 2× scale, encodes to PNG, and asserts the encoded pixel dimensions are 200×100 — validating display-scale independence through the full export path.

Confidence Score: 4/5

The Retina scaling fix is sound — pinning the bitmap to logical pixel dimensions is the correct approach — but croppedImage directly writes to NSGraphicsContext.current without an @MainActor annotation, leaving a background-actor call site unguarded by the type system.

The core logic is correct: the explicit NSBitmapImageRep with pinned pixel dimensions eliminates the display-scale dependency, the coordinate transforms were already correct, and the test validates the full PNG-encode path. The one concern is that croppedImage now explicitly mutates NSGraphicsContext.current — a main-thread AppKit contract — without @MainActor, so the function's isolation guarantee relies entirely on every caller happening to be on the main actor rather than being enforced by the compiler. Adding @MainActor is a one-line fix and the test already marks itself @MainActor, confirming the intent.

Sources/Panels/BrowserScreenshotPipeline.swift — the missing @MainActor on croppedImage and the .deviceRGB color-space choice.

Important Files Changed

Filename Overview
Sources/Panels/BrowserScreenshotPipeline.swift Core Retina fix: replaces lockFocus with explicit NSBitmapImageRep/NSGraphicsContext; new croppedImage implementation mutates NSGraphicsContext.current without @mainactor annotation, creating a latent actor-isolation risk.
cmuxTests/BrowserScreenshotCropTests.swift New regression test; correctly validates 1:1 pixel-to-coordinate mapping through the full PNG encode path; no issues.
cmux.xcodeproj/project.pbxproj Adds BrowserScreenshotCropTests.swift to the test target's file references and build sources; project wiring looks correct.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A["captureAndWrite(@MainActor)"] --> B["snapshot() → NSImage"]
    B --> C["BrowserScreenshotCrop.croppedImage\n(nonisolated static)"]
    C --> D["imageRect(forSelectionInView:)\n→ cropRect (integral)"]
    D --> E["NSBitmapImageRep\npixelsWide: cropRect.width\npixelsHigh: cropRect.height"]
    E --> F["NSGraphicsContext(bitmapImageRep:)"]
    F --> G["saveGraphicsState()\nNSGraphicsContext.current = context"]
    G --> H["image.draw(in: 0,0,w,h  from: cropRect)"]
    H --> I["restoreGraphicsState()"]
    I --> J["NSImage(size: cropRect.size)\n.addRepresentation(bitmap)"]
    J --> K["BrowserScreenshotPasteboardWriter\n.pngData / .write"]
    K --> L["tiffRepresentation → PNG"]
    L --> M["NSPasteboard / Data output"]
Loading

Comments Outside Diff (1)

  1. Sources/Panels/BrowserScreenshotPipeline.swift, line 116-120 (link)

    P1 The new implementation directly writes to NSGraphicsContext.current and manipulates the thread-local graphics state stack — both operations are AppKit main-thread contracts. Without @MainActor, the function's type signature permits a background-actor caller to invoke it without any compiler error or warning, which would race with main-thread AppKit drawing. The function should be annotated @MainActor to match the isolation it already requires in practice (its only production call site is inside the @MainActor captureAndWrite).

    Rule Used: Flag new or materially worsened Swift 6 actor isol... (source)

Reviews (2): Last reviewed commit: "fix(browser): crop screenshots at native..." | Re-trigger Greptile

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

@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/BrowserScreenshotPipeline.swift`:
- Around line 130-133: The crop pipeline must use the backing bitmap’s pixel
dimensions rather than NSImage.size for both imageRect(...) crop calculations
and NSBitmapImageRep allocation. Update the relevant imageRect and bitmap
creation flow to derive width and height from the selected representation or
CGImage, preserving consistent pixel-to-coordinate mapping, and add a regression
test where logical image size differs from backing pixel dimensions.
🪄 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 Plus

Run ID: d46e31e2-bf1b-4ee9-a35c-920e6ac56cdd

📥 Commits

Reviewing files that changed from the base of the PR and between 86c56b7 and 463b878.

📒 Files selected for processing (3)
  • Sources/Panels/BrowserScreenshotPipeline.swift
  • cmux.xcodeproj/project.pbxproj
  • cmuxTests/BrowserScreenshotCropTests.swift

Comment thread Sources/Panels/BrowserScreenshotPipeline.swift
@greptile-apps

greptile-apps Bot commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes a Retina-display bug in BrowserScreenshotCrop.croppedImage where NSImage.lockFocus() silently created a backing at the current display scale (2× on Retina), causing annotation crops to encode at twice the intended pixel dimensions. The fix replaces lockFocus/unlockFocus with an explicit NSBitmapImageRep whose pixelsWide/pixelsHigh are set from the logical cropRect, decoupling the encoded resolution from the ambient display scale.

  • BrowserScreenshotPipeline.swift: draws into an explicit bitmap context created with NSGraphicsContext(bitmapImageRep:) instead of lockFocus; adds a new invalidImageRepresentation guard already covered by the existing localized error.
  • BrowserScreenshotCropTests.swift + project.pbxproj: adds a regression test that encodes the cropped image to PNG and asserts on the bitmap's physical pixel dimensions rather than just NSImage.size, and wires it into the test target.

Confidence Score: 4/5

The production change is a targeted, well-contained fix to the AppKit drawing path; the only outstanding concern is that the new regression test does not distinguish the old and new code paths when CI runs on a 1× display.

The core change correctly replaces a display-scale-dependent drawing path with an explicit bitmap of known pixel dimensions, and the graphics-state save/restore is properly balanced. The test assertions check physical pixel counts, but the test would pass against both old and new code on a non-Retina host because the source image is created at 1:1 pixel density.

BrowserScreenshotCropTests.swift — the test design should be reviewed to ensure it catches the Retina scaling regression on 1× CI machines.

Important Files Changed

Filename Overview
Sources/Panels/BrowserScreenshotPipeline.swift Replaces lockFocus/unlockFocus with an explicit NSBitmapImageRep whose pixel dimensions are set from cropRect; fix is correct and paired with saveGraphicsState/restoreGraphicsState. New invalidImageRepresentation guard is already localized.
cmuxTests/BrowserScreenshotCropTests.swift New regression test checks encoded PNG pixel dimensions via BrowserScreenshotPasteboardWriter.pngData; correctly wired in pbxproj. Test only catches the Retina-scale bug when running on a Retina host — on 1× CI the assertion passes with both old and new code.
cmux.xcodeproj/project.pbxproj Adds PBXFileReference and PBXBuildFile entries for BrowserScreenshotCropTests.swift and places it in the test target's Sources build phase; no external dependency or Package.resolved impact.

Sequence Diagram

sequenceDiagram
    participant Caller as BrowserScreenshotPipeline
    participant Crop as BrowserScreenshotCrop
    participant Bitmap as NSBitmapImageRep
    participant Ctx as NSGraphicsContext
    participant Writer as BrowserScreenshotPasteboardWriter

    Caller->>Crop: croppedImage(from:selectionInView:viewBounds:)
    Crop->>Crop: imageRect() → cropRect (display-scale-independent)
    Crop->>Bitmap: init(pixelsWide: cropRect.width, pixelsHigh: cropRect.height)
    Crop->>Ctx: NSGraphicsContext(bitmapImageRep:)
    Crop->>Ctx: saveGraphicsState()
    Crop->>Ctx: "current = context"
    Crop->>Bitmap: image.draw(in:from:cropRect operation:.copy)
    Crop->>Ctx: restoreGraphicsState()
    Crop->>Crop: NSImage(size:).addRepresentation(bitmap)
    Crop-->>Caller: NSImage (explicit pixel-locked bitmap)
    Caller->>Writer: write(image, to: pasteboard)
    Writer->>Writer: tiffRepresentation → pngData
    Writer-->>Caller: done
Loading

Reviews (3): Last reviewed commit: "fix(browser): crop screenshots at native..." | Re-trigger Greptile

Comment thread cmuxTests/BrowserScreenshotCropTests.swift
@austinywang
austinywang force-pushed the issue-8827-draw-crop-zoom branch 2 times, most recently from d6c89ed to 6f89dfb Compare July 24, 2026 06:34
@cursor

cursor Bot commented Jul 24, 2026

Copy link
Copy Markdown

Bugbot is paused — on-demand spend limit reached

Bugbot uses usage-based billing for this team and has hit its on-demand spend limit.

A team admin can raise the spend limit in the Cursor dashboard, or wait for the next billing cycle to continue.

@austinywang

Copy link
Copy Markdown
Contributor Author

@codex review
@coderabbitai review
@greptile-apps review
@cubic-dev-ai review

@chatgpt-codex-connector

Copy link
Copy Markdown

To use Codex here, create a Codex account and connect to github.

@coderabbitai

coderabbitai Bot commented Jul 24, 2026 •

Copy link
Copy Markdown

@austinywang I’ll review the changes in #8831.

✅ Action performed

Review finished.

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.

@cubic-dev-ai

cubic-dev-ai Bot commented Jul 24, 2026

Copy link
Copy Markdown

@codex review
@coderabbitai review
@greptile-apps review
@cubic-dev-ai review

@austinywang I can't start this review because your workspace has reached its free monthly review limit. cubic has reviewed 241,631 of the 240,000 allowed lines of code this month. Reviews resume on 1 August 2026 (in 8 days). Paid plans include much higher monthly review limits. Upgrade now to resume reviews.

To help optimise your usage, you can tune cubic to get the most out of your usage limits:

Learn more →

@chatgpt-codex-connector

Copy link
Copy Markdown

To use Codex here, create a Codex account and connect to github.

1 similar comment
@chatgpt-codex-connector

Copy link
Copy Markdown

To use Codex here, create a Codex account and connect to github.

@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

♻️ Duplicate comments (1)
Sources/Panels/BrowserScreenshotPipeline.swift (1)

121-143: 🎯 Functional Correctness | 🟠 Major

Crop math still keyed off image.size, not backing pixel dimensions — same root cause flagged previously.

croppedImage computes cropRect via imageRect(..., imageSize: image.size) and then allocates the new bitmap with pixelsWide: Int(cropRect.width)/pixelsHigh: Int(cropRect.height). NSImage.size is a logical point size, independent of the backing representation's pixelsWide/pixelsHigh; if the source image's .size doesn't equal its native pixel dimensions (e.g. any capture path that sets a logical size different from the bitmap's pixel count), the pixel math in imageRect — and therefore the exact size of the bitmap this diff allocates — is wrong, even though the drawing itself is now Retina-independent. This is the same concern raised on the prior commit: crop box derivation should use the source representation's pixel dimensions, not NSImage.size, as the single source of truth.

The new regression test in BrowserScreenshotCropTests.swift doesn't cover this: makeBitmapImage explicitly sets bitmap.size equal to pixelsWide/pixelsHigh, so image.size == pixel size in the test and the mismatch case is never exercised.

Suggested direction (outside this hunk, in imageRect/croppedImage): derive pixel dimensions from the source bitmap representation instead of image.size.

private static func pixelSize(of image: NSImage) -> NSSize? {
    guard let rep = image.representations.compactMap({ $0 as? NSBitmapImageRep }).first else {
        return nil
    }
    return NSSize(width: rep.pixelsWide, height: rep.pixelsHigh)
}

Then fail closed (throw invalidImageRepresentation) when no bitmap representation is available, rather than falling back to the logical .size.

Based on path instructions ("If any PR logic derives crop/scale correctness from view/string heuristics, refactor to use explicit structured inputs ... and fail/disable when the authoritative signal is missing rather than guessing.") from .github/review-bot-rules/reliability-single-source-of-truth.md, and on the prior review comment on this same code path (lines 130-133 of the previous commit) raising the identical image.size vs. pixel-dimension concern.

🤖 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 121 - 143,
Update croppedImage and imageRect to derive crop dimensions from the source
NSBitmapImageRep pixel dimensions rather than image.size. Reuse a pixel-size
helper or equivalent representation lookup, and throw
BrowserScreenshotError.invalidImageRepresentation when no bitmap representation
exists; preserve the existing invalidSelection validation and bitmap allocation
flow.

Source: Path instructions

🤖 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/BrowserScreenshotCropTests.swift`:
- Around line 32-103: Update the test using withImageFocusBackingScale so it
exercises a code path that actually invokes NSImage.lockFocus and unlockFocus,
or remove the helper and add a focused regression scenario that reproduces the
pixel/point mismatch. Ensure encodedCropUsesOnePixelPerSnapshotCoordinate
validates the intended Retina backing-scale behavior rather than only explicit
NSBitmapImageRep crop dimensions.

---

Duplicate comments:
In `@Sources/Panels/BrowserScreenshotPipeline.swift`:
- Around line 121-143: Update croppedImage and imageRect to derive crop
dimensions from the source NSBitmapImageRep pixel dimensions rather than
image.size. Reuse a pixel-size helper or equivalent representation lookup, and
throw BrowserScreenshotError.invalidImageRepresentation when no bitmap
representation exists; preserve the existing invalidSelection validation and
bitmap allocation flow.
🪄 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 Plus

Run ID: 319799cd-3df0-449c-9b7d-3d5bcfd7de84

📥 Commits

Reviewing files that changed from the base of the PR and between 463b878 and d6c89ed.

📒 Files selected for processing (3)
  • Sources/Panels/BrowserScreenshotPipeline.swift
  • cmux.xcodeproj/project.pbxproj
  • cmuxTests/BrowserScreenshotCropTests.swift

Comment thread cmuxTests/BrowserScreenshotCropTests.swift Outdated
@austinywang
austinywang force-pushed the issue-8827-draw-crop-zoom branch from 6f89dfb to b788370 Compare July 24, 2026 06:43

@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/BrowserScreenshotPipeline.swift`:
- Around line 131-161: Strengthen the regression test for the crop flow around
NSBitmapImageRep and PNG encoding by using a fixture with distinct row and
column colors, then decode the encoded PNG and assert representative pixel
colors from the expected crop region. Keep the existing dimension checks, and
ensure the assertions detect blank, vertically flipped, or incorrectly cropped
output.
🪄 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 Plus

Run ID: 0f3e5151-5b35-47a3-a663-24b807afa8f3

📥 Commits

Reviewing files that changed from the base of the PR and between 6f89dfb and b788370.

📒 Files selected for processing (3)
  • Sources/Panels/BrowserScreenshotPipeline.swift
  • cmux.xcodeproj/project.pbxproj
  • cmuxTests/BrowserScreenshotCropTests.swift

Comment thread Sources/Panels/BrowserScreenshotPipeline.swift
@cursor

cursor Bot commented Jul 24, 2026

Copy link
Copy Markdown

Bugbot is paused — on-demand spend limit reached

Bugbot uses usage-based billing for this team and has hit its on-demand spend limit.

A team admin can raise the spend limit in the Cursor dashboard, or wait for the next billing cycle to continue.

@austinywang

Copy link
Copy Markdown
Contributor Author

@coderabbitai review
@greptile-apps review
@cubic-dev-ai review

@cubic-dev-ai

cubic-dev-ai Bot commented Jul 24, 2026

Copy link
Copy Markdown

@coderabbitai review
@greptile-apps review
@cubic-dev-ai review

@austinywang I can't start this review because your workspace has reached its free monthly review limit. cubic has reviewed 241,631 of the 240,000 allowed lines of code this month. Reviews resume on 1 August 2026 (in 8 days). Paid plans include much higher monthly review limits. Upgrade now to resume reviews.

To help optimise your usage, you can tune cubic to get the most out of your usage limits:

Learn more →

@coderabbitai

coderabbitai Bot commented Jul 24, 2026 •

Copy link
Copy Markdown

@austinywang I’ll review the changes in #8831.

✅ Action performed

Review finished.

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.

@austinywang
austinywang merged commit caf70df into main Jul 24, 2026
10 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.

Design mode draw: annotation screenshot crop is zoomed into the page instead of 1:1

1 participant