Auto-update was broken on every platform: builds since aaf4ccc embed pubkey
6F4B5994, but shipped 1.3.8 stable users trust 1DEAA803 and CI now signs with
1DEAA803 again. A key the installed app doesn't trust => download succeeds,
install() signature verification fails => "downloads but never updates".
Revert the desktop updater pubkey to 1DEAA803 so it matches both the CI
signing key and android/updater.rs (which already hardcodes 1DEAA803). This
also repairs Android auto-update, which was signing-vs-verifying mismatched.
One line, no logic change.
Co-authored-by: sim <sim@local>
Re-cut of the 1.3.10-2 beta from the synced cloud beta branch (supersedes the
-2 pre-release, which was deleted). Identical code to 1.3.10-2 — version-number
bump only. Bundles #701 persistence split, #702 top-bar unification, #703
onboarding accessibility gate, on top of 1.3.10-1 audit fixes.
Bundles the two macOS UX fixes already merged into beta:
- #702 unify macOS shell top inset with sidebar/background surface
- #703 require macOS Accessibility grant before first-run onboarding completes
Version synced across package.json, package-lock.json, tauri.conf.json,
Cargo.toml, Cargo.lock. Local release build verified (1.3.10-2 installs and
runs; top bar seamless; first-run requires Accessibility).
On macOS the hotkey needs Accessibility, but first-run onboarding could finish
after Microphone only and drop users into the main UI with a dead hotkey
("nothing happens when I press the key").
Cause: desktop onboarding gated completion on the hotkey *capability* flag
(requiresAccessibilityPermission). That flag is null while the capability IPC
loads, so requiresAccessibility was briefly false — if Mic got granted in that
window, refresh() called onComplete() before Accessibility was ever requested.
App.tsx's startup gate, by contrast, always requires Accessibility, so the two
disagreed.
Fix: gate completion on the real permission status, mirroring App.tsx — macOS
returns granted/denied, every other platform returns notApplicable, so this is
timing-independent and cross-platform safe. Also surface the Accessibility step
whenever it is denied (macOS-only state) and drop the capability dependency from
its grant button, so the step always appears and is actionable even before the
capability finishes loading.
No change on Windows/Linux/Android (Accessibility = notApplicable there).
Co-authored-by: sim <sim@local>
The 28px top inset that clears the macOS overlay traffic lights sat on the
content wrapper with no background, so it exposed the lighter WindowChrome
glass (--ol-window-bg) as a full-width band above the sidebar and panel —
reading as an extra top row that the Windows build (no inset) doesn't have.
Paint that wrapper with the same --ol-app-shell-bg the sidebar/background
already use, so the inset blends into one continuous surface with the panel
card floating on top. One property; token carries its own light + dark
values; no layout, token, or other UI changes.
Co-authored-by: sim <sim@local>
Behavior-unchanged decomposition of the 2757-line `persistence.rs` god-file
into a `persistence/` module, one file per concern. Everything is re-exported
via glob (`pub use <submodule>::*`) so the `crate::persistence::*` public
surface is byte-for-byte identical and no call site changes.
Layout:
- mod.rs shared fs/io helpers (data_dir, ensure_dir, atomic_write,
read_or_default) + the two cross-cutting consts + re-exports
- paths.rs models / recordings / Foundry roots + migration helpers
- credentials.rs OS credential vault (CredsRoot model, keyring/android/legacy,
CredentialAccount, CredentialsSnapshot, CredentialsVault)
- history.rs HistoryStore
- preferences.rs read_preferences + PreferencesStore
- style_pack.rs StylePackStore + ZIP import/export + prefs reconciliation
- dictionary.rs DictionaryStore + vocab presets
- correction.rs CorrectionRuleStore
Pure move + re-export: no logic, signatures, public API, or UI changed. Unit
tests moved next to the code they now cover. Shared helpers stay private in
`mod.rs` (parent items are visible to child modules), and glob re-exports keep
cfg-gated items (Android/Windows) resolution-safe across every target —
avoiding the explicit-`pub use` breakage seen in the earlier types.rs split.
Verified on macOS: cargo fmt/check/clippy clean (no new persistence warnings),
446 lib tests green. Android/Windows cfg paths verified by CI.
Part of #697.
Co-authored-by: sim <sim@local>
ensure_qa_window was un-gated, so it compiled on the Android target where WebviewWindowBuilder has no decorations/shadow/etc. Gate the desktop builder + WebviewUrl/WebviewWindowBuilder import to not(android/ios); add an Android/iOS stub returning get_webview_window (QA routes to main there; show_qa_window early-returns on Android so it's compile-only). Desktop (Win/macOS/Linux) already passed.
These 3 windows were declared in tauri.conf.json and created eagerly at startup, each holding a resident WKWebView content process even though they're rarely shown. Remove them from tauri.conf; create on first show via WebviewWindowBuilder (ensure_qa_window / ensure_less_computer_window / ensure_less_computer_glow_window) with config matching the old declarations exactly. QA drag-fix (make_qa_window_draggable_macos) was startup-only — re-applied on create. Drops ~3 resident WebKit processes (~30-50MB each) when idle.
Tradeoffs to verify on macOS: (1) first QA/Less-Computer open builds the webview before showing (slight first-use delay); (2) QA emit-race is benign (first emit = idle/empty = panel default); Less-Computer's first turn bubble could be missed on cold open (recovers next emit) — verify. Confirm QA still drags + positions; glow shows over fullscreen.
All 5 locales (en/ja/ko/zh-CN/zh-TW) were statically bundled into the index chunk that every one of the 5 webviews loads. Keep only zh-CN (fallback + source of truth) inline so init stays synchronous; lazy-load the other 4 as separate chunks on demand (initial non-zh-CN locale loads async after first paint, then changeLanguage; setLocalePreference awaits the locale before switching). Worst case = graceful zh-CN fallback, never broken keys. Cuts ~155KB raw / ~75KB gzip from the per-webview index; zh-CN users never load the other 4.
Build-verified: index 285KB + en/ja/ko/zh-TW separate chunks. Runtime-sanity: switch language in settings (en/ja/ko/zh-TW) to confirm it loads + applies.
During recording the audio level updates ~60Hz, re-rendering the whole Pill. The cancel/confirm SVG buttons and the pill metrics don't depend on level, so: React.memo(CircleButton) + useCallback(onCancel/onConfirm) lets the two buttons skip re-render per frame, and useMemo the os-derived metrics. Pure render optimization — identical output, no behavior/NSPanel change. Build-verified.
Re-add width=device-width after dark-mode squash replaced it; keep color-scheme meta for native control hints.
Co-authored-by: Cursor <cursoragent@cursor.com>
All 5 webviews (settings/capsule/qa/less-computer/glow) shared one ~942KB bundle, so every WebKit content process parsed+held the entire app (settings UI + LocalAsr 3.4k LOC + Style + Marketplace + chat panels). React.lazy each window root: FloatingShell (settings, 352KB) + QaPanel + LessComputerPanel + LessComputerGlow + Onboarding + AutoUpdateGate are now separate chunks loaded on demand. Capsule kept eager (tiny + latency-sensitive). Secondary webviews no longer load the settings UI → much lower resident memory per process.
Frontend-only, build-verified (chunks split, tsc+vite pass). Runtime-sanity: open settings / QA / Less Computer / glow once to confirm they still render (Suspense fallback is blank during first chunk load).
v1.3.7 fixed cross-desktop but not fullscreen: a plain NSWindow can't draw over ANOTHER app's fullscreen Space even with CanJoinAllSpaces+FullScreenAuxiliary+level (known Tauri limit: tauri#9556/#11488). The window must be a non-activating NSPanel. Add tauri-nspanel (macOS-only git dep), register the plugin, and convert the capsule via to_panel() at setup with NSNonactivatingPanel mask + FullScreenAuxiliary|CanJoinAllSpaces + level 25, per the plugin's own fullscreen example.
Note: Cargo.lock not regenerable here (no local cargo) — CI resolves the git dep; commit the lock update after the first local 'tauri dev'. Runtime-verify on macOS before release.
v1.3.6-5 fixed cross-desktop following but not fullscreen: FULL_SCREEN_AUXILIARY only lets the capsule JOIN the fullscreen Space; at alwaysOnTop's floating level (~3) it still renders behind the fullscreen window. setLevel(25) (above the menu bar, matching show_less_computer_glow which does overlay fullscreen) makes it render on top. Add a contract assertion to lock it in.
Runtime-verify on macOS: enter a fullscreen app, trigger dictation, capsule should now appear over it.
Add useRafThrottle; QaPanel/LessComputerPanel parse streamed markdown at frame-rate instead of per-token (was O(n^2)); LessComputer DOM-measure + IPC window-resize coalesced to one per frame. Capsule keyframes injected once into document.head instead of re-created every ~60Hz audio-level frame.
Backend perf: add [profile.release] (lto=thin/codegen-units=1/strip); tray mic watcher 1.5s->10s; global-hotkey dispatcher blocks on recv() instead of 250ms poll; coding_agent supervisor exits on non-macOS instead of 5s poll; cache+reuse reqwest client across utterances (net.rs) in OpenAI/Codex/Gemini providers (kills per-utterance cold TLS handshake).
fix(macos): capsule joins all Spaces + fullscreen via an absolute setCollectionBehavior(273) matching show_less_computer_glow; drop the racy Tauri set_visible_on_all_workspaces that deferred-overwrote the manual FullScreenAuxiliary bit.
chore: remove 12 orphaned coordinator/*.rs (never mod-declared -> not compiled); repoint macos-capsule-spaces contract test to the live coordinator.rs.
Replace fixed sidebar on narrow/Android with top bar, bottom tabs, and more sheet; adapt Overview, History, Vocab, and Settings for portrait; add android-ui-verify.ps1.
Co-authored-by: Cursor <cursoragent@cursor.com>