You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Adds first-class image support to CopyIT, allowing users to capture copied images in clipboard history, preview them, and restore them to the macOS pasteboard for pasting into other applications.
What changed
adds an image clipboard item type with persisted binary data and its original pasteboard type
detects PNG and TIFF first, then supports other image formats recognized by macOS through Uniform Type Identifiers
prioritizes image payloads when a pasteboard entry also exposes a textual representation
displays image thumbnails directly in clipboard history
restores the original image data and format when an item is selected
deduplicates images by binary payload while preserving the existing text deduplication behavior
keeps previously persisted text-only history compatible
updates the feature documentation to include image support
Tests
Added coverage for:
image item creation and metadata preservation
image persistence and retrieval
binary image deduplication
restoring image data to NSPasteboard
The application sources pass Swift compiler type-checking. The full xcodebuild test suite could not be run locally because the environment only provides Command Line Tools and does not have a complete Xcode installation selected.
Add image clipboard capture, persistence, preview, and restore on macOS
✨ Enhancement🧪 Tests📝 Documentation🕐 40+ Minutes
AI Description
• Capture images (PNG/TIFF prioritized, then any UTI-conforming image) from NSPasteboard.
• Persist image payload + original pasteboard type and dedupe by binary data.
• Render image thumbnails in history and restore original image data back to pasteboard.
Diagram
graph TD
PB{{"NSPasteboard"}} --> MON["ClipboardMonitor"] --> ITEM["ClipboardItem"] --> STORE[("UserDefaultsStore")] --> VM["PopoverViewModel"] --> UI["Popover UI"]
UI --> COPY["CopyItemUseCase"] --> PB
subgraph Legend
direction LR
_ext{{"External"}} ~~~ _svc["Service"] ~~~ _db[("Store")]
end
Loading
High-Level Assessment
The following are alternative approaches to this PR:
1. Store images on disk (cache dir) + persist only metadata/path
➕ Avoids bloating UserDefaults with large Data blobs
➕ Easier to enforce size limits/eviction policies (LRU)
➕ Potentially faster startup/decoding for large histories
➖ Requires filesystem management, cleanup, and migration logic
➖ More failure modes (missing files, permissions, partial writes)
➖ Slightly higher implementation complexity than Data-in-JSON
2. Use NSPasteboardItem/NSImage serialization instead of raw pasteboard bytes
➕ Leverages AppKit for supported formats/conversions
➕ Can normalize to a canonical representation (e.g., PNG) to simplify dedupe
➖ May lose the original pasteboard type/format fidelity
➖ Extra conversions can be slower and may alter metadata (color profile, etc.)
Recommendation: For an initial implementation, persisting raw image bytes + original pasteboard type (as done here) is a strong choice because it preserves fidelity and keeps the design simple. The main caveat is storage growth: consider adding a maximum image byte size, or moving to a disk-backed cache if users commonly copy large images.
Files changed (12) +133 / -12
Enhancement (8) +84 / -11
AppDelegate.swiftPlumb ClipboardItem through monitor callback+1/-2
Plumb ClipboardItem through monitor callback
• Updates ClipboardMonitoring callback wiring to receive a fully-built ClipboardItem instead of a raw String. AppDelegate now saves the emitted item directly and refreshes the view model.
ClipboardItem.swiftAdd image payload fields, factory, and payload-aware dedupe helper+30/-1
Add image payload fields, factory, and payload-aware dedupe helper
• Extends ClipboardItem with optional imageData and imagePasteboardType for persisted image support. Adds an image factory initializer and a hasSamePayload helper that compares image bytes for image items and content for non-image items.
ClipboardMonitor.swiftDetect and emit image clipboard items (UTType-based) before text+23/-2
Detect and emit image clipboard items (UTType-based) before text
• Adds image detection that prioritizes PNG and TIFF, then falls back to any pasteboard type whose UTI conforms to .image. If an image is found, emits an image ClipboardItem and skips string handling, ensuring images win when multiple representations exist.
CopyItemUseCase.swiftRestore images to NSPasteboard using original type + bytes+7/-1
Restore images to NSPasteboard using original type + bytes
• Writes image items back to the pasteboard with setData(forType:) using the preserved pasteboard type. Retains existing string behavior for non-image items.
UserDefaultsStore.swiftDeduplicate history by image bytes for images, text content otherwise+1/-1
Deduplicate history by image bytes for images, text content otherwise
• Switches dedupe logic to use ClipboardItem.hasSamePayload, enabling binary deduplication for images while preserving text/url dedupe semantics. Existing persisted text-only history remains decodable via optional fields.
ClipboardItemRow.swiftRender image thumbnails in history rows+20/-3
Render image thumbnails in history rows
• Adds an itemPreview that decodes NSImage from stored imageData and displays a thumbnail for image items. Falls back to the existing SF Symbol icon for non-image types and adds a photo icon mapping for .image.
ClipboardStoreTests.swiftAdd tests for image persistence and binary deduplication+20/-0
Add tests for image persistence and binary deduplication
• Adds coverage verifying imageData survives a save/fetch cycle and that image items are deduped by Data rather than the shared display label ("Image").
ClipboardItem now includes full imageData, and UserDefaultsStore JSON-encodes/rewrites the entire
history on each save, making each clipboard event’s persistence work proportional to total stored
image bytes. This can significantly increase CPU/time and storage usage when users copy large images
(e.g., screenshots).
+ let imageData: Data?+ let imagePasteboardType: String?
Evidence
The PR introduces imageData: Data? on the persisted model, and the store persists the full
[ClipboardItem] via JSONEncoder into a single UserDefaults key; with images present, every
save/fetch now processes large binary payloads repeatedly.
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution
## Issue description
`ClipboardItem` now carries raw image bytes (`Data`), and the existing `UserDefaultsStore` persists the entire `[ClipboardItem]` by JSON-encoding it. With images, this becomes a potentially large read/modify/write cycle per clipboard change.
## Issue Context
- `UserDefaultsStore.save()` reads + decodes all items, mutates, then re-encodes + writes the full array back.
- `Data` is JSON-encoded as base64, increasing encoded size and adding CPU overhead.
## Fix Focus Areas
- ClipboardManager/Core/Models/ClipboardItem.swift[3-48]
- ClipboardManager/Infrastructure/Persistence/UserDefaultsStore.swift[15-44]
## Suggested direction
- Persist image payloads outside `UserDefaults` (e.g., Application Support directory files, CoreData blobs, or a lightweight on-disk cache).
- Store only a stable reference in `ClipboardItem` (e.g., `imageFileName`/`imageDigest`), plus `imagePasteboardType`.
- Add an image size budget (per-item and/or total) and skip or downscale images beyond the cap.
- Keep backward compatibility by decoding older entries without the new fields and by migrating existing stored entries when first loaded.
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
2. NSImage decoded in body✓ Resolved🐞 Bug➹ Performance
Description
ClipboardItemRow constructs NSImage(data:) inside a computed view used by body, so view
invalidations can trigger repeated decoding of full image data on the main thread. This can increase
CPU/memory pressure and make the history list feel sluggish with large images.
+ if item.type == .image,+ let data = item.imageData,+ let image = NSImage(data: data) {+ Image(nsImage: image)
Evidence
The new itemPreview view decodes NSImage(data:) inline as part of body rendering, which can be
executed multiple times across list updates and state changes.
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution
## Issue description
The row view decodes raw image bytes (`NSImage(data:)`) during SwiftUI view construction. SwiftUI may reevaluate `body` frequently, which can repeatedly decode the same large payload.
## Issue Context
The preview is displayed at 40×32, but the decode input is still the full-resolution data.
## Fix Focus Areas
- ClipboardManager/UI/Popover/ClipboardItemRow.swift[48-63]
## Suggested direction
- Generate and persist a small thumbnail when ingesting the image (e.g., in `ClipboardMonitor.makeImageItem()` or when saving), and render that thumbnail in the row.
- Or cache the decoded `NSImage`/thumbnail by `ClipboardItem.id` (e.g., via an image cache in the view model or an `@StateObject` loader) so decoding happens once per item rather than on each render.
- Consider decoding off the main thread if generating thumbnails at runtime.
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
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
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
Adds first-class image support to CopyIT, allowing users to capture copied images in clipboard history, preview them, and restore them to the macOS pasteboard for pasting into other applications.
What changed
imageclipboard item type with persisted binary data and its original pasteboard typeTests
Added coverage for:
NSPasteboardThe application sources pass Swift compiler type-checking. The full
xcodebuildtest suite could not be run locally because the environment only provides Command Line Tools and does not have a complete Xcode installation selected.Closes #1