Files
Lumotia/docs/architecture-map/02-tauri-runtime/commands/paste.md
Jake 26c7307607
Some checks failed
check / cargo check (macos-latest) (push) Has been cancelled
check / cargo check (ubuntu-22.04) (push) Has been cancelled
check / cargo check (windows-latest) (push) Has been cancelled
check / svelte build + lint (push) Has been cancelled
agent: lumotia-rebrand — docs, scripts, root config, residuals
Phase 9 of the rebrand cascade. Sweep covers everything the Phase 8
frontend pass deliberately skipped: docs/, root markdown, scripts,
Cargo.toml descriptions, code comments that survived earlier
word-boundary sed, plus a handful of identifiers caught on the final
verify pass.

transcription-app changes:
- README.md, HANDOVER.md, KNOWN-ISSUES.md, run.sh — magnotia/Magnotia
  -> lumotia/Lumotia.
- docs/ — sweep across all subdirs except docs/handovers/ (preserved
  as immutable audit trail). Includes architecture-map references
  to magnotia_core::*, magnotia_storage::*, etc. now pointing at
  lumotia_*; dev-setup.md tracing output examples (lumotia_startup
  target); brief/ + superpowers/ + issues/ + whisper-ecosystem/ +
  audit/.
- Cargo.toml descriptions on 9 crates (core, audio, cloud-providers,
  hotkey, llm, mcp, plus referenced others).
- crates/core/src/{error,hardware,recommendation,paths}.rs +
  crates/audio/src/wav.rs + crates/llm/src/model_manager.rs +
  crates/cloud-providers/src/keystore.rs + crates/mcp/src/lib.rs —
  doc comments and a model-manager user-agent string.
- Caught on final pass: BroadcastChannel("magnotia_task_sync") -> ...
  ("lumotia_task_sync"); magnotia_locale i18n localStorage key
  renamed + migration shim added; CSS keyframe names
  magnotiaPulse / magnotiaBar / magnotiaFade renamed in the design-
  system kit; magnotia_viewer_item / magnotia_viewer_mode handoff
  keys renamed in HistoryPage + viewer/+page.svelte; src/assets/
  wordmark.svg text.
- src-tauri/src/lib.rs comment cleanup ("magnotia era" was sed'd
  to "lumotia era" earlier — restored).

Preserved (intentional):
- crates/core/src/paths.rs — keeps "magnotia" / "Magnotia" / ".magnotia"
  legacy detection strings in legacy_and_target_paths() so the
  migration shim can still find user data from the magnotia era.
- src/lib/stores/{page,focusTimer}.svelte.ts + src/lib/i18n/index.ts
  — migration call sites reference the legacy magnotia keys
  deliberately.
- docs/handovers/ — historical audit trail.

cargo build --workspace passes. npm run check: 0 errors / 0 warnings
(3958 files). cargo test --workspace: 339 pass / 0 fail.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 12:38:03 +01:00

8.9 KiB

name, type, slice, last_verified
name type slice last_verified
Paste at cursor architecture-map-page 02-tauri-runtime 2026/05/09

commands::paste

Where you are: Architecture mapTauri runtimeCommands → Paste

Plain English summary. Auto-insert-at-cursor: copy the transcript onto the clipboard and synthesise the platform's paste keystroke (Ctrl+V / Cmd+V) so the text lands in whatever app the user was already focused on. Adds a replace-with-raw flow that fires undo first. Skips the keystroke when the focused window is a terminal emulator (terminals duplicate the keystroke through the PTY). Hides the always-on-top preview overlay before the keystroke so a Wayland compositor doesn't accidentally route the paste back into Lumotia. Restores the user's prior clipboard 300 ms after the paste to honour the "never silently clobber the user's clipboard" contract.

At a glance

  • Path: src-tauri/src/commands/paste.rs.
  • LOC: 790.
  • Tauri commands exposed:
    • paste_text(app, text: String) -> Result<PasteOutcome, String>. Copy + paste at cursor. Returns a PasteOutcome { backend, pasted, copied, message }.
    • paste_text_replacing(app, text: String) -> Result<PasteOutcome, String>. Replace flow: undo → 60 ms gap → paste. Same return shape.
    • detect_paste_backends() -> Vec<String>. Pure probe used by Settings: returns the names of available backends on the current OS / session.
  • Events emitted: none.
  • Depends on: arboard::Clipboard, tauri::Manager (to find and hide the preview window), std::process::Command (every backend shells out).
  • Called from frontend at: dictation result toast / replace-with-raw button (the two main user-facing actions).

What's in here

