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

Functional requirements — Rymflux v1

Derived from the charter vision, scope, and platform philosophy. Each FR traces to a test case and user story.

Functional requirements — Rymflux v1

Plugin system

FR-PLUGIN-1: Plugin discovery and registration

Related story Register a content-source plugin
Priority Must

The platform shall scan a well-known plugins directory at startup and register every valid plugin whose manifest parses successfully. Invalid manifests shall be logged and skipped without crashing the platform.

Acceptance criteria:

Scenario: Valid plugin discovered at startup
  Given a directory "~/.rymflux/plugins/" exists
  And a subdirectory "spotify" contains a valid plugin.toml and an executable entry point
  When the platform starts
  Then the plugin is registered with id="spotify" and its declared capabilities are available

Scenario: Invalid manifest skipped
  Given a subdirectory "broken" contains a malformed plugin.toml
  When the platform starts
  Then the broken plugin is not registered
  And an error is logged with the path and parse failure reason
  And the platform continues startup without crashing

Scenario: Plugin directory does not exist
  Given no plugins directory exists
  When the platform starts
  Then the platform starts with zero plugins registered
  And no error is surfaced to the user

FR-PLUGIN-2: Plugin manifest format

Related story Define a plugin’s identity and capabilities
Priority Must

The platform shall accept a plugin.toml manifest with the following required fields and reject manifests that omit any of them.

Field Type Required Description
id string yes Unique plugin identifier (reverse domain or slug)
name string yes Human-readable name
version semver yes Plugin version
entry string yes Path to executable (relative to manifest directory)
capabilities array yes List of supported operations

Supported capability values: search, get_track, get_album, get_artist, stream. (get_lyrics is reserved for v2.)

Scenario: Valid manifest accepted
  Given a plugin.toml contains id, name, version, entry, and at least one capability
  When the platform parses the manifest
  Then registration succeeds

Scenario: Missing required field
  Given a plugin.toml is missing the "id" field
  When the platform parses the manifest
  Then the manifest is rejected
  And the error message includes the missing field name

Scenario: Unknown capability declared
  Given a plugin.toml declares capability "teleport"
  When the platform parses the manifest
  Then the unknown capability is ignored
  And a warning is logged

FR-PLUGIN-3: Plugin lifecycle

Related story Run a plugin when needed
Priority Must

The platform shall manage plugin processes through a defined lifecycle: launch on first request, keep alive for a configurable idle timeout, terminate on request, and recover from crashes.

Scenario: Plugin launched on first request
  Given a registered plugin with entry "./spotify"
  And the plugin process is not running
  When the platform sends a "search" request
  Then the plugin executable is launched
  And the request is delivered within 5 seconds of launch

Scenario: Plugin crash during request
  Given a plugin process is running
  When the plugin process exits unexpectedly before responding
  Then the platform returns a PLUGIN_CRASHED error to the caller
  And the plugin is marked for restart on the next request
  And the core platform continues without crashing

Scenario: Plugin timeout
  Given a plugin does not respond within the configured timeout (default 30s)
  When the timeout expires
  Then the platform kills the plugin process
  And returns a PLUGIN_TIMEOUT error to the caller

Scenario: Idle plugin terminated
  Given a plugin has no requests for the configured idle timeout (default 5 min)
  When the idle timeout expires
  Then the plugin process is terminated gracefully
  And the plugin remains registered

FR-PLUGIN-4: Plugin search capability

Related story Search content from a plugin
Priority Must

A plugin that declares the search capability shall accept a search query string and return a list of matching tracks, albums, and artists in a standardized JSON format.

Scenario: Plugin returns search results
  Given a plugin with capability "search"
  When the platform sends a search request with query "Imagine Dragons"
  Then the plugin returns a JSON response containing
    | Field     | Type   | Description           |
    |-----------|--------|-----------------------|
    | id        | string | Plugin-scoped content ID |
    | title     | string | Track/album/artist name  |
    | type      | string | "track" | "album" | "artist" |
    | artist    | string | Artist name            |
    | duration  | int    | Duration in seconds (tracks only) |
  And the response is returned within the configured timeout

Scenario: Plugin returns empty results
  Given a plugin with capability "search"
  When the platform sends a search request with query "zzzznotexist"
  Then the plugin returns an empty results array
  And no error is raised

FR-PLUGIN-5: Plugin streaming capability

Related story Stream audio from a plugin
Priority Must

A plugin that declares the stream capability shall accept a content ID and return audio data either as a stream URL or raw bytes over the IPC channel, as declared in its manifest.

