Files
Marcos Paulo e8c8c06d1e
ci / ci (macos-latest) (push) Has been cancelled
ci / ci (ubuntu-latest) (push) Has been cancelled
ci / ci (windows-latest) (push) Has been cancelled
feat: smart folder model, media time fragments, link card URL guard (MiniMax); specs for 10 more gaps (Codex) and the registry with bloat measurements
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-21 14:41:26 -03:00

9.7 KiB

Global quick-capture window (agy gap 11, roadmap 38)

Part of specs/plugins-parity/. Reuse note.capture, native eframe viewport and desktop host. No new crate. Native shortcut dependency is an explicit owner gate.

Research (2026-10-21; agy claims checked)

  • Apple documents Fn-Q for Quick Note: Notes keyboard shortcuts. Confirms global-capture pattern; does not prove Reflect/Raycast parity, which is dropped.
  • Candidate safe facade global-hotkey documentation supports Windows/macOS/Linux X11, requires platform event-loop integration, and does not support Wayland. Do not copy its unwrap examples or claim every desktop works.
  • Wayland alternative is user-authorized XDG GlobalShortcuts portal, a later adapter with its own binding/approval gate. No bundled privileged helper.

Problem / goal

While another application has focus, explicitly registered shortcut opens compact NotesCraft capture viewport. Submit to chosen live vault's Daily note or Inbox through note.capture; preserve unsent text on dismissal or save failure. App must already run; no background daemon or launch-at-login in v1.

Verified baseline / files

apps/notescraft/src/main.rs: each opened vault is separate process and NotesCraftWindow wraps NotesApp; launcher_window.rs spawns vault processes. crates/engine/src/capture/{mod,rules}.rs already handles daily/Inbox/folder, 1 MiB text, source/title and attachment. crates/ui-egui/src/shell/hotkeys/{defaults,dispatch,commands}.rs implements focused-app hotkeys only; not global registration. crates/ui-egui/src/shell/app.rs holds app state and shell/app/window/{commands,frame}.rs dispatches actions. apps/notescraft/Cargo.toml has no direct global-hotkey dependency. crates/engine/src/prefs/{app_settings,settings,mod}.rs owns app preferences; prefs/catalog/list.rs provides remembered vaults.

Non-goals

Auto-start after quit; screenshot/clipboard monitoring; frontmost-app text extraction; cross-vault writes without live session; silent capture when locked/read-only; mobile global shortcut. No changes to shared craft-libs API.

Design decisions

D1 Pure draft state

New crates/ui-egui/src/capture/model.rs, declared by new capture/mod.rs and existing src/lib.rs. Draft { text:String, title:Option<String>, target:Target }, Target Daily or Inbox only; retain in memory, never auto-persist plaintext. CaptureState models Hidden, Editing, Saving, Failed with a monotonically increasing submission generation. Validate <=1 MiB UTF-8, title <=200 characters, whitespace-only invalid; no truncation. submit() produces one request only from Editing/Failed, rejects repeats during Saving. Complete matching generation clears draft on success; stale result cannot clear newer draft. Esc/close hides preserving draft. User's explicit Discard clears. Quit with unsent draft asks Discard/Cancel through existing close flow; crash loses draft, documented.

D2 Host/window lifecycle

Single secondary viewport per owning process, about 520x280 points, theme tokens, title/target, multiline input, Save/Discard; optional frameless styling must retain accessible Close and drag affordance. Open requests coalesce, repeat shortcut focuses existing window; never toggles it closed. Set focus/visibility via eframe viewport commands, no busy-loop or IO in paint. Request repaint on native hotkey event so minimized main window still processes it. Main editor does not receive capture keystrokes. Save Ctrl/Cmd+Enter, Escape hide; Tab navigation/focus restored on reopening. Submit failure keeps draft/window, success hides. Closing main process follows normal unsaved-data checks, unregisters hotkey, closes child.

D3 Shared command surface

Existing note.capture is sole persistence command. New engine capture.window.request {} -> {requested:true} queues one ephemeral UI event on Session; drained once by live shell. enabled requires writable live Session; no text/draft/token returned. Headless sessions return unsupported instead of claiming a window opened. capture.window.status {} -> {supported,available} reports host capability only. Host registers capability with Session at startup; registry never depends on egui. New capture/window.rs + registration in capture/mod.rs, add IDs to core_commands and assistant_gate (status READ, request WRITE; request may focus UI and must respect assistant gate). Native event and palette invoke same request command. CLI/MCP/control can request live window; no new public IPC server.

