- Copy only (right-click -> Copy): write to clipboard without auto-pasting or hiding the window - Color swatches: hex/rgb/hsl entries show a preview chip - SQLite WAL + synchronous=NORMAL so the UI can read while the clipboard poller writes - Memoize list rows (React.memo) so selection/focus changes don't re-render every row's preview - Add docs/GUIDE.md (living install + usage doc), docs/SHARING.md, docs/TESTING.md, docs/LAUNCH-AT-LOGIN.md, docs/PRODUCT.md, CHANGELOG.md, ROADMAP.md - Add homelab Gitea Actions CI (.gitea/workflows/ci.yml) + gitleaks allowlist - Bump version to 0.2.0
198 lines
9.1 KiB
Markdown
198 lines
9.1 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** — `` 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
|