Scenario: Plugin returns stream URL
  Given a plugin with capability "stream" and streaming_mode = "url"
  When the platform sends a stream request with content_id "123"
  Then the plugin returns a JSON object containing
    | Field       | Type   | Description            |
    |-------------|--------|------------------------|
    | url         | string | Direct audio stream URL |
    | content_type| string | MIME type of the audio  |
  And the URL is accessible by the playback engine

Scenario: Plugin returns audio bytes (**v2** — pipe mode deferred, see ADR-006)
  Given a plugin with capability "stream" and streaming_mode = "pipe"
  When the platform sends a stream request with content_id "123"
  Then the plugin writes audio bytes to the IPC channel
  And the stream includes content_type metadata
  And the stream ends when the audio is complete or on an explicit stop signal

FR-PLUGIN-6: Plugin metadata capability

Related story Get track, album, or artist details
Priority Must

A plugin that declares metadata capabilities shall return detailed information for a given content ID.

Scenario: Plugin returns track details
  Given a plugin with capability "get_track"
  When the platform sends a get_track request with id "123"
  Then the plugin returns a JSON object containing
    | Field       | Type   |
    |-------------|--------|
    | id          | string |
    | title       | string |
    | artist      | string |
    | album       | string |
    | duration    | int    |
    | cover_art   | string (URL, optional) |
    | streamable  | bool   |

Scenario: Plugin returns 404 for unknown content
  Given a plugin with capability "get_track"
  When the platform sends a get_track request with id "nonexistent"
  Then the plugin returns error code CONTENT_NOT_FOUND

Room framework

FR-ROOM-1: Room registration and lifecycle

Related story Register a room experience
Priority Must

The room framework shall accept room registrations and manage their lifecycle: activate (room becomes visible and interactive), deactivate (room hidden, state preserved), suspend (resources reclaimed, state preserved).

Scenario: Room registers successfully
  Given a room declares itself with a unique id and display name
  When the room framework processes the registration
  Then the room appears in the shell's room list
  And the room is not active until the user enters it

Scenario: User enters a room
  Given a registered room with id "music"
  When the user navigates to the music room
  Then the room receives an "activate" event
  And the room's home screen is displayed
  And the previous room receives a "deactivate" event (state preserved)

Scenario: Duplicate room ID rejected
  Given a room with id "music" is already registered
  When a second room attempts to register with id "music"
  Then the second registration is rejected
  And an error is logged

FR-ROOM-2: Room search integration

Related story Search content within a room context
Priority Should

A room may declare search filters that narrow the set of plugins queried and the result fields displayed. The search manager respects these filters when processing queries from that room.

Scenario: Music Room search filters by type
  Given the Music Room is active
  And the Music Room declares filters: types=["track", "album", "artist"]
  When the user searches for "Imagine Dragons"
  Then the search manager queries all plugins with capability "search"
  And results are filtered to types track, album, and artist only

Scenario: Room with no search filters
  Given a room that does not declare search filters
  When the user searches
  Then all searchable plugins are queried
  And all result types are returned

FR-ROOM-3: Room player expansion

Related story Customize the player for a room
Priority Should

A room may register a player expansion view that is displayed when the user expands the mini-player. The expansion view receives the currently playing content metadata.

Scenario: Music Room player expansion
  Given the Music Room is active
  And a track is playing
  When the user expands the mini-player
  Then the Music Room's expansion view is displayed
  And the view receives the current track's metadata (title, artist, album, duration, cover_art)
  And the mini-player controls (play/pause, next, previous, seek, volume) remain visible

Scenario: Room without expansion
  Given a room that does not register a player expansion view
  When the user expands the mini-player
  Then a default view is shown with basic metadata and controls

Core platform services

FR-CORE-1: Source-agnostic playback engine

Related story Play audio from any source
Priority Must

The playback engine shall accept an audio source as a URL or file path and play it. The engine shall have no awareness of plugins, rooms, or content types.

Scenario: Play audio from URL
  Given the playback engine receives a stream URL "https://example.com/audio.mp3"
  When the user presses play
  Then audio playback starts within 2 seconds
  And the engine emits a "playing" event with the source URL

Scenario: Play audio from file path
  Given the playback engine receives a file path "/home/user/music/track.flac"
  When the user presses play
  Then audio playback starts within 2 seconds

Scenario: Engine ignores source provenance
  Given the playback engine receives a source
  When queried for the source's origin
  Then the engine returns no plugin or room metadata
  And returns only the source URL or file path

