# maCopy A fast, native macOS clipboard manager that lives in your menu bar. Built with Tauri 2, React, TypeScript, and SQLite. ![macOS](https://img.shields.io/badge/macOS-10.15+-black?logo=apple) ![Tauri](https://img.shields.io/badge/Tauri-2-blue?logo=tauri) ![License](https://img.shields.io/badge/license-MIT-green) ## Features - **Menu bar app** — no dock icon, stays out of your way - **Global hotkey** — `` Ctrl+` `` opens the window (doesn’t steal macOS Cmd+` window-cycle); configurable in Settings - **Clipboard monitoring** — watches the macOS pasteboard changeCount (no busy hashing while idle); captures text, images, and file paths - **Fast picker** — list API returns truncated previews only (image blobs stay in SQLite until paste); refreshes on copy / window focus instead of reloading everything every second - **Full-text search** — instant filtering via SQLite FTS5 - **Quick paste** — `Cmd+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-select** — `Cmd+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); oversized images are skipped - **Dark/light mode** — follows macOS system appearance - **Privacy** — whitespace-only entries are ignored; password/card/banking previews are redacted (full value still pastes); pause monitoring from the tray - **Paste plain / transforms** — ⌥-click or right-click for plain text and transforms (trim, case, JSON, …) - **Copy only** — right-click → Copy puts content on the clipboard without auto-pasting or hiding the window - **Color swatches** — hex/rgb/hsl entries (e.g. `#3366ff`) show a small color chip in the list - **Launch at login** — Settings toggle registers a macOS Login Item (use the built `.app`) - **Image thumbnails** — list shows small thumbs; full images stay in the DB until paste ## Docs | Doc | | |---|---| | [docs/GUIDE.md](docs/GUIDE.md) | **Install + usage guide** (the living doc — start here if you just want to use the app) | | [docs/README.md](docs/README.md) | Docs index | | [docs/TESTING.md](docs/TESTING.md) | Automated tests | | [docs/LAUNCH-AT-LOGIN.md](docs/LAUNCH-AT-LOGIN.md) | Start when Mac logs in | | [docs/PRODUCT.md](docs/PRODUCT.md) | Professional / shipping checklist | | [docs/SHARING.md](docs/SHARING.md) | Where the app lives + how to share it with others | | [CHANGELOG.md](CHANGELOG.md) | Version history | | [ROADMAP.md](ROADMAP.md) | Next features + brainstorm | - **macOS** 10.15+ - **Rust** 1.77+ — [install via rustup](https://rustup.rs) - **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 ```bash 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](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, ~60 tests) | | `npm run test:rust` | Run Rust backend tests (~52 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](https://v2.tauri.app) | | Frontend | React 18, TypeScript, Tailwind CSS v4 | | Backend | Rust, rusqlite (bundled SQLite) | | Clipboard | [arboard](https://crates.io/crates/arboard) for system clipboard access | | Mouse position | [core-graphics](https://crates.io/crates/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 & 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. 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](docs/TESTING.md)** for the full map. ```bash npm test # ~60 frontend tests npm run test:rust # ~52 Rust tests npm run check # lint + all tests (~112) ``` ## Building for Release ```bash 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](docs/LAUNCH-AT-LOGIN.md). ## Roadmap & product readiness - Feature backlog: [ROADMAP.md](ROADMAP.md) - Professional shipping checklist: [docs/PRODUCT.md](docs/PRODUCT.md) ## License MIT