Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 46 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ interpretation as separate, searchable layers.

This repository now contains the complete integrated product tree:

- `apps/macos/` — SwiftUI/AppKit clipboard and screenshot-note capture,
library, detail, lifecycle, and search client;
- `apps/macos/` — SwiftUI/AppKit Accessibility selection, clipboard, and
screenshot-note capture, library, detail, lifecycle, and search client;
- `apps/chrome-extension/` — build-free Manifest V3 web capture extension;
- `services/backend/` — loopback FastAPI API, SQLite/FTS5 storage, OpenAI
enrichment, embeddings, and hybrid retrieval;
Expand Down Expand Up @@ -106,11 +106,24 @@ prove Screen Recording authorization. The macOS README documents the one-time
reset and reauthorization needed when migrating from an earlier ad-hoc build.

Recall remains a normal Dock app and also keeps its existing menu-bar extra.
While the app is running, global screenshot and clipboard capture default to
`Option+Shift+Command+4` and `Option+Shift+Command+C`; they remain available
when the main window is closed. Configure, disable, or restore them in
**Settings > Global capture shortcuts**. The hotkeys use Carbon registration
and do not require Accessibility or Input Monitoring permission.
While the app is running, global selection, screenshot, and clipboard capture
default to `Option+Shift+Command+S`, `Option+Shift+Command+4`, and
`Option+Shift+Command+C`; they remain available when the main window is closed.
Capture Selection reads the focused external app's selected text only after the
shortcut, then opens the existing review window near that selection. It requires
macOS Accessibility access. Carbon hotkey registration itself, clipboard
capture, and screenshot capture do not require Accessibility or Input Monitoring
permission. Configure, disable, or restore all three actions in **Settings >
Global capture shortcuts**. An off-by-default **Clipboard Compatibility Mode**
can handle custom-drawn apps such as WeChat that can copy a selection but do not
expose selected text or even a focused control to Accessibility. It temporarily
sends Copy twice to the verified frontmost application, binds to the exact AX
control when one is available, requires matching results, and makes a best-effort
restoration. Recall blocks Secure Event Input and known secure/protected controls,
but custom-drawn apps may omit per-control safety attributes. macOS exposes neither clipboard-writer identity
nor an atomic restore, so rare races or a very delayed Copy can still change the
clipboard; history tools or Universal Clipboard may also record the transient
copies.

## Load the Chrome extension

Expand Down Expand Up @@ -213,6 +226,32 @@ a permission error. The macOS suite passes 70/70. B-014 is closed: with Recall's
main window closed and another app focused, the physical screenshot shortcut
completed a non-empty region, and the clipboard shortcut opened Capture after
text was copied.

D-034 adds user-triggered native Accessibility selection capture without
changing the backend contract or saving surrounding context. The native draft
is labeled as a selection but submits through the existing text/clipboard source
contract; selection bounds are used only to position Quick Capture and are never
persisted. The host suite passes 108/108 tests, including permission and secure-
field fail-closed behavior, exact Unicode preservation, old shortcut migration,
cancellation, oversized-source rejection, and multi-screen placement geometry.
The user accepted the primary native path and the D-035 WeChat compatibility
path on the stably signed app on 2026-07-21.

D-035 adds the opt-in transactional clipboard fallback requested after initial
real-device selection testing. It never runs for missing permission, Recall
itself, known secure/protected content, oversized text, or cancellation before
the transaction begins. The AX failure produces a ticket for the exact frontmost
application and, when available, its focused control. That scope is revalidated
immediately before each of two Copy attempts, together with Secure Event Input
and any exposed security attributes. Recall accepts only
two consecutive, matching clipboard results and attempts restoration only while
the observed change count remains unchanged. This substantially narrows races
but cannot make restoration atomic or identify the writer. The in-memory backup
is never logged, persisted, or sent to the backend. The current host suite passes
149/149 tests. The user reported no issue in final WeChat testing and authorized
merge; rich text, image, Finder-file, and race cases remain release-regression
coverage rather than an open merge gate.

Final regression also passes 215 backend tests, 44/44 stress scenarios, and
68/68 Chrome-extension tests.

