Skip to content
Rymflux Documentation
Esc
navigateopen⌘Jpreview
On this page

Shell and Room Framework specification

Shell and Room Framework specification

Why the shell doesn’t own presentation

A shell that controls layout, navigation, and metadata rendering produces a one-size-fits-all UI — exactly what the vision rejects. The shell’s job is limited to:

  1. Providing the application frame (titlebar, sidebar, mini-player).
  2. Managing navigation between rooms.
  3. Subscribing to platform events and routing them to the active room.
  4. Rendering the active room’s component and the player expansion slot.

The shell does not know what an “album” is. It does not know what a “podcast episode” is. It does not render search results differently for music vs. audiobooks. Rooms own all of that. The shell is a frame, not an application.

Responsibility

Shell: top-level Tauri webview, navigation, mini-player, room registry, event dispatch. Room Framework: room registration, lifecycle, context injection.

Room registry

Static array compiled into the binary:

// src/shell/rooms.ts
const ROOMS: Room[] = [
  MusicRoom,
  // Future experience rooms (Podcast, Audiobook, Radio, etc.) added here
];

No dynamic loading in v1. Rooms are Svelte components in the same repository.

Lifecycle

Inactive → shell activates → Active (onActivate called)
Active   → shell deactivates → Inactive (onDeactivate called)

V1 only implements Inactive ↔ Active. No Suspended state — deactivated rooms remain mounted in the DOM (hidden via CSS). This makes reactivation instant and avoids state serialization.

  • onActivate(ctx): room receives RoomContext with platform handles. Room should set up its state, fetch initial data, subscribe to events.
  • onDeactivate(): room should unsubscribe from events, save draft state. Component remains mounted.
  • Rooms that don’t need cleanup don’t implement onDeactivate.

RoomContext

interface RoomContext {
  search: (query: string) => Promise<SearchResults>;
  queue: QueueHandle;
  library: LibraryHandle;
  playback: PlaybackHandle;
  navigate: (view: string) => void;
}

Handles are imperative — rooms call methods directly, not through stores.

Player expansion slot

The shell reserves a region in its layout. When the player is in expanded state and the active room defines playerExpansion, the shell renders the room’s component into that region.

  • Expansion state: shell-level boolean, toggled by expand button
  • Persisted in: Svelte state (not SQLite)
  • Rooms without playerExpansion: the slot is empty, not hidden by CSS
<!-- Shell layout (simplified) -->
<div class="shell">
  <nav><!-- room navigation --></nav>
  <main><!-- active room component --></main>
  {#if playerExpanded && activeRoom?.playerExpansion}
    <div class="player-expansion-slot">
      <svelte:component this={activeRoom.playerExpansion} {...expansionProps} />
    </div>
  {/if}
  <MiniPlayer />
</div>

Room-internal navigation

Rooms manage their own sub-view navigation. The shell does not provide a router. The expected pattern is a local navigation stack (see Navigation: shell-level vs room-internal for the full definition and examples).

Key rule: if a navigate call crosses a room boundary, it is shell-level navigation. If it stays within the room, it is room-internal. In v1 with only one room, shell-level navigate is a no-op stub — there is no other room to switch to.

Event subscriptions

The shell subscribes to all Tauri events and provides derived state to rooms through RoomContext handles. Rooms do not subscribe to Tauri events directly.

Events the shell subscribes to:

  • playback_state_changed → updates mini-player, propagates to active room
  • track_changed → updates queue display
  • queue_updated → updates queue display
  • search_result / search_done / search_error → provides to active room
  • plugin_status_changed → updates plugin status display
  • library_updated → refreshes library views
  • platform_ready → removes loading screen, enables navigation
  • playback_error → shows toast notification

References

  • ADR-009 (Room API TypeScript interface)
  • FR-ROOM-1, FR-ROOM-2, FR-ROOM-3 (functional.md)

Was this page helpful?