maCopy/README.md
ilia 80a6c01cdb Fix cursor positioning, resize, and paste; add App tests and Cursor rules
- Use CoreGraphics (core-graphics crate) for global mouse position instead
  of Tauri's window-relative cursor_position() which fails when hidden
- Switch to titleBarStyle overlay with hiddenTitle for native resize handles
  while keeping the frameless look (decorations:false had no resize affordance)
- Fix paste_and_refocus: use .output() instead of .spawn() so osascript
  actually completes, increase delay to 250ms for reliable app refocus
- Add comprehensive App.test.tsx (7 integration tests) bringing total to 47
- Add multi-select and type badge tests for ClipboardList and ContextMenu
- Update Tauri mock in setup.ts with innerSize/scaleFactor for resize tests
- Create .cursor/rules/ with 4 rule files for project conventions
- Add npm run lint and npm run check scripts
- Update README with full usage table and current test counts (74 total)

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-12 14:15:42 -04:00

7.0 KiB

maCopy

A fast, native macOS clipboard manager that lives in your menu bar. Built with Tauri 2, React, TypeScript, and SQLite.

macOS Tauri License

Features

  • Menu bar app — no dock icon, stays out of your way
  • Global hotkeyCmd+Shift+V opens the window from anywhere
  • Clipboard monitoring — polls every 500ms for text, images, and file paths
  • Full-text search — instant filtering via SQLite FTS5
  • Quick pasteCmd+1 through Cmd+9 to paste the Nth item directly into the previous app
  • Paste & return — clicking an entry copies it to clipboard, hides the window, and auto-pastes into the previously-focused app
  • Multi-selectCmd+Click to toggle, Shift+Arrow or Shift+Click to range-select, Cmd+A for all, Enter to paste selected
  • Pin entries — pinned items stay at the top and are never auto-deleted
  • Context menu — right-click for Paste, Pin/Unpin, Delete (with multi-select support)
  • Resizable window — drag edges to resize; size is remembered between sessions
  • Window positioning — choose where the window appears: near cursor, center, or any corner (configurable in Settings)
  • Auto-trim — keeps up to 50K entries (configurable: 1K/5K/10K/50K)
  • Dark/light mode — follows macOS system appearance
  • Privacy — whitespace-only entries are ignored; pause monitoring from the tray

Prerequisites

  • macOS 10.15+
  • Rust 1.77+ — install via rustup
  • Node.js 18+ and npm
  • Xcode Command Line Toolsxcode-select --install
  • Accessibility permission — required for auto-paste (System Settings → Privacy & Security → Accessibility → add maCopy)

Quick Start

git clone gitea@10.0.30.169:ilia/maCopy.git
cd maCopy
npm install
npm run tauri dev

The app will compile the Rust backend, start the Vite dev server, and launch the menu bar app.

Usage

Action How
Open/close window Click tray icon or press Cmd+Shift+V
Paste an entry Click it, or press Enter
Quick paste Cmd+1 through Cmd+9
Search Just start typing
Select multiple Cmd+Click or Shift+Arrow
Select all Cmd+A
Delete Backspace or Delete (on selected items)
Pin/unpin Right-click → Pin/Unpin
Settings Tray icon → Settings…
Dismiss Escape or click outside

Development

Project Structure

maCopy/
├── src/                        # React + TypeScript frontend
│   ├── main.tsx                # Entry point
│   ├── App.tsx                 # Main app: state, keyboard nav, multi-select
│   ├── App.test.tsx            # App integration tests
│   ├── index.css               # Tailwind v4 + custom theme tokens
│   ├── types.ts                # Shared TypeScript interfaces
│   ├── test/                   # Test setup and factories
│   │   ├── setup.ts            # Tauri API mocks for jsdom
│   │   └── factories.ts        # makeEntry(), makeSettings()
│   └── components/
│       ├── SearchBar.tsx
│       ├── ClipboardList.tsx   # Entry list with multi-select, type badges
│       ├── ContextMenu.tsx     # Right-click menu with multi-select labels
│       └── SettingsPanel.tsx   # Toggles, max history, window position
├── src-tauri/                  # Rust backend
│   ├── Cargo.toml
│   ├── tauri.conf.json         # Tauri config, permissions, window
│   └── src/
│       ├── main.rs             # Binary entry point
│       ├── lib.rs              # App setup: tray, hotkey, window management
│       ├── clipboard.rs        # Background polling thread (500ms, SHA-256 dedup)
│       ├── db.rs               # SQLite CRUD + FTS5 + settings + auto-trim
│       └── commands.rs         # Tauri IPC commands
├── .cursor/rules/              # Cursor AI rules for this project
├── package.json
├── vite.config.ts
├── vitest.config.ts
└── tsconfig.json

Scripts

Command Description
npm run tauri dev Run in development mode with hot reload
npm run tauri build Build a release .app bundle
npm test Run frontend tests (Vitest, 47 tests)
npm run test:rust Run Rust backend tests (27 tests)
npm run test:all Run all tests (frontend + backend)
npm run lint TypeScript type-check
npm run check Lint + all tests

Tech Stack

Layer Technology
Framework Tauri 2
Frontend React 18, TypeScript, Tailwind CSS v4
Backend Rust, rusqlite (bundled SQLite)
Clipboard arboard for system clipboard access
Mouse position core-graphics for global cursor coordinates
Search SQLite FTS5 with content-sync triggers
Hotkey tauri-plugin-global-shortcut
Autostart tauri-plugin-autostart
Testing Vitest + @testing-library/react (frontend), cargo test (backend)

Architecture

Clipboard Polling

A dedicated Rust thread polls the system clipboard every 500ms using the arboard crate. Each new clipboard value is hashed (SHA-256) and compared against the last known hash to avoid duplicates. Text, images (stored as base64 PNG data URIs), and file paths are all captured.

Paste & Return

When you select an entry, maCopy writes it to the system clipboard, hides its window, waits 250ms for macOS to refocus the previous app, then simulates Cmd+V via AppleScript. This requires Accessibility permission.

SQLite + FTS5

The database uses a content-synced FTS5 virtual table with triggers that automatically keep the full-text index in sync with the clipboard_entries table. This enables instant prefix search as you type.

Window Behavior

The window uses titleBarStyle: "overlay" for native resize handles while keeping the frameless aesthetic. It's always-on-top and hides on blur. Position is determined by the user's setting (near cursor via CoreGraphics, center, or a screen corner).

Data Storage

The SQLite database is stored at:

~/Library/Application Support/maCopy/clipboard.db

Testing

Frontend (47 tests)

npm test

Tests cover App integration, SearchBar, ClipboardList (including multi-select), ContextMenu, and SettingsPanel. Tauri APIs are mocked in src/test/setup.ts.

Backend (27 tests)

npm run test:rust

Tests use in-memory SQLite databases and cover: CRUD operations, FTS5 search, auto-trim with pin preservation, settings persistence, SHA-256 hashing, and PNG encoding.

All tests

npm run test:all     # 74 total tests
npm run check        # lint + all tests

Building for Release

npm run tauri build

The built .app bundle will be in src-tauri/target/release/bundle/macos/.

License

MIT