D4 Multi-process ownership

Global preference quick_capture.owner_vault is explicit canonical existing vault path selected by user, not active/recent vault chosen implicitly. Only process whose live Session matches may register chord; other vault windows show owner name and enabled/disabled status. Launcher alone cannot capture. Two processes for same vault: first successful OS registration wins, second shows conflict and never steals. Owner closes -> feature unavailable until owner vault reopened; no implicit rerouting or process spawning. Changing owner unregisters current registration before saving/applying replacement; never operate two registrations. Chord change: attempt replacement safely, on failure preserve prior effective registration and display configured/effective values rather than silent mismatch.

D5 OS registration / dependency gate

Default quick_capture.enabled=false; configurable chord, proposed Mod+Alt+Shift+N (validate against app table; OS conflicts checked at registration), selected owner path. Persist via existing settings; local machine preferences, never vault sync. Use candidate global-hotkey only after owner approval, version/pinning/license audited then; root workspace and target-specific app manifest changes belong to task 3. Register on macOS main event-loop thread; Windows manager/event-loop same thread. Poll channel without blocking and unregister on disable/rebind/exit. Repo forbids unsafe: no handwritten FFI, no exception to lints. No eframe/winit platform callbacks assumed available without compile probe against pinned versions. If adapter cannot integrate supported event loop, stop/report, do not install low-level keyboard hooks.

macOS/Windows/X11 candidates; OS denial/chord conflict -> visible actionable error and ordinary palette request still works. Wayland reports unsupported in v1, with instructions to use focused-app capture; do not label focused-app binding global. Native Wayland portal postponed until owner approves binding and permission UX. global-hotkey approval is not approval for portal/objc/windows dependency additions.

D6 UI and privacy

Strings [ui.quick_capture] appended to crates/i18n/locales/en.toml, table module through text/features/mod.rs. Reuse craft-ui inputs/buttons/modal and theme tokens; no logging draft text. Settings enable/chord/owner/status and unsaved-draft warning. Memory draft belongs only to selected owner process. If selected vault becomes read-only/locked, refuse submit; preserve draft and explain. No clipboard reads, screenshot capture, network or automatic focus tracking.

Acceptance criteria

  1. Pure model tests: whitespace/oversize rejected; repeated Save emits one request; failed save preserves text; success clears only matching generation; Escape retains, Discard clears.
  2. capture.window.request via control and native event shows same viewport once; headless returns unsupported. No UI imports below L5.
  3. Actual OS smoke test from another app while owner main window minimized: shortcut shows/focuses capture, input lands there, Cmd/Ctrl+Enter saves to selected owner Daily/Inbox exactly once. Main editor unchanged except intended daily-buffer update.
  4. Dirty daily note receives capture through existing Session buffer, undo remains correct. Read-only or write error leaves draft editable and retryable.
  5. Two vault processes do not compete; non-owner cannot register. Second identical owner reports conflict; disable/quit releases chord. No foreground-app shortcut mistaken for global.
  6. Chord change failure preserves previous effective shortcut; OS unsupported/denied status explicit. Wayland does not claim support.
  7. Close/reopen child preserves draft; process quit asks before discard. No capture text in settings, disk recovery, logs or control status.
  8. Kittest covers keyboard/focus/Save/Discard, localized strings; visual snapshots read at Mocha/Latte and compact width. Main-window close retains existing safety flow.

Open owner questions

  • Q1: approve new global-hotkey dependency for desktop host (reason: safe Rust global registration under unsafe-forbidden policy), macOS/Windows/X11 first? Version/lock changes require approval before task 3.
  • Q2: accept Wayland unsupported v1 or approve separate portal dependency/permission design? No implicit native bindings added.
  • Q3: confirm one explicit owner vault, app-running-only lifecycle and no automatic transfer/launch-on-login.
  • Q4: confirm memory-only draft with quit confirmation, rather than disk plaintext recovery; proposed chord Mod+Alt+Shift+N remains user-configurable.

Feature ideas (researched, ranked)

  1. Wayland GlobalShortcuts portal, permission-driven binding after owner approval.
  2. Quick Note resume pattern from Apple Notes shortcuts: one retained draft; later multiple draft tabs only on demand.
  3. Platform-native shortcut preferences using global-hotkey supported backends, with real conflict diagnostics.