- 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>
184 lines
7.0 KiB
Markdown
184 lines
7.0 KiB
Markdown
# maCopy
|
|
|
|
A fast, native macOS clipboard manager that lives in your menu bar. Built with Tauri 2, React, TypeScript, and SQLite.
|
|
|
|

|
|

|
|

|
|
|
|
## Features
|
|
|
|
- **Menu bar app** — no dock icon, stays out of your way
|
|
- **Global hotkey** — `Cmd+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 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)
|
|
- **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](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@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](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 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)
|
|
|
|
```bash
|
|
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)
|
|
|
|
```bash
|
|
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
|
|
|
|
```bash
|
|
npm run test:all # 74 total tests
|
|
npm run check # lint + all tests
|
|
```
|
|
|
|
## Building for Release
|
|
|
|
```bash
|
|
npm run tauri build
|
|
```
|
|
|
|
The built `.app` bundle will be in `src-tauri/target/release/bundle/macos/`.
|
|
|
|
## License
|
|
|
|
MIT
|