9.1 KiB
AGENTS.md
This file describes how to work safely in the Furumi repository. It applies to the entire project.
Project intent
Furumi is a cross-platform, federated P2P player for personal music libraries. Every node must remain a complete local player without network access. Other nodes add discovery, availability, trusted-device synchronization, and direct track transfer; they are never a prerequisite for using the local library.
Read ARCHITECTURE.md before changing federation, device sync, playback
handoff, persistence, or runtime boundaries.
Preserve these architectural invariants:
- Local import, browsing, playback, playlists, likes, and history work offline.
- No central Furumi service becomes required for discovery, playback, or synchronization.
- Federation membership does not imply trusted-device membership.
- Remote state is merged, replicated, or cached locally; it is not treated as an always-available database.
- Network, database, filesystem, image, and audio preparation work stays out of the interactive UI path.
- Losing peers may reduce remote availability but must not invalidate local state.
Toolchain and checks
The crate uses Rust edition 2024 and Rust 1.97 or newer.
Run the checks relevant to every code change:
cargo fmt --all -- --check
cargo check --all-targets
cargo test --all-targets
cargo clippy --all-targets
cargo clippy --all-targets currently reports known warnings. Do not hide
them with broad allow attributes. Do not claim -D warnings passes unless
the existing warning set has actually been resolved.
Use cargo fmt --all after editing Rust. Keep Cargo.lock committed and
update it only when dependencies change.
Source boundaries
src/main.rsowns process startup, terminal restoration, Tokio setup, and platform media-loop integration.src/app/state.rsowns UI/navigation state.src/app/action.rsandsrc/app/event.rsdefine input intent and runtime results.src/app/update.rsperforms state transitions and requests effects. It must not perform blocking I/O.src/app/mod.rsowns runtime orchestration and effect execution.src/library/owns SQLite library queries, models, imports, and migrations.src/player/owns rodio playback and audio analysis.src/federation/owns DHT discovery, peer catalogs, audio exchange, and federation caches.src/devices.rsowns trusted-device replication, membership, and playback coordination.src/ui/rendersAppState; rendering code must not start background work or mutate persistence.src/media.rsowns OS media controls.src/visualizer.rsandsrc/visualizations/own the Rhai visualization host and bundled scripts.
Prefer adding a focused module when a responsibility has a clear boundary. Do not split files solely to reduce line count if doing so introduces leaky APIs or circular ownership.
Keep large test suites in their existing adjacent files:
src/app/update_tests.rssrc/devices/tests.rssrc/federation/tests.rssrc/library/tests.rs
Application state and concurrency
AppState is the single source of truth for the TUI. The normal flow is:
external event -> AppEvent -> Action/update -> Effect -> runtime -> AppEvent
Follow these rules:
- Keep update logic deterministic wherever practical.
- Return an
Effectfor work that requires runtime services. - Report asynchronous results through
AppEvent. - Use
tokio::task::spawn_blockingfor blocking SQLite, hashing, tag parsing, or filesystem-heavy work invoked from async code. - Do not hold a mutex guard across
.await. - Do not call terminal rendering APIs from background tasks.
- Preserve event sequence or request identifiers where they prevent stale search, download, or synchronization results from overwriting newer state.
Library and SQLite
library::Library is the authority for local catalog persistence. Keep SQL in
the library/device persistence layers rather than spreading it through the UI
or runtime.
When changing schema or stored data:
- provide an upgrade path for existing databases;
- make migrations repeatable and safe on partially upgraded databases;
- preserve foreign-key and uniqueness invariants;
- distinguish durable user data from replaceable federation/cache data;
- test both the new behavior and migration-sensitive queries;
- never solve a schema problem by deleting or recreating a user's database.
Content identifiers are the bridge between independent libraries. Avoid assuming that local numeric row IDs identify the same track on another node.
Federation and wire protocols
Discovery and transfer are separate concerns:
- the DHT provides distributed discovery;
- peer catalog streams provide richer metadata;
- audio streams transfer content;
- the device-sync protocol replicates trusted personal state.
Treat protocol and serialized-data changes as compatibility changes:
- preserve existing ALPN values unless intentionally introducing a new protocol version;
- retain backward-compatible
serdedefaults for fields added to wire types; - tolerate peers with older or partial metadata;
- bound incoming messages and validate untrusted lengths/identifiers;
- keep catalog/federation authority separate from trusted-device authority;
- assume peers can disappear between discovery and transfer;
- keep retries and fallback sources idempotent.
Do not introduce a required coordinator, registry, account server, or canonical network database.
Trusted-device synchronization
Device sync is offline-first. Operations may arrive late, more than once, or in a different order.
When changing it:
- preserve operation-ID deduplication;
- use the existing hybrid logical time ordering rules consistently;
- ensure deletes survive offline replicas through tombstones;
- compact tombstones only after the required acknowledgements;
- keep snapshots able to repair peers that missed older operations;
- treat membership and revocation as replicated durable state;
- keep playback commands targeted and deduplicated;
- test merge behavior from at least two operation orders.
Do not replace eventual reconciliation with assumptions about a continuously connected leader.
Playback and remote resolution
The application owns the logical queue; player::Controller owns physical
audio playback.
Remote and local tracks should continue through the same queue and UI model. When a track is unavailable locally, resolve it asynchronously and replace or materialize the pending entry without blocking the event loop.
Preserve:
- queue position and prefetch indexes when inserting or removing tracks;
- shuffle/repeat behavior;
- pause and seek state during device handoff;
- fallback to another advertised source when a peer disappears;
- the distinction between cached audio and content imported into the durable library.
UI and key bindings
The TUI is keyboard-first and cross-platform.
- Keep rendering pure over
&AppState. - Use semantic
Actionvalues rather than checking raw keys inside views. - Add default bindings in
src/config/default_keymap.toml. - Preserve user overrides and context-specific bindings.
- Account for narrow terminals, Unicode display width, and empty/loading/error states.
- Restore raw mode, alternate screen, bracketed paste, and keyboard enhancements on exit and panic.
Do not print to stdout/stderr while the alternate-screen UI is active; use
tracing and visible application status instead.
Cross-platform work
Furumi supports Linux, macOS, and Windows.
- Keep OS-specific code behind narrow
cfgboundaries. - Do not introduce shell-only behavior into portable paths.
- Use platform data/config/cache directories through the existing config helpers.
- Consider path encoding, separators, and non-UTF-8 filesystem values.
- Changes to media controls, terminal setup, clipboard behavior, or audio devices require explicit review of all three platforms.
If only one target can be exercised locally, state which platform-specific paths remain unverified.
Tests
Add tests at the closest stable boundary:
- pure state transitions in
app/update_tests.rs; - SQLite behavior and migrations in
library/tests.rs; - operation merge, tombstone, membership, and playback sync behavior in
devices/tests.rs; - search/ranking/federation conversion in
federation/tests.rs; - protocol-specific tests beside
federation/audio.rsorfederation/catalog.rswhen appropriate.
Prefer in-memory SQLite databases and deterministic fixtures. Temporary files must use unique names and must not depend on a developer's music library, configuration, home directory, network peers, or audio hardware.
Do not remove a test merely because a refactor makes it inconvenient. Update it to assert the preserved behavior.
Documentation and releases
Keep public documentation aligned with the decentralized product model.
README content should explain user value and setup; ARCHITECTURE.md should
explain architectural decisions and invariants rather than restating source
code.
Release archives are produced by .github/workflows/release.yml and must
include the binary, README.md, and LICENSE.
The project is licensed under WTFPL version 2.