Home

dev / openkara

publicthedavidweng/OpenKara· sync paused
Overview Code History Branches Pull requestsIssuesInsights
main
HomeOverview Code PRsIssues

#700 refactor(architecture): isolate Tauri behind platform adapters without committing to a new UI stack

open

Opened by jweng10 · 5 days ago

jweng10opened this issueAuthor· 5 days ago

Sign in to comment.

Linked pull requests

No linked pull requests yet.

Goal

Make OpenKara less dependent on Tauri as an application architecture, while keeping Tauri 2 as the supported desktop shell today.

We have not decided whether to move to GPUI/GPUI Shell or any other native UI framework. We have not decided to ship a Web App. This refactor should lower the cost of either future path:

  1. Keep Tauri + React and add a real browser/Web App runtime.
  2. Replace the desktop presentation/shell with GPUI Shell (or another host) while reusing the Rust domain logic.
  3. Keep the current stack indefinitely, but improve testability and reduce framework coupling.

Do not optimize for a speculative migration at the expense of the existing product. Preserve behavior and current desktop release coverage.

Architectural guardrail: preserve Tauri's convenience

Tauri is an implementation dependency, not an architectural defect.

Keep direct Tauri API usage in desktop-only modules when that is the simplest, most maintainable option. Do not introduce an abstraction unless it serves an existing cross-platform consumer, removes meaningful business-layer coupling, or enables concrete testing. Prefer Tauri's official commands, state management, plugins, events, window APIs, packaging, and updater over custom replacements.

In practice:

  • It is fine for clearly scoped desktop adapters and native-only features to call [@tauri-apps](/tauri-apps)/* or Tauri Rust APIs directly.
  • Do not introduce one-to-one wrappers around every Tauri API, duplicate the plugin system or IPC transport, or demand browser/GPUI equivalents for every desktop feature.
  • A future browser or GPUI adapter should implement only meaningful capabilities, with explicit unsupported cases. Do not diminish desktop UX to achieve fictitious API parity.
  • Judge success by reduced business/UI coupling, clearer ownership, easier testing, and unchanged developer ergonomics—not by achieving zero Tauri imports or maximizing the number of interfaces.
  • Keep native features and desktop release ergonomics just as easy to add and maintain as before; if an abstraction adds ceremony without measurable value, leave the direct desktop integration in place.

Current baseline (verify against main before implementation)

There are already useful seams. Extend them; do not replace them with a competing abstraction layer:

  • src/lib/backend/types.ts defines domain-facing backend interfaces.
  • src/lib/backend/BackendProvider.tsx, context.ts, and tauri-backend.ts provide typed injection and a Tauri implementation.
  • src/runtime/event-source.ts defines RuntimeEventSource, but its default subscribes to Tauri directly.
  • website/src/AppPreview.tsx renders the real React app in a browser by installing window.__TAURI_INTERNALS__ mocks; this proves UI reuse, not production Web App readiness.
  • src-tauri/src/commands/README.md already describes commands as IPC adapters and asks for domain logic to remain outside them.

Remaining coupling examples (inventory the full codebase; this is not an exhaustive list):

  • src/components/Playback/PlaybackStage.tsx and cover/streaming UI import Tauri APIs.
  • src/hooks/use-file-drop.ts binds directly to Tauri WebView drag-and-drop.
  • src/lib/fullscreen-player.ts, src/lib/native-context-menu.ts, and src/runtime/window-shell-runtime.ts handle desktop-specific windows/menus.
  • src/playback/youtube-watch-native.ts owns Tauri-specific native WebView/window behavior.
  • Event subscriptions, browser-independent stores, startup/runtime composition, and native-only functionality are not yet consistently separated.

Required direction

1. Define boundaries around capabilities, not around hypothetical frameworks

Keep the core React views, UI state, lyric rendering, design tokens, and domain types independent of the desktop host where practical.

  • Reuse the existing Backend contracts. Factor or refine only when a real call site warrants it.
  • Make transport-neutral event/subscription interfaces injectable. Tauri should be one adapter, not an implicit global dependency in shared code.
  • Move direct [@tauri-apps](/tauri-apps)/* imports from shared presentation/hooks into explicitly named desktop adapters or desktop-only composition modules. Do not hide native calls behind generic helpers spread across the app.
  • Separate domain operations from platform capabilities: file selection/drop, local file URLs, menus, context menus, multiwindow/fullscreen controls, notifications, AirPlay, deep links, embedded WebViews, lifecycle and updates.
  • For genuinely native-only features, expose a typed capability or explicit unsupported state; avoid misleading browser no-ops, fake support flags, repeated runtime detection, or massive interfaces where every method is optional.
  • Keep platform-specific logic outside generic components where feasible. A desktop-only view or feature may remain desktop-only; do not force lowest-common-denominator UX.
  • Make app startup/composition select the host adapter explicitly. Shared code should not silently fall back to tauriBackend or require window.__TAURI_INTERNALS__ to function.

2. Make the Rust domain independent of the desktop framework where it has a real benefit

  • Audit the direction of dependencies between src-tauri/src/commands/, AppState, and the audio, lyrics, library, catalog, remote, separation, and persistence modules.
  • Keep #[tauri::command], window handles, Tauri plugin types, IPC errors and event emission at the outer boundary. Core business operations should receive ordinary Rust inputs/services and return domain results.
  • Prefer small extraction/refactoring of concrete coupling points over moving every module into a new crate. Add library crates only if they reduce actual coupling, clarify a reused API, or improve measured build/test iteration.
  • Preserve real-time audio constraints, native model/runtime management, SQLite guarantees, streaming-source contracts, and existing platform behavior.
  • Do not introduce HTTP, WebSocket, WASM, a local daemon, or FFI just for architectural symmetry. They are possible future adapters, not requirements of this issue.

3. Preserve a future browser implementation path

  • It should be possible to compose the reusable React application against a future WebBackend / Web runtime without importing Tauri modules into the shared entrypoint.
  • Preserve the existing website preview and E2E mock fixtures. Prefer mocks at the typed backend/runtime boundary over growing the fake Tauri global API; migrate carefully and retain equivalent coverage.
  • Inventory functionality that can be browser-local (e.g. UI, lyrics rendering, local file selection, Web Audio, browser storage) versus functionality that would require a server or a platform-specific implementation (e.g. current ONNX pipeline, native audio output, streaming auth/proxy, AirPlay, native menus/windows).
  • Do not implement a Web App, server, service, browser storage engine, or a production WebBackend in this issue. Document blockers and stable ports only where justified.

4. Keep the refactor small and verifiable

  • Start with a source-based dependency map of direct Tauri imports, host assumptions, and native-only capabilities in both TS and Rust.
  • Consolidate the highest-impact leaks first; avoid churn in already clean domain modules.
  • Keep one authoritative implementation per concern; do not create redundant service locators, event buses, DI frameworks, or adapters solely to satisfy an architectural diagram.
  • No UI restyling, component-library migration, GPUI experiment, framework swap, feature removals, or unnecessary package/workspace restructuring.
  • Update the relevant contracts under docs/references/contracts/ when actual public IPC surfaces change. Record the durable architecture boundary in an existing architecture reference or a concise ADR if a new decision is introduced. Follow AGENTS.md and the existing engineering and verification rules.

Acceptance criteria

  • A clear, code-backed inventory identifies direct Tauri dependencies and native-only capabilities; unresolved platform blockers are documented without speculative implementation.
  • Shared UI/domain-facing modules no longer need direct imports of [@tauri-apps](/tauri-apps)/* at the primary cross-platform seams; intentional desktop-only imports live in explicit desktop modules.
  • Backend injection, typed runtime events, host lifecycle, and native capabilities have unambiguous owners. The desktop runtime remains functional with the existing Tauri adapter.
  • No browser preview/test needs a new fake Tauri API surface as a consequence of the refactor. Existing preview behavior and automated UI fixtures remain working.
  • Core Rust domain behavior does not depend on Tauri handles or command wrappers at newly refactored boundaries. No blanket crate split is required.
  • macOS, Windows, and Linux desktop capabilities and release/packaging paths remain unchanged, including playback, separation, streaming sources, library operations, updates, deep links, native menus/windows and relevant platform-specific tests.
  • Existing TypeScript, Rust, UI/accessibility and packaging verification applicable to touched areas passes. Report actual tests and any untested platform-specific cases.
  • No production Web App, GPUI migration, new server transport, or duplicate long-lived framework is introduced.

Implementation approach

Treat this as a boundary-hardening outcome, not a mandate for a large rewrite. Deliver the smallest coherent, reviewable changes. If the inventory reveals several independent high-risk slices, split implementation into narrowly scoped linked child issues/PRs rather than one enormous PR; keep this issue as the shared acceptance target.

Success means changing the host or adding browser support would mainly involve writing a new adapter and handling genuinely platform-specific features—not untangling Tauri calls from the whole UI and core. It does not mean promising the current Rust audio/AI engine will run unmodified in a browser.