Skip to content

Clipboard enhancements: pin items, performance overhaul, UX improvements#248

Open
MrLionware wants to merge 3 commits intoospfranco:mainfrom
MrLionware:feature/clipboard-enhancements
Open

Clipboard enhancements: pin items, performance overhaul, UX improvements#248
MrLionware wants to merge 3 commits intoospfranco:mainfrom
MrLionware:feature/clipboard-enhancements

Conversation

@MrLionware
Copy link

@MrLionware MrLionware commented Mar 13, 2026

Summary

Major clipboard system rework with pin support, performance optimizations, and multiple UX improvements across the app.

Clipboard Pin Feature

  • Pinnable clipboard items that always stay at the top of the list, surviving reorders from new copies
  • Cmd+K opens a contextual actions menu positioned above the bottom bar; Cmd+P toggles pin (works both from the menu and as a direct shortcut)
  • Pin state persists across app restarts via secure storage
  • Pinned items are never evicted when the clipboard reaches the 1000-item limit
  • Selection follows the item to its new position after pinning/unpinning
  • Opening clipboard defaults selection to the first unpinned (most recently copied) item, since pinned items are always visible

Clipboard Performance Overhaul

  • O(1) duplicate detection via hash map instead of O(n) linear scan across all items
  • MiniSearch indexes only first 500 chars instead of full text, dramatically reducing memory and indexing time for large texts
  • Search results map back to full items by ID, avoiding storing full text in the search index
  • In-place array mutations (splice/unshift) instead of full array spread copies
  • Debounced persistence (2s) instead of persisting on every single change
  • Text preview truncated at 5,000 chars with total length indicator

Clipboard UX Improvements

  • Reduce clipboard detection delay from 1s to 200ms (native NSPasteboard polling interval)
  • Fix empty state showing [] instead of "No items"
  • Fix clipboard list not updating in real-time when copying text from the preview panel (MobX computed was returning same array reference)
  • Fix down arrow navigation going past visible items during search
  • Detail panel is now scrollable with selectable text and proper content padding
  • deleteItem now correctly resolves display index to raw index (required after pin reordering)

Other App Improvements

  • File search hotkey now shows the window (file_search added to itemsThatShouldShowWindow)
  • Hotkey toggle: second press hides window if the same view is already showing
  • ESC always closes the window instantly instead of navigating backwards through views
  • Process manager shows app icons next to process names
  • Fix Xcode 16.3 sandbox settings (ENABLE_APP_SANDBOX, ENABLE_USER_SCRIPT_SANDBOXING) blocking app functionality
  • Fix systemPreferences crash when accessing ~/Library/PreferencePanes in sandboxed builds

Files Changed

File Changes
src/stores/clipboard.store.tsx Pin support, hash-based dedup, debounced persist, MiniSearch optimization, display-index-aware delete
src/stores/keystroke.store.ts Cmd+K menu toggle, Cmd+P pin shortcut, menu key guard (modifier-aware), ESC simplification, clipboard nav fix
src/stores/ui.store.tsx File search window display, hotkey toggle, clipboard default selection to first unpinned item, file search debounce
src/widgets/clipboard.widget.tsx Pin indicator, actions menu, scrollable/selectable preview, truncation, empty state fix
src/widgets/processes.widget.tsx App icon rendering for processes
src/stores/systemPreferences.tsx Try-catch for sandboxed PreferencePanes access
macos/sol-macOS/lib/ClipboardHelper.swift Polling interval 1.0s → 0.2s
macos/sol.xcodeproj/project.pbxproj Disable app sandbox and user script sandboxing, Sentry upload defaults

Test Plan

  • Open clipboard manager via hotkey — selection should land on most recent (first unpinned) item
  • Press Cmd+K — actions menu appears above "More" button in bottom bar
  • Press Cmd+P — item pins/unpins, selection follows item, pin icon (📌) appears
  • Pinned items remain at top after copying new text
  • Pin state survives app restart (requires "Save History" enabled)
  • Press Shift+Delete — deletes the correct item even with pinned items reordering the list
  • Copy text from the preview panel — new entry appears immediately in the left list
  • Paste very long text (100k+ chars) — clipboard opens quickly, preview is scrollable and truncated
  • Search clipboard — pinned results appear first, down arrow stops at last visible result
  • Press ESC — window closes immediately from any view
  • Press clipboard hotkey twice — second press hides the window
  • File search hotkey opens the file search view
  • Process manager shows app icons next to process names

Major clipboard system rework with pin support, performance optimizations,
and multiple UX improvements across the app.

Clipboard pin feature:
- Add pinnable clipboard items that always stay at the top of the list
- Cmd+K opens an actions menu above the bottom bar, Cmd+P toggles pin
- Pin state persists across app restarts via secure storage
- Pinned items are never evicted when the clipboard reaches the 1000-item limit
- Selection follows the item to its new position after pinning/unpinning
- Opening clipboard defaults selection to the first unpinned (most recent) item

Clipboard performance overhaul:
- O(1) duplicate detection via hash map instead of O(n) linear scan
- MiniSearch indexes only first 500 chars instead of full text
- Search results map back to full items by ID, avoiding storing full text in index
- In-place array mutations (splice/unshift) instead of array spread copies
- Debounced persistence (2s) instead of persisting on every change
- Text preview truncated at 5000 chars with total length indicator

Clipboard UX improvements:
- Reduce clipboard detection delay from 1s to 200ms (native polling interval)
- Fix empty state showing "[]" instead of "No items"
- Fix clipboard list not updating when copying text from the preview panel
- Fix down arrow navigation going past visible items during search
- Detail panel is now scrollable with selectable text and proper padding
- Delete item now correctly resolves display index to raw index

