Introduces the architectural spine for the Wyrdnote engine layer per the spec at outputs/wyrdnote/2026-05-10-engine-architecture-spec.md in the CORBEL-Main workspace, Codex-reviewed 2026/05/10. Clean-room derivative of VoiceInk's TranscriptionService / TranscriptionServiceRegistry / TranscriptionPipeline shape. Built from the architectural pattern only; no GPL-3.0 code, comments, or identifiers lifted. Wyrdnote names, control flow, and trait signatures are original. What this lands: crates/cloud-providers/src/provider.rs (new, 184 lines) TranscriptionProvider async trait. Object-safe via async_trait so Arc<dyn TranscriptionProvider> is the canonical shape. Lives in cloud-providers (not transcription) per D7 so an OEM licensee implementing the trait depends only on this crate plus magnotia-core (no transcription internals leaked through the trait surface). Surrounding types: ProviderId (lower-kebab-case), ProviderKind, NetworkRequirement, CostClass, ProviderCapabilities, EngineProfile, ProviderTranscript. Compile-time object-safety witness in tests. crates/cloud-providers/Cargo.toml (modified) Adds async-trait + serde dependencies. crates/cloud-providers/src/lib.rs (modified) Re-exports the provider surface. crates/transcription/src/registry.rs (new, 183 lines) EngineRegistry: catalogue of providers keyed by ProviderId. Push-style registration, default-provider id captured at construction. Read-only after boot. Full unit-test coverage: empty default, register/get round-trip, default-resolves-after-registration, re-register replaces, ids enumeration. crates/transcription/src/orchestrator.rs (new, 286 lines) LocalProviderAdapter wraps Arc<LocalEngine>, presents async TranscriptionProvider upward via tokio::task::spawn_blocking. Per D7 the adapter lives in the orchestrator, NOT as impl TranscriptionProvider for LocalEngine — this keeps the dependency edge one-directional and avoids leaking async-runtime requirements onto the synchronous Transcriber trait. Orchestrator: single transcribe() entry point; resolves a provider from the registry, derives TranscriptionOptions from the EngineProfile, dispatches. Returns a clear error when an unregistered provider is named. Unit tests use a mock CannedProvider to validate dispatch, error handling, and option routing without booting a real model. crates/transcription/Cargo.toml (modified) Depends on magnotia-cloud-providers + async-trait. Dev-dep tokio gains macros + rt-multi-thread features for #[tokio::test]. crates/transcription/src/lib.rs (modified) Exports Orchestrator, EngineRegistry, LocalProviderAdapter, and re-exports the provider trait surface so downstream crates depend only on magnotia-transcription for both local engines and the trait. KNOWN-ISSUES.md (modified) Adds KI-06: existing dictation, live, and meeting commands still call LocalEngine::transcribe_sync directly via pick_engine. The orchestrator path is dormant until a Phase A.1 follow-up commit migrates the call sites. Cloud providers (Phase G) cannot be exercised end-to-end until the rewire lands. Phasing rationale: this commit lands the abstractions cleanly with full test coverage; the rewire is a separate diff so the chunking + post-processing logic in transcribe_file moves without inflating this commit beyond review fidelity. Test results: 9 new tests pass (orchestrator: dispatches, errors-on- unregistered, routes-initial-prompt, object-safe; registry: empty-default, round-trip, default-resolves, re-register, ids-list). Pre-existing 59 transcription tests + 5 cloud-providers tests still green. cargo check --workspace clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
7.2 KiB
Known issues
Tracked limitations and partial implementations in the current codebase. Each entry points at the code that owns the gap so future work can find it.
Power management
KI-01 — macOS App Nap guard pending Apple Silicon runtime verification
Status: macOS code path compiles and the registry tests pass, but runtime behaviour against actual OS idle-throttling has not been confirmed on Apple Silicon hardware. Also tracked internally as RB-08.
Source: src-tauri/src/commands/power.rs:73 (PowerAssertion::begin), src-tauri/src/commands/power.rs:140 (objc_bridge).
Impact: Long live-dictation sessions on macOS may still be slowed or paused by the OS until verified end-to-end on hardware.
Workaround: Keep the app window focused, or move the cursor periodically. For testing, App Nap can be disabled globally with defaults write NSGlobalDomain NSAppSleepDisabled -bool YES (revert with -bool NO).
KI-02 — Linux power assertion is a no-op
Status: PowerAssertion::begin does nothing on Linux. The planned implementation (systemd-logind / GNOME idle inhibitor via org.freedesktop.login1.Inhibit) is described in the file-level doc but not wired up.
Source: src-tauri/src/commands/power.rs.
Impact: Long sessions can be paused by the compositor's idle hooks (screen lock, suspend timers) on KDE, GNOME, Hyprland, Sway, etc.
Workaround: Raise the system idle / screen-lock timeout while dictating, or wrap launch with systemd-inhibit --what=idle:sleep:handle-lid-switch ./run.sh.
KI-03 — Windows power assertion is a no-op
Status: PowerAssertion::begin does nothing on Windows. The planned implementation (SetThreadExecutionState(ES_CONTINUOUS | ES_SYSTEM_REQUIRED | ES_AWAYMODE_REQUIRED) on begin, ES_CONTINUOUS alone on end) is described in the file-level doc but not wired up.
Source: src-tauri/src/commands/power.rs.
Impact: Long sessions can be paused by Windows sleep policies.
Workaround: Set the active power plan's sleep to "Never" while dictating.
Cloud providers
KI-04 — magnotia-cloud-providers crate is not user-exposed
Status: The crate is a declared workspace dependency in src-tauri/Cargo.toml:36 and compiles into the binary, but no Tauri command, page, or settings field invokes store_api_key / retrieve_api_key. There is no UI to enter or store a cloud API key in the current build.
Source: crates/cloud-providers/src/keystore.rs. The TODO in the doc comment captures the planned migration to OS-native credential storage (the keyring crate or equivalent) before the feature is wired up.
Impact: None for end users today (no exposed surface). When the feature is wired post-launch, cross-restart persistence must land before any "save key" UX ships, otherwise saved keys reset on every restart.
Workaround: N/A.
Frontend
KI-05 — Dual theme system: settings.theme and preferences.theme coexist
Status: Theme is stored twice. The legacy settings.theme ("Dark" / "Light" / "System", localStorage-only) is bound to two SegmentedButton controls in SettingsPage.svelte. The canonical preferences.theme ("dark" / "light" / "system", Tauri-persisted via save_preferences) is what the DOM and runtime actually consume. A migration $effect in src/routes/+layout.svelte (and the secondary +layout@.svelte files for /preview, /viewer, /float) syncs settings.theme → preferences.theme whenever the legacy value changes.
Source:
src/lib/stores/page.svelte.ts:61(legacy default:theme: "Dark")src/lib/stores/preferences.svelte.ts:30(canonical default:theme: "dark")src/routes/+layout.svelte:61-68(migration$effect)src/lib/pages/SettingsPage.svelte:1118,:2360(UI bindings still on the legacy field)
Impact: No user-facing bug; theme switching works. The cost is code complexity and a small race window where preference fan-out can lag behind a settings.theme change. The $effect is lightweight (string compare plus a debounced persist when the mapped value differs), not a hot-loop performance problem despite earlier framing.
Resolution (deferred): Switch the two SettingsPage bindings to preferences.theme with mapped options, retire the migration $effect in all four route layouts, drop theme from SettingsState, and add a one-shot localStorage migration that copies any historical settings.theme into preferences on first run after the cleanup. Touches ~5 files including the 2 484-LOC SettingsPage.svelte; deferred from the v0.1 polish pass to avoid a moderate-risk frontend change immediately before launch.
Workaround: N/A.
Engine architecture (Phase A)
KI-06 — transcribe_file and live-transcription commands not yet rewired through Orchestrator
Status: Phase A introduced the TranscriptionProvider async trait (crates/cloud-providers/src/provider.rs), EngineRegistry (crates/transcription/src/registry.rs), and Orchestrator plus LocalProviderAdapter (crates/transcription/src/orchestrator.rs). The abstractions compile, are object-safe, and pass unit tests against a mock provider. The existing dictation path (src-tauri/src/commands/transcription.rs) still calls LocalEngine::transcribe_sync directly via pick_engine, not through the orchestrator. Live and meeting commands likewise route around the orchestrator.
Source: src-tauri/src/commands/transcription.rs:29 (pick_engine), src-tauri/src/commands/live.rs, src-tauri/src/commands/meeting.rs.
Impact: None at runtime. The orchestrator path is dormant until a follow-up commit migrates the call sites. Cloud providers (Phase G) cannot be exercised end-to-end until this rewire lands.
Resolution (deferred): Phase A.1 follow-up commit. Build an EngineRegistry at app boot (one LocalProviderAdapter per LocalEngine), inject Arc<Orchestrator> into AppState, replace pick_engine plus engine.transcribe_sync chunking-loop calls with orchestrator.transcribe(audio, &profile) per chunk. Keep the chunking strategy in the command layer (it is a strategy concern, not a dispatch concern). Tests should cover both paths against a mock provider before touching the real engines.
Workaround: N/A. Existing path works as before.
How to add an entry
When shipping a partial implementation or known limitation, add a KI-NN entry here with the four standard fields: Status, Source (file:line), Impact, Workaround. Link from the affected module's doc comment back to this file by ID.