Expand Down
101 changes: 84 additions & 17 deletions apps/macos/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ loopback backend at `http://127.0.0.1:8765`; normal runs do not use an in-proces
database or mock API client.

The macOS client, hardened backend, and Chrome extension now live in the same
integration tree. The current D-032 target passes all 70 contract, networking,
Vision, global-shortcut, lifecycle, validation, idempotency, store, and
code-signing identity tests.
integration tree. The current D-034 branch passes all 108 Accessibility,
contract, networking, Vision, global-shortcut, lifecycle, validation,
idempotency, store, window-placement, and code-signing identity tests.

## Requirements

Expand Down Expand Up @@ -141,6 +141,53 @@ build at CDHash `143035…`, rebuilding with the same signer and
signer-based requirement. The rebuilt process launched `/usr/sbin/screencapture`,
showed the region overlay, and returned without a permission error after Escape.

### Accessibility permission for Capture Selection

Accessibility is independent of Screen Recording. Recall requests it only when
the user chooses **Request Access** in Settings or invokes Capture Selection
without authorization. Enable the exact Recall build under **System Settings >
Privacy & Security > Accessibility**, return to the source app, select the text
again, and retry the shortcut; the initial permission call cannot read the
selection retroactively. Clipboard and screenshot capture continue to work when
Accessibility is disabled. Use one stably signed Recall copy during acceptance
so the permission row, running process, and shortcut owner all refer to the same
application identity.

### Clipboard Compatibility Mode

Some applications can copy selected text but do not expose it through the
Accessibility selected-text attribute. **Settings > Selection access >
Clipboard Compatibility Mode** is an off-by-default fallback for those apps.
Permission failure, Recall itself, known secure/protected controls, oversized
input, and cancellation before the transaction never synthesize Copy.
The Settings permission row always shows current Accessibility status. When
access is granted it offers **Manage Access…**, because macOS owns revocation and
Recall cannot implement a real in-app on/off switch for that system permission.

When enabled, Recall retains a ticket for the exact frontmost application whose
selected-text lookup failed. It also binds the exact AX focused control when the
app exposes one with complete safety evidence. Custom-drawn apps such as WeChat
may expose neither a focused control nor per-control safety attributes; their
ticket remains application-scoped, while Secure Event Input and any attributes
that do become available are still checked before Copy.
It waits for the shortcut modifiers to be released, deep-copies bounded ordered
pasteboard items and types into memory, and revalidates that same control
or frontmost application immediately before each of two Copy attempts. It accepts text only when both
attempts produce consecutive change counts and matching complete clipboard
payloads. It then attempts restoration only while the last observed count is
unchanged. The AX and pasteboard transaction runs on its own serial actor rather
than `MainActor`, so a slow source app or lazy pasteboard provider does not block
Recall's UI. The backup is not logged, persisted, or sent to the backend.

Restoration cannot be guaranteed or made invisible. macOS exposes neither the
writer behind a change count nor an atomic compare-and-restore operation. The
two matching Copy results reject common competing-writer races, but a sufficiently
narrow race, a source app that processes Copy after Recall's timeout, a crash, or
a pasteboard write failure can still change the clipboard. Clipboard history
apps, Universal Clipboard, and other observers may record either temporary copy.
Keep the mode disabled if that tradeoff is not acceptable; the explicit
clipboard shortcut remains available.

## Build and test from the command line