Constants (src-tauri/src/commands/paste.rs:29)

  • CLIPBOARD_RESTORE_MS = 300 — window after paste before restoring the user's prior clipboard.
  • PREVIEW_HIDE_SETTLE_MS = 80 — compositor settle time after hiding the preview overlay.
  • UNDO_PASTE_GAP_MS = 60 — gap between undo and follow-up paste in the replace flow (brief item #17).

PasteOutcome (src-tauri/src/commands/paste.rs:43)

Frontend-facing struct: backend: Option<String> (e.g. "wtype", "xdotool", "ydotool", "osascript", "sendkeys"), pasted: bool, copied: bool, message: Option<String>.

paste_text (src-tauri/src/commands/paste.rs:67)

Step-by-step:

  1. Snapshot the current clipboard text via arboard::Clipboard::get_text (or None for non-text content).
  2. Write the transcript onto the clipboard. If this fails, return immediately with copied=false and the error.
  3. Probe the focused window via detect_focused_terminal(). If a known terminal class hits, return with a "Terminal detected" message; the user can finish manually with Ctrl+Shift+V or right-click. Note: prior clipboard is intentionally NOT restored here, because the user's recovery path needs the transcript on the clipboard.
  4. hide_preview_overlay_for_paste(&app).await — find the transcription-preview window if it exists and is visible, hide it, sleep 80 ms.
  5. trigger_paste_keystroke() — per-OS dispatch.
  6. If the keystroke fired, schedule restore_prior_clipboard 300 ms later via a detached tokio::spawn.

paste_text_replacing (src-tauri/src/commands/paste.rs:185)

Same shape as paste_text but with an extra step: after copying and hiding the preview, fire trigger_undo_keystroke(), sleep 60 ms, then paste. The undo removes whatever text Lumotia already inserted (the cleaned-up transcript), the paste inserts the raw text. Used by the "replace with raw" frontend button per brief item #17.

detect_paste_backends (src-tauri/src/commands/paste.rs:258)

Pure probe: Linux returns the subset of ["wtype", "xdotool", "ydotool"] that resolve via command -v; macOS always returns ["osascript"]; Windows always returns ["sendkeys"]. Settings uses this to tell the user "install wtype" when Linux is empty.

Terminal classifier

  • KNOWN_TERMINAL_CLASSES (src-tauri/src/commands/paste.rs:292) — substring list ordered longest-first so windowsterminal wins over terminal. Covers Windows (PowerShell, conhost, console, pwsh, cmd, WindowsTerminal), macOS (Terminal, iTerm, iTerm2), Linux (alacritty, konsole, gnome-terminal-server, kitty, wezterm, foot, st, urxvt, xterm, hyper, tilix).
  • classify_terminal(raw) (src-tauri/src/commands/paste.rs:331) — case-insensitive substring search over the list.
  • detect_focused_window_class() plus per-OS implementations (:345).

Per-OS focused-window probes

  • Linux X11: xdotool getactivewindow getwindowclassname (:365).
  • Linux Wayland: returns None. No reliable probe from an unprivileged client. Documented as "by design".
  • macOS: osascript -e 'tell application "System Events" to get name of first application process whose frontmost is true' (:386).
  • Windows: PowerShell P/Invoke wrapper around GetForegroundWindow + GetWindowThreadProcessId + Get-Process (:407).

Per-OS paste / undo backends

  • Linux: linux_paste / linux_undo walk pick_linux_backend_order (:502), which flips the preference order based on session type (wtype, ydotool, xdotool for Wayland; xdotool, ydotool, wtype for X11). Each tool is invoked via Command::new; wtype uses -M ctrl v -m ctrl, xdotool uses key ctrl+v, ydotool uses raw linux input keycodes (29:1 47:1 47:0 29:0). Undo replaces V (47) with Z (44).
  • macOS: osascript -e 'tell ... to keystroke "v" using command down' (:594). Undo swaps in "z".
  • Windows: powershell -Command "(New-Object -ComObject WScript.Shell).SendKeys('^v')" (:634). Undo uses '^z'.

Helpers

  • snapshot_clipboard_text (:125) — wraps arboard.
  • schedule_clipboard_restore (:137) — fires a tokio::spawn that sleeps CLIPBOARD_RESTORE_MS and calls restore_prior_clipboard.
  • restore_prior_clipboard (:152) — reads the current clipboard, calls should_restore (:171), and writes back the prior content if and only if the clipboard still holds the transcript we wrote (i.e. the user has not copied something else in the interim).
  • hide_preview_overlay_for_paste (:240) — find the preview window, check visibility, hide, sleep 80 ms.

Data flow

frontend invoke('paste_text', { text })
  -> snapshot clipboard
  -> write transcript to clipboard
  -> detect terminal: yes -> early return with "manual paste needed" message
  -> hide preview overlay (Wayland focus quirk)
  -> trigger paste keystroke per OS
  -> spawn 300 ms timer -> restore prior clipboard if clipboard still holds the transcript
  -> return PasteOutcome

Replace flow inserts an undo keystroke and a 60 ms gap between hide and paste.

Watch-outs

  • Focus must already be on the target window when the keystroke fires. The global hotkey flow preserves this naturally; clicking Lumotia's own UI does not. The frontend Settings page surfaces the caveat next to the toggle.
  • Wayland focused-window probe is intentionally absent. No way to do this from an unprivileged Wayland client. Result: terminal detection on Wayland-Kitty users will miss; they fall back to the manual Ctrl+Shift+V they already use.
  • Clipboard restore is best-effort. If the user copies something else within the 300 ms window, should_restore will (correctly) decline to write back. If a slow Wayland compositor delays the keystroke past 300 ms, we restore too early and the synthesised Ctrl+V pastes the user's old clipboard. Tradeoff documented in Handy #921.
  • Linux backend order is session-aware. A user with XDG_SESSION_TYPE=wayland and only xdotool installed will fall through to xdotool with a warning; their X11-app focus on a Wayland session works fine but Wayland-native apps will not see the keystroke.
  • No backend = clipboard-only. If trigger_paste_keystroke fails on Linux (no tools installed), the user still has the transcript on the clipboard; Settings shows the install-wtype hint via detect_paste_backends.
  • PowerShell process spawn cost on Windows. Each paste spawns a fresh PowerShell. Acceptable for one-off paste invocations; if you ever build a streaming-paste mode, switch to native SendInput via the windows crate.
  • Permissions. The macOS path requires the user to have granted Accessibility permissions to Lumotia (System Settings → Privacy → Accessibility). Without that, osascript returns success but the keystroke is silently dropped. There is no probe today; flag this in onboarding.

See also

  • Window managementtranscription-preview is the window that gets hidden by hide_preview_overlay_for_paste.
  • Small commands → clipboard — the bare copy_to_clipboard command, used when the user explicitly wants clipboard-only.
  • Live transcription — the upstream of the dictation result that gets pasted.
  • Hotkey bridge — the global hotkey path that preserves focus on the target window.