@specfork/clipboard-manager
Local desktop clipboard history for text, HTML, images and files, with limits, search, pins, collections, paste, global hotkey, ignore rules for secrets, editing, optional encryption and retention.
Clipboard manager
One person keeps a history of what they copy on their own computer. Every copy of text, formatted text, an image or a list of files becomes an item; items can be searched, edited, pinned, sorted into collections and pasted back into the app the person was using. Content that the source app or the person marks as secret is never stored, history can be capped, aged out, cleared or encrypted, and nothing leaves the device. Behaviour derived from CopyQ's open-source code and documentation (GPL-3.0-or-later), written fresh and stack-free. Not affiliated with or endorsed by Lukas Holecek (hluk) or the CopyQ project. The name is used only to credit the source.
| Area | Prefix | Covers |
|---|---|---|
| capture.md | CAP | Clipboard monitoring, stored formats, size limits, duplicates, pause |
| sensitive.md | SEN | Concealed marker, ignore rules, blocked texts, masking, screen-capture protection, logs |
| selection.md | SYN | Primary selection: storing, merging, synchronising with the clipboard |
| items.md | ITM | Item fields, previews, create, edit, notes, tags, pin, delete, move, sort |
| collections.md | COL | History and other collections, favourites, moving items between them |
| search.md | SRC | Filtering items, matching rules, regex, scope |
| paste.md | PST | Activating items, pasting into the previous window, plain text, tray menu, popup |
| retention.md | RET | Count limits, age limits, clearing, history kept in memory only, delete all |
| storage.md | STO | Local files, saving, encryption at rest, lock, export and import |
| app.md | APP | Background instance, tray, hotkeys, window, shortcuts, command interface, settings |
Single user, no account, no server, no network. Runs on Windows, macOS and Linux (X11 and Wayland);
platform differences are stated as behaviour. Shortcuts are defaults and configurable; Ctrl means
Cmd on macOS. Formats are named by MIME type; instants are UTC ISO 8601; setting and field names
are snake_case.
This spec is licensed under Creative Commons Attribution 4.0 International (CC BY 4.0): https://creativecommons.org/licenses/by/4.0/legalcode
It describes behaviour of CopyQ (https://github.com/hluk/CopyQ, GPL-3.0-or-later)
in new words; no code or text was copied.
See README.md front matter derived_from.
App
Status: Draft · Prefix: APP
Purpose
The background instance, tray, global hotkeys, main window, keyboard shortcuts, the minimal command interface and settings.
Requirements
| ID | Requirement | Verified by |
|---|---|---|
| APP-1 | One background instance per user session. Starting the app again shows the first instance's window and exits with code 0. | start twice → one process, window shown |
| APP-2 | Tray icon: primary click toggles the main window, or opens the tray menu when tray_click is menu; the menu has recent items (PST-9), pause/resume (CAP-7), show/hide, clear history, settings and quit. Without a tray area the app relies on hotkeys and says so once. |
click tray → window toggles |
| APP-3 | Global hotkeys, configurable: show popup (PST-8) Ctrl+Alt+V (macOS Cmd+Option+V); show/hide main window, pause/resume and paste_item_n unset. A hotkey held by another app or the OS is marked not registered; the rest still work. |
another app holds Ctrl+Alt+V → marked |
| APP-4 | Wayland: hotkeys go through the desktop's global-shortcut service when present (the desktop may ask to confirm); otherwise settings show the toggle and menu commands (APP-7) to bind in the desktop's own settings. |
Wayland without service → commands shown |
| APP-5 | Main window: the current collection's items, collection bar, filter. It opens on the display with the pointer, keeps size and position where the platform allows, hides on Esc, and hides 500 ms after losing focus when close_on_unfocus is on (default). |
click elsewhere → hidden after 0.5 s |
| APP-6 | Window shortcuts, configurable, defaults: Enter activate, Shift+Enter plain text, F2 edit, Shift+F2 note, Ctrl+E external editor, F4 formats, Delete remove, Ctrl+N new item, Ctrl+T new collection, Ctrl+W delete collection, Ctrl+Shift+P pin, Ctrl+Up/Down move, Ctrl+, settings, Ctrl+Q quit. Assigning a key already in use is refused with the action that holds it. |
assign F2 to pin → refused, names edit |
| APP-7 | Command interface: a CLI talks to the running instance over a per-user local channel, never a network port: show, hide, toggle, menu, add <text>…, read [row] [--format <MIME>], select <row> (activate without paste), remove <row>…, count, pause [minutes], resume, clear, each optionally after --collection <name>. Rows are 0-based from the top. |
add a b, count → 2 |
| APP-8 | Command exit codes: 0 done, 1 no running instance, 2 usage error or no such row/collection, 3 locked (STO-6). add goes through SEN-3 rules like a copy. Requests from another OS user are refused. |
locked, read 0 → exit 3 |
| APP-9 | start_at_login (default off) is offered once on first run and adds or removes a per-user login item without administrator rights. |
accept on first run → starts after next login |
| APP-10 | Settings live in one per-user file; unknown keys are ignored and invalid values fall back to their default, each with a warning, and the rest still load. Changes apply without restart. | max_items=-4 → 200 used, warning |
| APP-11 | ui_language (auto = OS language, falls back to English); theme: system|light|dark (system); confirm_quit (default on) asks before quitting because capture stops. |
quit → confirmation shown |
Out of scope
- Scripting language, plugins, automatic commands triggered by clipboard content, menus of custom actions; only APP-7 commands exist.
- Self-update and installers.
Decisions
- A minimal command set (APP-7) replaces the source's full scripting engine, which is out of scope.
- The show hotkey defaults to
Ctrl+Alt+V, avoidingCtrl+Shift+V, which many apps use to paste as plain text.
Capture
Status: Draft · Prefix: CAP
Purpose
Turning changes of the system clipboard into history items: what is stored, what is skipped, and how often.
Requirements
| ID | Requirement | Verified by |
|---|---|---|
| CAP-1 | With store_clipboard on (default), every clipboard change made by another app becomes an item at the top of the history collection (COL-1) within 1 s, after the checks of SEN and CAP-3..6. |
copy "abc" in an editor → item "abc" on top within 1 s |
| CAP-2 | Stored formats: text/plain (as UTF-8), text/html, text/uri-list (files), image/png (platform bitmaps converted to PNG), image/svg+xml, image/gif, plus any MIME type listed in extra_formats (empty). Other formats are dropped; a change with no stored format makes no item. |
copy only a private app format → no item |
| CAP-3 | Text that is empty or only whitespace, with no other stored format, makes no item while ignore_blank is on (default). |
copy " " → no item |
| CAP-4 | format_size_limits: ordered {pattern (MIME glob), max_bytes}, first match wins; default one rule * = 100 MiB; max_bytes 0 never stores that format. An over-limit format is dropped and the rest kept; nothing left → no item. |
rule text/html 0, copy from a web page → item has text only |
| CAP-5 | Duplicate: when every stored format equals an existing unpinned item of the history collection, that item moves to the top and gets a new last_used_at; no item is added. A duplicate of a pinned item adds nothing and moves nothing. |
copy "a", "b", "a" → 2 items, "a" on top |
| CAP-6 | Changes the app makes itself (activating an item, PST-1) create no item; the activated item moves to the top when move_activated_to_top is on (default). |
activate 3rd item → it is first, count unchanged |
| CAP-7 | Monitoring can be paused and resumed from the tray, a hotkey and the command interface, for 5, 15 or 60 minutes or until resumed. While paused nothing is captured and the tray icon shows the paused state; a timed pause resumes by itself. | pause 5 min, copy → no item; after 5 min, copy → item |
| CAP-8 | Change detection works on every platform within 1 s. On Wayland it needs the compositor's clipboard-access protocol or a desktop extension; without them the app says monitoring is unavailable and stays usable for manual items and paste. | Wayland without protocol → notice, Ctrl+N still works |
| CAP-9 | notify_on_copy (default off): a notification on each new item with its first notification_lines lines (0..20, default 3) of text, or image W×H or the file count; never for content handled by SEN. |
on, copy 2 files → notification "2 files" |
| CAP-10 | Each captured item records source_app and source_window_title only while record_source is on (default off); otherwise both are used for SEN-3 and then discarded. |
default, copy → item has no source fields |
Out of scope
- Capturing drag-and-drop data or the macOS find pasteboard.
- Converting between formats beyond bitmap → PNG.
Decisions
- Recording the source window is off by default (CAP-10): window titles often reveal document names and are not needed to use history.
- The size limit default of 100 MiB matches the source; the per-format list replaces its environment variable with a setting.
Collections
Status: Draft · Prefix: COL
Purpose
Grouping items into named collections: the history that receives copies, favourites, and any others the person makes.
Requirements
| ID | Requirement | Verified by |
|---|---|---|
| COL-1 | Collection: {id, name (1..100 chars, trimmed, unique case-insensitive), position, kind: history|regular, max_items (optional, overrides [RET-1](#RET-1))}. Exactly one collection is history (default name "Clipboard"); captures go there. |
fresh install → one collection "Clipboard" |
| COL-2 | Another collection can be made the history; the previous one becomes regular and keeps its items. | make "Work" history → next copy lands in "Work" |
| COL-3 | Ctrl+T creates a collection; rename from its menu; Ctrl+W deletes the current one after a confirmation naming its item count. The history collection cannot be deleted. |
Ctrl+W on history → refused |
| COL-4 | With collection_tree on (default off), / in names groups collections in a tree: "Work/Snippets" shows under "Work". |
create "A/B" → tree node A with child B |
| COL-5 | The collection bar is hidden while only one collection exists; with show_counts on (default off) each collection shows its item count. |
create second collection → bar appears |
| COL-6 | Items move or copy between collections by drag (Ctrl held = copy) or the item menu; formats, note, tags and sensitive travel along, pinned becomes false, and the target's limit applies (RET-1). |
move pinned item → arrives unpinned |
| COL-7 | Favourites: "add to favourites" copies the item into the collection named by favourites_collection (default "Favourites"), created on first use; adding an equal item again does nothing. |
add twice → one copy in Favourites |
| COL-8 | Navigation: Ctrl+Tab / Ctrl+Shift+Tab next and previous, Ctrl+1..Ctrl+9 the nth collection, Ctrl+0 the last. |
Ctrl+2 → second collection shown |
Out of scope
- Collections backed by a folder on disk or a cloud folder.
- Automatic sorting of new items into collections by rules or scripts.
Decisions
- Favourites are a plain collection (COL-7), so no separate data model is needed.
Items
Status: Draft · Prefix: ITM
Purpose
What an item holds, how it is shown, and how the person creates, edits, annotates, pins, deletes and orders items.
Requirements
| ID | Requirement | Verified by |
|---|---|---|
| ITM-1 | Item: {id (UUID), collection_id, position, formats: {MIME type: bytes}, created_at, last_used_at, origin: clipboard|selection|manual|import|command, pinned (false), sensitive (false), note (≤ 10000 chars), tags (≤ 20, each 1..50 chars, unique case-insensitive)}. |
add 21st tag → refused |
| ITM-2 | Preview: text shows its first preview_lines lines (1..50, default 5) with a marker when cut; HTML shows formatted when rich_text is on (default), else its text; images show a thumbnail within preview_image_max (default 320×240 px) keeping aspect; file lists show names and count; other formats show MIME types and sizes. Note and tags show under the preview. |
12-line text → 5 lines + "more" marker |
| ITM-3 | Ctrl+N creates an empty text item at the top of the current collection with origin: manual and opens the editor; saving empty text discards it. |
Ctrl+N, save empty → no item |
| ITM-4 | F2 edits text in place; saving replaces text/plain and drops text/html if the text changed; Esc discards the edit. Image-only items cannot be edited in place. |
edit an HTML item's text → item has text only |
| ITM-5 | Ctrl+E opens the item in external_editor (text) or image_editor (images) through a temporary file readable only by the user; each save of that file updates the item; the file is deleted when the editor exits. |
change text in editor, save → item updated |
| ITM-6 | Shift+F2 edits the note; tags are added or removed from the item menu. Notes and tags are searchable (SRC-1). |
add tag "work" → shown under item |
| ITM-7 | Pin (Ctrl+Shift+P toggles): a pinned item keeps its row when items are added or removed above or below it, is never removed by limits (RET-2) and cannot be deleted until unpinned; the person can still move it. |
pin row 3, copy 2 new items → still row 3 |
| ITM-8 | Delete removes the selected unpinned items at once; an "undo" offer stays for 10 s, after which the data is purged per RET-7. Selected pinned items are skipped with a notice. |
delete 2, undo within 10 s → both back |
| ITM-9 | Order: drag, or Ctrl+Up/Ctrl+Down/Ctrl+Home/Ctrl+End moves selected items; Ctrl+Shift+S sorts them by text (case-insensitive, locale-aware), Ctrl+Shift+R reverses them. Multi-select with Shift/Ctrl-click and Ctrl+A. |
select 3, Ctrl+Shift+R → order reversed |
| ITM-10 | F4 shows each format of the item with its MIME type and size and lets the person view it as text or image, or remove one format; removing the last format deletes the item. |
remove text/html → item keeps text |
| ITM-11 | sensitive can be set and cleared by the person on any item (SEN-6). |
flag item → preview masked |
Out of scope
- Rendering arbitrary formats (rich documents, audio, video); they show as format lists.
- Vim-style editing and syntax highlighting.
Decisions
- Editing text drops the HTML version (ITM-4) so the two never disagree when pasted.
- Pin uses
Ctrl+Shift+P; the source has no default key for it.
Paste
Status: Draft · Prefix: PST
Purpose
Getting an item back into the app the person is working in: activation, pasting, plain text, several items, the tray menu and the popup.
Requirements
| ID | Requirement | Verified by |
|---|---|---|
| PST-1 | Activate (Enter, double-click, or single click when activate_on_single_click is on, default off) puts every stored format of the item on the clipboard, updates its last_used_at and moves it per CAP-6. |
activate HTML item, paste in a rich editor → formatted |
| PST-2 | activate_pastes (default on): after activation the window hides, focus returns to the window active before the app opened, and the paste keystroke is sent. activate_pastes off: copy only; activate_hides (default on) controls hiding. |
text editor focused, popup, Enter → text inserted |
| PST-3 | Paste keystroke: Ctrl+V (macOS Cmd+V) by default; paste_key_rules: ordered {app or window_title regex, keys} override it, e.g. Ctrl+Shift+V or Shift+Insert for terminals. |
rule terminal → Ctrl+Shift+V sent there |
| PST-4 | If the previous window no longer exists or cannot take focus, the item is only copied and a notice says so. | close target app, activate → copied, notice |
| PST-5 | Wayland: when sending key events to other apps is not permitted, the app copies only and says "copied, paste with Ctrl+V", unless the desktop offers a consented input service. macOS: sending the paste needs the Accessibility permission; without it, copy only and explain how to grant it. | macOS without permission → copied, hint shown |
| PST-6 | Shift+Enter activates as plain text: only text/plain is put on the clipboard. |
Shift+Enter on HTML item → paste is unformatted |
| PST-7 | Several selected items activate as one: their texts joined by item_separator (default newline) in list order, text only. Selected items without text are skipped. |
select "a","b", Enter → "a\nb" |
| PST-8 | Popup: the show hotkey (APP-3) opens a compact list of the history collection at the pointer (Wayland: centre of the active display), focused on the filter; Enter, a click or digits 1..9 (first nine rows) activate. |
hotkey, press 2 → second item pasted |
| PST-9 | Tray menu lists the first tray_items (0..99, default 5) items of the history collection with one-line previews (masked when sensitive); choosing one activates it, pasting only when tray_pastes is on (default on). |
5 items in tray menu, choose 3rd → pasted |
| PST-10 | Optional hotkeys paste_item_1..paste_item_9 (unset by default) activate and paste that row of the history collection without opening any window. |
bind paste_item_2, press → 2nd item pasted |
Out of scope
- Typing text key by key into apps that block paste.
- Templates or placeholders expanded on paste.
Decisions
- Pasting into the previous window is the default (PST-2), as in the source; platforms that forbid it fall back to copy with a clear hint (PST-5) instead of failing silently.
Retention
Status: Draft · Prefix: RET
Purpose
How much history is kept, for how long, and how the person removes it.
Requirements
| ID | Requirement | Verified by |
|---|---|---|
| RET-1 | max_items 1..100000 (default 200) per collection, overridable per collection (COL-1). Adding an item to a full collection removes the bottom-most unpinned item. |
limit 3, add 4th → oldest unpinned gone |
| RET-2 | Pinned items are never removed by limits. When every item is pinned and the collection is full, the new item is not added and a notice says why, once per hour at most. | 3 pinned, limit 3, copy → no item, notice |
| RET-3 | Lowering max_items below the current count asks for confirmation naming how many items will be removed, then trims at once. |
200 items, set 50 → "150 items will be removed" |
| RET-4 | max_age_days 0..3650 (default 0 = off): unpinned items of the history collection whose last_used_at is older are removed at start and every hour. Other collections are never aged. |
30 days, item last used 31 days ago → removed |
| RET-5 | Clear history: removes all unpinned items of the history collection and the search history after confirmation; a checkbox in the same confirmation also removes pinned ones. | clear → only pinned remain |
| RET-6 | clear_on_exit (default off): unpinned history items are removed on quit, and at next start if the app did not quit cleanly. |
on, kill app, start → history empty |
| RET-7 | Removal is real: a removed item's data, including separately stored large formats, is gone from storage at the next save (STO-3); there is no hidden trash. | remove image item, inspect data folder → its file gone |
| RET-8 | persist_history (default on): when off, the history collection is held in memory only and never written to disk; other collections are still saved. |
off, copy, quit, start → history empty, no history file |
| RET-9 | Delete all data: after typing DELETE, every collection, item, ignore rule, blocked hash and search query is removed and the history collection recreated empty; settings stay. |
delete all → one empty "Clipboard" |
Out of scope
- Size-based quotas for a whole collection (bytes); limits are per format (CAP-4) and per item count.
Decisions
- Default limit 200 items matches the source; age limits and memory-only history are designed here.
@specfork/privacyis not required: it depends on@specfork/auth, which this account-free app does not use; delete-all (RET-9) and export (STO-10) follow the same intent locally.
Search
Status: Draft · Prefix: SRC
Purpose
Finding items quickly by typing.
Requirements
| ID | Requirement | Verified by |
|---|---|---|
| SRC-1 | Typing while the item list has focus starts a filter. It matches text/plain, the visible text of text/html (not tags or attributes), file names in text/uri-list, notes and tags. Images and other formats match only through notes and tags. |
type "invoice" → item tagged "invoice" shown |
| SRC-2 | Default matching: case-insensitive, and every space-separated word must occur somewhere in the item, in any order. | "foo bar" matches "bar then foo" |
| SRC-3 | search_regex (default off): the query is one regular expression, case-insensitive unless search_case_sensitive is on; an invalid expression shows its error and no results. |
regex ^a\d+$ → only "a12" style items |
| SRC-4 | Results keep collection order; the first result is selected and Enter activates it (PST-1). |
filter, Enter → first match on clipboard |
| SRC-5 | number_search (default off): typing digits selects that row (1-based) instead of filtering. |
on, type 3 → row 3 selected |
| SRC-6 | Esc clears the filter; Esc with no filter hides the window. |
Esc, Esc → filter cleared, window hidden |
| SRC-7 | Scope: current collection by default; Ctrl+Shift+F toggles searching all collections, where each result shows its collection name. |
scope all → match in "Work" shown with label |
| SRC-8 | Items flagged sensitive match only through note and tags (SEN-6). |
sensitive item text "pin 1234", search 1234 → not shown |
| SRC-9 | save_search_history (default off): keeps the last 20 queries locally, encrypted when STO-4 is on, and cleared by RET-5. |
off → no query stored after restart |
| SRC-10 | With 10,000 text items of up to 10 KB each, results update within 300 ms of the last keystroke. | timing test → ≤ 300 ms |
Out of scope
- Fuzzy or semantic search, OCR of images.
Decisions
- Searching all collections at once (SRC-7) and the timing target (SRC-10) are designed here; the source filters one collection at a time.
Selection
Status: Draft · Prefix: SYN
Purpose
Where the OS has a primary selection next to the clipboard, storing selected text and keeping the two in step.
Requirements
| ID | Requirement | Verified by |
|---|---|---|
| SYN-1 | These rules apply only where a primary selection exists: Linux X11, and Wayland compositors with the primary-selection protocol. Elsewhere the settings are hidden. | Windows → no selection settings |
| SYN-2 | store_selection (default off): selection changes also become items, with origin: selection, through the same checks as CAP and SEN. |
on, select "foo" → item "foo" |
| SYN-3 | Growing selection: when the new selection text starts or ends with the text of the top item and that item came from the selection, the top item is updated instead of adding one. | select "he", extend to "hello" → one item "hello" |
| SYN-4 | When the clipboard receives the same text as the top selection item, the clipboard's formats are merged into that item instead of adding one. | select "x", then copy "x" → one item with html too |
| SYN-5 | sync_clipboard_to_selection and sync_selection_to_clipboard (both default off) copy the text of one to the other after it changes. Only text formats are copied. |
clipboard → selection on, copy "a" → middle-click pastes "a" |
| SYN-6 | A sync waits until no Shift, Ctrl or Alt key is held (giving up after 5 s) and at least 50 ms after the change, and is skipped when the source changed again meanwhile or the target already holds the same text. | hold Shift while selecting → no sync until released |
| SYN-7 | Content ignored or concealed by SEN is never synchronised. | concealed clipboard → selection unchanged |
| SYN-8 | On Wayland without the needed protocols the settings show as unavailable with the reason. | protocol missing → options greyed with reason |
Out of scope
- The macOS find pasteboard and other secondary buffers.
Decisions
- Sync is off in both directions by default; each one silently overwrites data, so the person turns it on knowingly.
Sensitive data
Status: Draft · Prefix: SEN
Purpose
Clipboard contents include passwords, tokens and private text. These rules decide what is never stored, how stored items can be hidden, and what the app never reveals.
Requirements
| ID | Requirement | Verified by |
|---|---|---|
| SEN-1 | Concealed content is never stored, shown, notified, synchronised or logged: on Linux the password-manager hint format set to secret; on macOS the concealed pasteboard type; on Windows any of the formats that exclude content from clipboard history or monitoring, or set history or cloud upload to 0. |
password manager copies a password → no item, no notification |
| SEN-2 | store_concealed_from: list of app identifiers whose concealed content may be stored (empty). Adding one needs encryption at rest on (STO-4) and a confirmation; items stored this way are flagged sensitive (SEN-6). There is no global switch. |
encryption off, add an app → refused |
| SEN-3 | Ignore rules: ordered {name, enabled, app (exact identifier), window_title (regex), text (regex), format (MIME)}; a change matching every set field of an enabled rule is ignored. A rule with no field set is invalid. |
rule window_title (?i)vault, copy from "My Vault" → no item |
| SEN-4 | Default rule password_windows, enabled: window title matches (?i)passw(or)?d|passwort|mot de passe|contraseña. |
copy from a window titled "Password Safe" → no item |
| SEN-5 | Blocked texts: "never store this text" on an item deletes it and keeps only a salted SHA-256 of its text; a later copy with the same text is ignored. The text itself is not kept anywhere. Blocked hashes can be listed (by date only) and removed. | block "hunter2", copy it again → no item |
| SEN-6 | Items flagged sensitive (by SEN-2 or by the person) show a masked preview (dots plus character count), are matched by search only through note and tags, never appear in notifications, and show masked in the tray menu. |
flag an item → list shows "•••• (12 chars)" |
| SEN-7 | After a sensitive item is activated, the clipboard is emptied clear_sensitive_after_s seconds later (0..3600, default 30; 0 = never), unless the clipboard changed in between. |
activate sensitive item, wait 30 s → clipboard empty |
| SEN-8 | Ignored and concealed content leaves no trace: no item, no notification, no log line with its content; the other app's clipboard is left as it is. | copy concealed text, paste elsewhere → still pastes |
| SEN-9 | hide_from_screen_capture (default on): the app's windows and menus are excluded from screenshots and screen sharing where the OS supports it (Windows, macOS); elsewhere settings say it has no effect. show_clipboard_in_title (default off) controls whether the current clipboard appears in the window title and tray tooltip. |
Windows, share screen → app window blank to viewers |
| SEN-10 | Logs contain no item content, window titles or regex texts: only counts, rule names and error reasons. | ignored copy by rule "bank" → log says rule "bank" matched, no content |
Out of scope
- Detecting secrets by content heuristics (entropy, card numbers).
- Per-item encryption separate from STO-4.
Decisions
- Concealed content can be stored only per app and only with encryption on (SEN-2); the source allows a global override through scripting.
- Clearing the clipboard after a sensitive paste (SEN-7) is built in; the source offers it as an optional shared script.
Storage
Status: Draft · Prefix: STO
Purpose
Where items live on the device, how they are saved safely, optional encryption at rest with locking, and moving data in and out.
Requirements
| ID | Requirement | Verified by |
|---|---|---|
| STO-1 | All data stays in the per-user OS data folder, created readable and writable only by that user; the app makes no network requests at all. | inspect permissions → owner-only; traffic monitor → zero requests |
| STO-2 | Format data larger than inline_max_bytes (default 4096) is stored in its own file referenced by the item and deleted with it. |
copy 1 MB image → separate file exists |
| STO-3 | Changes are saved within 2 s; every write goes to a temporary file that replaces the old one only when complete, so a crash loses at most the last 2 s and never corrupts a collection. | kill app mid-save → previous state loads |
| STO-4 | encrypt (default off): all collection files, separate format files, search history and blocked hashes are encrypted with authenticated encryption (AES-256-GCM or XChaCha20-Poly1305) under a random 256-bit data key; the data key is wrapped by a key derived from the person's password (Argon2id ≥ 64 MiB, ≥ 3 passes). Settings and ignore rules stay readable. |
encrypt on, search data folder for a copied word → not found |
| STO-5 | Turning encryption on asks for a password (≥ 8 chars, typed twice), says a forgotten password makes the data unrecoverable, rewrites all data encrypted and deletes the plain files. Turning it off needs the password and rewrites plain. | turn on → old plain files gone |
| STO-6 | Locked state (at start with encryption on, or after STO-7): items are not shown, monitoring is paused and nothing is captured, the tray shows locked; entering the password unlocks. | locked, copy → no item after unlock |
| STO-7 | lock_after_min 0..1440 (default 0 = never): with encryption on, after that many minutes without interaction with the app, the data key is dropped from memory and the app locks. |
5 min idle → password needed |
| STO-8 | use_os_keystore (default off): the data key is kept in the OS credential store so start needs no password; STO-7 still applies. |
on, restart → unlocked without prompt |
| STO-9 | Changing the password needs the old one and re-wraps only the data key, taking under 1 s at any history size. Five wrong passwords in a row add a delay of 30 s, doubling each further failure up to 1 h; data is never deleted for failures. | 6th wrong try → wait 60 s |
| STO-10 | Export: chosen collections or all into one file {format: "clipboard-manager-export", version: 1, exported_at, collections: [{name, kind, items: [{formats (base64), created_at, last_used_at, pinned, note, tags, sensitive}]}]}; sensitive items are left out unless chosen; an optional export password encrypts the whole file (STO-4 scheme). |
export 3 items → file has 3 items |
| STO-11 | Import of such a file adds its items as new items (new ids) into collections of the same name, created when missing; nothing existing is overwritten; limits (RET-1) apply; a wrong password or another format/newer version → unsupported_export and nothing changes. |
import twice → items twice, originals untouched |
Out of scope
- Sync between devices, cloud backup, sharing collections.
Decisions
- Save within 2 s (STO-3); the source batches saves for minutes, which loses recent history on a crash.
- The OS key store is off by default (STO-8), so encrypted data needs the password after restart; the source turns it on by default.
- Locking pauses capture (STO-6) rather than queueing plaintext in memory.
From Specfork/clipboard-manager at 3c213d0, licence CC-BY-4.0.