Scenario: Unsupported format rejected
  Given the playback engine receives a source with unsupported format
  When playback is attempted
  Then the engine returns error UNSUPPORTED_FORMAT
  And does not crash

FR-CORE-2: Queue management

Related story Build and manage a playback queue
Priority Must

The queue shall maintain an ordered list of content items that persists across room switches. Items are referenced by their stable content ID.

Scenario: Add item to empty queue
  Given the queue is empty
  When a content item is added to the queue
  Then the queue contains one item
  And the item is set as the next to play

Scenario: Reorder items in queue
  Given the queue contains items [A, B, C]
  When item C is moved to position 1
  Then the queue order is [C, A, B]

Scenario: Queue persists across room switches
  Given the queue contains [A, B, C]
  And the user is in the Music Room
  When the user switches to another room
  Then the queue still contains [A, B, C]
  And playback continues uninterrupted

Scenario: Remove item from queue
  Given the queue contains [A, B, C]
  When item B is removed
  Then the queue contains [A, C]
  And playback of the current item (if B is playing) advances to the next item

FR-CORE-3: Unified library

Related story Save content to library
Priority Must

The library shall store content items using opaque, source-independent UUIDs. A separate resolution table shall map each content UUID to the source plugin and foreign ID for playback. The library shall support favorites, playlists, and playback history.

Scenario: Save content to favorites
  Given a content item with UUID "abc-123" exists in search results
  When the user saves it to favorites
  Then the library stores the content UUID as the track's identity
  And the library stores plugin_id as a resolution hint, not as content identity
  And two saves of the same track via different plugins share one UUID
  And the item appears in the user's favorites list

Scenario: Play back saved content
  Given a content item with UUID "abc-123" is in the library
  When the user plays it
  Then the library resolves the content UUID to (plugin_id, foreign_id) via the mapping table
  And the resolved plugin's stream capability is called

Scenario: Add content to playlist
  Given a library item with UUID "abc-123" exists
  When the user adds it to a playlist named "Favorites 2026"
  Then the playlist contains the content UUID
  And the original library item is unchanged

Scenario: View playback history
  Given the user has played content items [A, B, C]
  When the user views history
  Then history shows [C, B, A] (most recent first)
  With timestamps for each play

FR-CORE-4: Parallel search manager

Related story Search across all plugins
Priority Must

The search manager shall dispatch a search query to all registered plugins that declare the search capability, collect results with a configurable per-plugin timeout, merge them, and return a unified result set.

Scenario: Parallel search across two plugins
  Given two registered plugins with capability "search": PluginA and PluginB
  When the user searches for "Imagine Dragons"
  Then both plugins receive the search query
  And results from both are returned

Scenario: Slow plugin does not block results
  Given PluginA responds to search in 100ms
  And PluginB takes longer than the configured timeout (30s)
  When the user searches
  Then PluginA's results are displayed after 100ms
  And PluginB's results are either displayed when they arrive or omitted on timeout
  And a notification "PluginB did not respond" is shown

Scenario: Search results deduplicated in memory
  Given PluginA returns track with title "Believer" and foreign_id "plA-1"
  And PluginB returns track with title "Believer" and foreign_id "plB-1"
  When the search manager merges results
  Then the results list contains one entry for "Believer"
  And the entry carries both (pluginA, plA-1) and (pluginB, plB-1) as candidate sources
  And no database writes occur

Scenario: Library save creates resolution records
  Given a deduplicated search result for "Believer" with two candidate sources
  When the user saves "Believer" to their library
  Then library_items contains one row for "Believer" with a single UUID
  And content_resolution contains two rows: (UUID, pluginA, plA-1) and (UUID, pluginB, plB-1)

Scenario: No plugins registered
  Given zero plugins with capability "search" are registered
  When the user searches
  Then the search manager returns an empty result
  And a message "No search plugins installed" is shown

Identity system

FR-ID-1: Stable content UUIDs

Related story Identify content independently of source
Priority Must

Every content item stored in the library, queue, playlist, or history shall be identified by a UUID that does not encode the source plugin. The platform shall maintain a content resolution table that maps content UUIDs to one or more (plugin_id, foreign_id) tuples.

Scenario: Content UUID generated on first save
  Given a content item returned by a plugin
  When the item is added to the library
  Then a UUID is assigned as the content ID
  And the mapping (plugin_id, foreign_id, content_uuid) is stored in the resolution table