Run from the repository root. The explicit Derived Data path keeps generated
Expand Down Expand Up @@ -204,14 +251,18 @@ Run the build and tests again after regeneration.
- Run as a normal Dock app with the existing `MenuBarExtra`. Closing the main
window does not quit Recall, so menu-bar and global capture remain available
while the app is running.
- Register native global screenshot and clipboard shortcuts with Carbon
`RegisterEventHotKey`, without Accessibility or Input Monitoring permission.
Defaults are `Option+Shift+Command+4` for screenshot capture and
`Option+Shift+Command+C` for clipboard capture.
- Configure either action in Settings with an A–Z or 0–9 key plus Command,
Option, Control, and/or Shift; each shortcut requires at least two modifiers,
and the two actions cannot use the same combination. Either action can be
disabled, and **Restore Defaults** restores both defaults.
- Register native global selection, screenshot, and clipboard shortcuts with
Carbon `RegisterEventHotKey`. Defaults are `Option+Shift+Command+S`,
`Option+Shift+Command+4`, and `Option+Shift+Command+C`, respectively. Carbon
registration itself, screenshot capture, and clipboard capture need no
Accessibility or Input Monitoring permission; reading another app's selected
text requires Accessibility access.
- Configure any action in Settings with an A–Z or 0–9 key plus Command, Option,
Control, and/or Shift; each shortcut requires at least two modifiers, and no
enabled actions can share a combination. Any action can be disabled, and
**Restore Defaults** restores all three defaults. Existing two-action settings
migrate without losing custom values; if the new selection shortcut cannot be
registered, the two established actions remain active.
- Apply shortcut changes transactionally. If any new registration fails, Recall
restores the previous working registrations and shows the failure in
Settings, the menu, and the menu-bar status icon.
Expand All @@ -225,6 +276,13 @@ Run the build and tests again after regeneration.
responses.
- Capture non-empty clipboard text, optional notes, and best-effort source-app
metadata through `POST /v1/captures`.
- After the explicit selection shortcut, ask the focused external app for its
Accessibility selected text and best-effort range bounds. Reject Recall itself,
secure/protected fields, empty selections, unresponsive or unsupported
controls, and selections over 12,000 characters without reading the clipboard
or silently truncating. Open the existing Quick Capture near valid bounds or
centered on the current screen when bounds are unavailable. Bounds never leave
memory; only reviewed text, source app, and optional note can be saved.
- Select a screenshot region, preview it, and explicitly extract visible text
as reviewed source content. GPT/cloud is the default; Apple Vision/on-device
uses the same UI and subsequent Capture pipeline without calling the backend
Expand Down Expand Up @@ -268,8 +326,11 @@ Run the build and tests again after regeneration.
selected-text/no-selection Captures have been verified end to end against the
macOS client. Keep the credential and machine-specific extension origin only
in the untracked root `.env`.
- Clipboard and screenshot source-application detection is best effort. The app
does not read active window titles or Accessibility selections.
- Clipboard, screenshot, and Accessibility source-application detection is best
effort. Selection capture reads only after its explicit shortcut; it does not
read active window titles, URLs, surrounding text, or every selection change.
Applications that do not expose `AXSelectedText` remain compatible through the
separate clipboard action.
- Global shortcuts work only while Recall is running. Launch at login is a
separate future opt-in. Stable Screen Recording authorization now survives a
rebuild. Real-device acceptance also passes: with Recall's main window closed
Expand All @@ -284,9 +345,11 @@ Run the build and tests again after regeneration.
- App sandboxing, notarization, and bundling the Python service are outside the
current P0 Build Week scope.

The D-032 command-line suite executes 70/70 contract, networking, production
Vision, global-shortcut, lifecycle, validation, retry, polling, store, and
signing-identity tests.
The original D-034 command-line suite executed 108/108 tests. With the D-035
clipboard transaction and privacy coverage, the current suite executes 149/149
Accessibility, pasteboard, contract, networking, production Vision,
global-shortcut, lifecycle, validation, retry, polling, store, window-placement,
and signing-identity tests.

## Manual test matrix

Expand All @@ -302,6 +365,10 @@ after rerunning them on the current integrated tree.
| Shortcut settings | Confirm the defaults, change screenshot capture to `Option+Shift+Command+5`, relaunch Recall, then choose **Restore Defaults**. Also try one modifier and a duplicate combination. | The valid change persists across restart and defaults restore correctly. Invalid or duplicate combinations are rejected without replacing the active shortcuts. |
| Registration failure | Choose a combination already owned by macOS or another app and apply it. | Recall restores the preceding active shortcuts and exposes the failure in Settings, the menu, and the menu-bar status icon. |
| Global clipboard | Close the main window without quitting Recall, focus another app with 32 known clipboard characters, and press `Option+Shift+Command+C` twice. | Quick Capture opens with the exact 32 characters. The second trigger preserves the existing draft and shows an explanatory notice. |
| Accessibility permission | With Recall absent or disabled in **Privacy & Security > Accessibility**, select text in another app and press `Option+Shift+Command+S`; then grant access and retry. | The first attempt reads and saves nothing, explains the permission, and offers System Settings/current-clipboard recovery. After authorization, Settings reports access enabled and a new explicit attempt can read the selection. |
| Global selection | Close the main window without quitting Recall, select known Chinese/emoji/multiline text in TextEdit, and press `Option+Shift+Command+S`. Repeat near each screen edge and with no selection. | Quick Capture shows the exact text and Unicode count, opens near the selection while staying inside the visible screen, focuses the optional note, and saves only after review. No-selection stays unsaved with an actionable fallback. |
| Accessibility compatibility | Repeat selection capture in Safari/Chrome, selectable PDF text in Preview, and an Electron or custom-drawn control with Clipboard Compatibility Mode off. Try a password field and a selection longer than 12,000 characters. | Supported apps produce exact text and best-effort positioning. Unsupported controls fail clearly without touching the clipboard. Secure, unknown-safety, and oversized text never becomes a draft or request. |
| Clipboard compatibility | Put known plain text, rich text, an image, and Finder files on the clipboard in turn. Enable Clipboard Compatibility Mode, select known text in WeChat, and press `Option+Shift+Command+S`. Also try no selection, a password field, rapid repeats, switching controls/apps, and manually changing the clipboard during capture. | Eligible WeChat text opens as a selection draft and discloses two temporary Copies. The prior clipboard is restored with all materialized representations in the normal path. Detected focus/payload/count races create no draft and do not attempt restoration; because macOS has no writer identity or atomic restore, verify the clipboard manually rather than claiming zero race risk. Clipboard-history tools may retain the transient selection. |
| Global screenshot | Close the main window without quitting Recall, focus another app, and press `Option+Shift+Command+4`; cancel once and complete a region once. | The selector starts without blocking Recall. Cancellation leaves no draft or temporary PNG; a completed region opens the existing screenshot draft and disclosure UI. |
| GPT screenshot note | Choose **Capture Screenshot Note**, select a text region, keep **GPT · Cloud**, and choose **Extract source text**. | A preview appears before upload, the UI states the cloud boundary, extracted text fills only the source field, your optional personal note stays separate, and only text is saved. |
| Local screenshot note | Repeat with **Apple Vision · On device**, disconnect the network after the backend is already running, and extract. | Text extraction succeeds on the Mac, the UI confirms local processing, and no `/v1/ocr` request is made. Saving still uses the localhost Capture API. |
Expand All @@ -315,7 +382,7 @@ after rerunning them on the current integrated tree.
| Search limits and failure | Try 512/513-character queries, a control character, a backend error, and a genuine search-route `404`. | Valid input reaches the backend; invalid input is blocked locally; only `404` enables the visible local fallback. |
| API-provided web record | POST `contracts/examples/capture-request.json`, refresh, and open the record. | URL, title, selection, note, and truncation state appear in separate sections. Context starts collapsed with a character count; Show/Hide works and long context clearly limits only its on-screen preview. |
| Real Chrome flow | Load the extension unpacked, configure its exact origin, save a web selection, and refresh Recall. | The Chrome-created card appears without a database edit; repeat once with the backend stopped to verify the popup error. |
| Menu-bar entry | Exercise **Open Recall**, **Capture Clipboard**, **Capture Screenshot Note**, **Search**, **Check Connection**, and **Quit Recall**. | Each item performs its intended action without unexpected duplicate windows. |
| Menu-bar entry | Exercise **Open Recall**, **Capture Selection**, **Capture Clipboard**, **Capture Screenshot Note**, **Search**, **Check Connection**, and **Quit Recall**. | Each item performs its intended action without unexpected duplicate windows. |

For the short walkthrough, use
[`docs/demo-script.md`](../../docs/demo-script.md).
Loading