maCopy
macOS clipboard manager in the menu bar. Built with Tauri 2, React, TypeScript, and SQLite.
Features
| Area | Behavior |
|---|---|
| Menu bar | No dock icon; tray glyph is a clipboard with ∞ |
| Global hotkey | Ctrl+` opens the window (does not steal macOS Cmd+` window-cycle); configurable in Settings |
| Clipboard monitoring | Watches pasteboard changeCount; captures text, images, and file paths |
| List performance | Truncated previews in list API; image blobs stay in SQLite until paste |
| Search | Full-text filtering via SQLite FTS5 |
| Quick paste | Cmd+1 through Cmd+9 paste the Nth item into the previous app |
| Paste and return | Click copies, hides window, auto-pastes into the previously focused app |
| Multi-select | Cmd+Click toggle; Shift+Arrow or Shift+Click range; Cmd+A all; Enter to paste |
| Pin | Pinned items stay at top and skip auto-delete |
| Context menu | Right-click: Paste, Pin/Unpin, Delete (multi-select aware) |
| Window | Resizable; size remembered; position: near cursor, center, or corner |
| Auto-trim | Up to 50K entries (1K/5K/10K/50K); skips oversized images |
| Appearance | Dark/light follows macOS |
| Privacy | Ignores whitespace-only entries; redacts password/card previews; pause monitoring from tray |
| Transforms | Option-click or right-click: plain text, trim, case, JSON, etc. |
| Copy only | Right-click Copy puts content on clipboard without auto-paste |
| Color swatches | Hex/rgb/hsl entries show a color chip in the list |
| Launch at login | Settings toggle registers a macOS Login Item |
| Image thumbnails | Small thumbs in list; full image loaded on paste |
Docs
| Doc | |
|---|---|
| docs/GUIDE.md | Install and usage guide — start here to use the app |
| docs/README.md | Docs index |
| docs/TESTING.md | Automated tests |
| docs/LAUNCH-AT-LOGIN.md | Start when Mac logs in |
| docs/PRODUCT.md | Professional / shipping checklist |
| docs/SHARING.md | Where the app lives + how to share it with others |
| CHANGELOG.md | Version history |
| ROADMAP.md | Next features + brainstorm |
- macOS 10.15+
- Rust 1.77+ — install via rustup
- Node.js 18+ and npm
- Xcode Command Line Tools —
xcode-select --install - Accessibility permission — required for auto-paste (System Settings → Privacy & Security → Accessibility → add maCopy)
Quick start
git clone gitea@git.levkin.ca: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 Ctrl+` (configurable in Settings) |
| Paste an entry | Click it, or press Enter |
| Paste plain (no formatting) | ⌥/Alt+click, or right-click → Paste plain |
| Copy without pasting | Right-click → Copy |
| Transforms | Right-click → Transform (trim, case, JSON, …) |
| 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… |
| Change hotkey | Settings → Global hotkey → press new shortcut |
| Launch at login | Settings → Launch at login (see docs/LAUNCH-AT-LOGIN.md) |
| 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, ~72 tests) |
npm run test:rust |
Run Rust backend tests (~67 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 monitoring
A background Rust thread watches the macOS pasteboard changeCount and only reads the clipboard when it changes. Text, images (full PNG + thumbnail), and file paths are captured. List IPC returns truncated / redacted previews and image thumbnails — not multi‑MB blobs.
Paste and return
When you select an entry, maCopy writes it to the system clipboard (text or image), hides its window, waits briefly for macOS to refocus the previous app, then simulates Cmd+V via AppleScript. This requires Accessibility permission. Optional transforms (plain, trim, JSON, …) run before the write.
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. User queries are tokenized before MATCH so path punctuation (/…, .) does not trip FTS5 syntax. Opened with journal_mode=WAL + synchronous=NORMAL so the UI can read while the background poller writes, without blocking on full fsyncs.
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 (with a short delay so clicks register). 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
See docs/TESTING.md for the full map.
npm test # ~72 frontend tests
npm run test:rust # ~67 Rust tests
npm run check # lint + all tests (~139)
Building for Release
npm run tauri build
The built .app bundle will be in src-tauri/target/release/bundle/macos/.
Copy it to /Applications, then enable Launch at login in Settings — details in docs/LAUNCH-AT-LOGIN.md.
Roadmap & product readiness
- Feature backlog: ROADMAP.md
- Professional shipping checklist: docs/PRODUCT.md
License
MIT