Scenario: Same content from two plugins
  Given PluginA returns a track identified as "plA-1"
  And PluginB returns the same track identified as "plB-1"
  When both are added to the library
  Then they share the same content UUID (deduplication)
  And the resolution table has two entries: (plA-1 → uuid) and (plB-1 → uuid)

Initial rooms

FR-MUSIC-1: Music Room browsing

Related story Browse music content
Priority Must

The Music Room shall provide a browse view organized by album, artist, and genre. Content is sourced from all registered plugins that support music-type content.

Scenario: Browse albums
  Given at least one plugin with capability "search" is registered
  When the user opens the Music Room and selects "Albums"
  Then the view displays album covers and titles
  And selecting an album shows its track list

Scenario: Browse by genre
  Given content with genre metadata exists across plugins
  When the user browses by a specific genre
  Then only content matching that genre is shown
  And results are drawn from all plugins

Scenario: No content available
  Given zero plugins are registered
  When the user opens the Music Room
  Then the view shows "No music sources installed"
  And a link to the plugin setup guide

FR-MUSIC-2: Music Room player expansion

Related story Expanded player in Music Room
Priority Must

When the mini-player is expanded in the Music Room, the expansion view shall show album artwork, now-playing metadata, and the playback queue.

Scenario: Player expansion shows artwork and metadata
  Given a track is playing in the Music Room
  When the user expands the mini-player
  Then the expanded view shows album artwork, track title, artist name
  And the queue is accessible below the player controls

FR-HOME-1: Home (Lobby)

Related story Platform home screen
Priority Must

The Home view shall display: continue listening (most recent unfinished content), recently played, pinned rooms, and recent searches.

Scenario: Home shows continue listening
  Given the user has partially played a track
  When the user returns to Home
  Then "Continue Listening" shows the partially played track with progress

Scenario: Home shows pinned rooms
  Given the user has pinned the Music Room
  When the user views Home
  Then the Music Room appears in pinned rooms
  And clicking it activates the Music Room

Scenario: Empty state on first launch
  Given the user has never played any content
  When Home is displayed
  Then "Continue Listening" section is hidden
  And "Getting Started" guide is shown with plugin installation instructions

Reference plugin

FR-REF-1: Local-files reference plugin

Related story Use Rymflux with local audio files
Priority Must

A local-files plugin shall ship with Rymflux. It shall scan configured directories for audio files, index them, and make them available through search, get_track, get_album, get_artist, and stream capabilities.

Scenario: Plugin indexes music directory
  Given a configured music directory containing .mp3, .flac, and .ogg files with basic ID3 tags
  When the local-files plugin starts
  Then the plugin indexes all supported audio files
  And search returns tracks organized by album and artist extracted from metadata

Scenario: Stream local file
  Given the local-files plugin is registered
  When the playback engine requests stream for a known local track
  Then the plugin returns a file:// URL pointing to the audio file on disk

Scenario: Plugin handles directory with no audio files
  Given an empty configured music directory
  When the local-files plugin starts
  Then the plugin registers successfully
  And search returns empty results with no error

Traceability

FR User story file Priority Depends on
FR-PLUGIN-1 register-plugin.md Must
FR-PLUGIN-2 register-plugin.md Must
FR-PLUGIN-3 register-plugin.md Must FR-PLUGIN-1, FR-PLUGIN-2
FR-PLUGIN-4 search-across-sources.md Must FR-PLUGIN-3
FR-PLUGIN-5 stream-from-plugin.md Must FR-PLUGIN-3
FR-PLUGIN-6 stream-from-plugin.md Must FR-PLUGIN-3
FR-ROOM-1 register-room.md Must
FR-ROOM-2 register-room.md Should FR-ROOM-1, FR-CORE-4
FR-ROOM-3 register-room.md Should FR-ROOM-1, FR-CORE-1
FR-CORE-1 play-music-from-any-source.md Must FR-PLUGIN-5
FR-CORE-2 play-music-from-any-source.md Must FR-CORE-1
FR-CORE-3 save-to-library.md Must FR-PLUGIN-4, FR-ID-1
FR-CORE-4 search-across-sources.md Must FR-PLUGIN-4
FR-ID-1 save-to-library.md Must
FR-MUSIC-1 play-music-from-any-source.md Must FR-ROOM-1, FR-CORE-4
FR-MUSIC-2 play-music-from-any-source.md Must FR-ROOM-3, FR-CORE-1
FR-HOME-1 play-music-from-any-source.md Must FR-ROOM-1
FR-REF-1 register-plugin.md Must FR-PLUGIN-1 through FR-PLUGIN-5

Was this page helpful?