Other app improvements:
- File search hotkey now shows the window (added to itemsThatShouldShowWindow)
- Hotkeys toggle: second press hides window if same view is already showing
- ESC always closes the window instantly instead of navigating backwards
- Process manager shows app icons next to process names
- Fix Xcode 16.3 sandbox settings blocking app functionality
- Fix systemPreferences crash when accessing ~/Library/PreferencePanes

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR delivers a major clipboard manager rework (pinning + faster search/dedup + persistence improvements) alongside several UX/hotkey tweaks, file search improvements, and macOS native/project updates to support these features and improve responsiveness.

Changes:

  • Add pinned clipboard items (top-sticky), a clipboard actions menu (⌘K), and pin toggle (⌘P) with persistence.
  • Rework clipboard performance: hashed duplicate detection, reduced search indexing payload, and debounced persistence.
  • Improve file search behavior and native search limits; update macOS project/build settings and dependencies.

Reviewed changes

Copilot reviewed 13 out of 14 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
src/stores/clipboard.store.tsx Pin state, dedup/search performance changes, debounced persistence, display-index-aware operations
src/widgets/clipboard.widget.tsx Pinned indicator, scrollable/selectable preview, truncation, empty-state fix, actions menu UI
src/stores/keystroke.store.ts ⌘K menu toggle, ⌘P pin hotkey, ESC close behavior, clipboard nav fix
src/stores/ui.store.tsx File search window display + debounce, hotkey toggle behavior, clipboard default selection logic
src/widgets/processes.widget.tsx Render process icons via FileIcon
src/stores/systemPreferences.tsx Guard sandboxed PreferencePanes access with try/catch
macos/sol-macOS/lib/FileSearch.h Add depth/result limits to native file search API
macos/sol-macOS/lib/FileSearch.mm Apply directory skipping + depth/result limiting to improve performance
macos/sol-macOS/lib/JSIBindings.mm Thread result limit state through JSI searchFiles binding
macos/sol-macOS/lib/ClipboardHelper.swift Reduce pasteboard polling interval (1.0s → 0.2s)
macos/sol.xcodeproj/project.pbxproj Update build settings (sandbox/signing/Sentry script defaults/etc.)
macos/sol.xcodeproj/xcshareddata/xcschemes/debug.xcscheme Update Xcode scheme upgrade version
macos/sol.xcodeproj/xcshareddata/xcschemes/release.xcscheme Update Xcode scheme upgrade version
macos/Podfile.lock Bump Sparkle dependency version

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

Comment on lines +21 to +31
static NSSet *skippedDirectories = nil;

static NSSet *getSkippedDirectories() {
if (!skippedDirectories) {
skippedDirectories = [NSSet setWithObjects:
@"node_modules", @".git", @"Library", @".cache",
@".Trash", @"__pycache__", @".npm", @".yarn",
@"Pods", @"build", @"DerivedData", nil];
}
return skippedDirectories;
}
Comment on lines 7 to +13
import MiniSearch from "minisearch";
import { storage } from "./storage";
import { captureException } from "@sentry/react-native";

const MAX_ITEMS = 1000;
const MAX_TEXT_INDEX_LENGTH = 500; // Only index first N chars for search
const PERSIST_DEBOUNCE_MS = 2000;
Comment on lines +231 to 238
while (store.items.length > MAX_ITEMS) {
const last = store.items[store.items.length - 1];
if (last?.pinned) break;
const removed = store.items.pop();
if (removed) {
removeFromIndex(removed);
}

store.items = store.items.slice(0, MAX_ITEMS);
}
Comment on lines +89 to +95
function removeFromIndex(item: PasteItem) {
try {
minisearch.remove({ id: item.id, indexText: "", datetime: 0 });
} catch {
// ignore missing id errors
}
}
Comment on lines +241 to +243
const [item] = store.items.splice(index, 1);
item.datetime = Date.now();
store.items.unshift(item);
Comment on lines +748 to +749
if (firstUnpinned > 0) {
store.selectedIndex = firstUnpinned;
MrLionware and others added 2 commits March 13, 2026 08:52
…view issues

Image clipboard:
- Detect images (TIFF/PNG) on pasteboard and save as temp PNGs
- Native FileImageView component (NSImageView) for displaying local images
- Show image thumbnails in clipboard list and full preview in detail panel
- Paste images back to apps via pasteFileToFrontmostApp

Hotkey optimization:
- Eliminate native→JS→native round-trip for app-opening hotkeys
- Apps with URLs now open directly in native via NSWorkspace
- Pass urlMap alongside shortcuts to updateHotkeys
- syncHotkeys() rebuilds URL map when apps change

PR review fixes:
- Thread-safe static local in getSkippedDirectories (FileSearch.mm)
- Remove unused captureException import (clipboard.store.tsx)
- Use minisearch.discard(id) instead of remove with synthetic doc
- Fix removeLastItemIfNeeded to evict oldest unpinned item from end
- Fix popToTop to update MiniSearch datetime and trigger persist
- Fix showClipboardManager selectedIndex edge case (>= 0 vs > 0)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Use NSImage(pasteboard:) for automatic format handling and check for
JPEG, HEIC, GIF, and BMP in addition to TIFF and PNG.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@ospfranco
Copy link
Owner

Before you start adding support for images you should take a look into the comments here:

#234

It's not something AI can one shot and this PR is way too large now. I appreciate the effort to improve but cannot really merge so many changes at once

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.

3 participants