home notes

Stellar

Stellar is a local-first music player for desktop and mobile.

It’s written in Rust and Kotlin and uses Compose Multiplatform. The code is on GitHub.

This is an incomplete collection of design notes. More polished writing is also published on fractal.beauty:

Coordinating sync 2026-07-05

A user has multiple devices that need to sync. Each device knows and connects to all the other devices. When a device comes online, it needs to sync to get the latest version of the graph and share its offline changes, then peers broadcast and apply updates incrementally.

By syncing, two peers exchange the set difference of their metadata graphs. Then both peers update their graphs, keeping the latest version of each entity. In theory, it’s safe for a peer to sync with multiple peers concurrently, but it might be wasteful because B and C are already synced and send the same difference to A twice. So, I think it’s simplest for each peer to only sync with one peer at a time. Also, sync should be fast, and other peers should already be up to date when coming online, so the order shouldn’t matter too much.

We could either prioritize syncing with peers who we haven’t synced with, or with peers we have synced it.

So, we have something that limits us to one concurrent sync, and when we’re not syncing, we start a sync with a connected peer that hasn’t synced yet (either MRS or LRS, or random/unspecified is fine I guess). It’s the responsibility of the client end to initiate, since only one side needs to and the client side likely just came online. If a peer disconnects and reconnects, the pair should sync again, since they may have missed incremental updates. That seems like it’ll work.

It might be nice to model/evaluate this a little more rigorously…

Library separation 2026-07-05

I think we should tag the graph with a unique ID shared across devices. This ensures that if you accidentally connect to a device that has another user’s library, they aren’t automatically synced together; the user needs to manually merge them. This seems especially important because schemas might not match between libraries.

This might also lend to account switching on a single device, but a running instance of the app only needs to support one graph at a time.

When do we generate this tag? When you launch the app for the first time, it should generate an ID. But what about the second device? Does it also immediately generate an ID, then you need to manually combine them when they connect and the library IDs don’t match? If the second device’s library is empty, it could merge automatically. The alternative is asking the user if they whether they want to start a library or join an existing library, but if we can make it work without that, that’s probably better UX, especially since single-device is a supported configuration.

When we merge two graphs, do the core tasks need to restart? It seems like it would get messy. We probably need the library ID to be mutable, but then we need to handle the library ID changing mid-sync and so on…

Worked example:

Automatic case:

Interactive case: