Internal · SpaceMusic Engineering
The Cloud-Only Library
How browsing, picking, and flipping will work once the operator's disk stops being a library — and why that makes the system both safer and able to run with the internet unplugged.
Why a file picker became a security question
A share link to a SpaceMusic show is meant to be handed out freely: scan the QR code, nudge a parameter, watch the textures move. It is public by design. The library is not.
The moment the web Pro UI grew a file browser, those two facts collided. A public, link-gated control surface could suddenly page through the operator's private files. Nobody decided that on purpose — it fell out of putting a library picker into a surface that anyone with a link can open. This document is about how we resolve the collision. The interesting part is that the fix is not a lock on the browser; it is a change to what "the library" is. Once that lands, the leak closes by construction, the system gains the ability to run offline, the installer gets leaner, and multi-user browsing starts working — all from one move.
The library today, and why the disk leaks
Plan 049 gave the browser two libraries side by side. One is a per-user cloud library: objects in MinIO, reached through the authenticated /api, with every call confined to the caller's own prefix so users can't see each other's files. That half is sound. The other is a local library: an arbitrary folder on the show machine's disk, listed for viewers over the LAN and — in a WAN session — over the Centrifugo relay. That half is the problem child.
The relay is identity-less by design. The core never learns who is connected; the share link carries a session token, not an account. That is a deliberate, load-bearing property — it's what lets a QR code work without a login. But it means "only show the local library to logged-in users" is unenforceable where it matters. You can hide the Local tab in the UI all you like; a holder of the session token can publish on the listing request channel directly and get the folder contents back anyway. The file names leak to anyone with the link.
So the choice was never "how do we gate the local library." It was: we can't gate it without giving the relay an identity it was designed not to have. We had to remove it. The question became what to put in its place.
Two libraries, no disk
The reframe is to delete the arbitrary-disk library entirely. Everything an operator can load lives in the cloud and is synced to a local cache. Two libraries remain, and both are cloud-backed:
- Core library. One global, read-only starter set — gradients, default clips, primitives — curated by an admin. The truly-basic essentials ship bundled in the installer (so a fresh machine has something before it ever sees the internet); the rich set syncs down from the cloud. It eventually generalizes to installable packs. shared · read-only · same for everyone
-
User library. The per-user cloud library we already have —
userlibs/{uuid}, prefix-isolated — fully editable by its owner. per-user · isolated · editable
The "Local" tab disappears. The browser shows Core and Your Library, both backed by the cloud, both served instantly from a cache that lives on the Host. There is no longer any way to browse the show machine's raw disk — which is exactly why the leak is gone, not gated.
Figure 1 · Two cloud libraries, one local cache Open full size · print A3 landscape ↗
Read Figure 1 top to bottom. The two libraries live in MinIO; storage-api brokers access — strict per-user isolation for user libraries, shared-read for the core. CloudMirror pulls the core and the operator's own library into a local cache on the Host (the accent box), seeded first by the bundled essentials. From there the engine's FileLoaders read off local disk. The two client types matter: a WAN browser reaches the api directly with its own login; a LAN viewer reads the cache and never touches the internet at all. That last point deserves its own section, because it's where "everything is cloud" stops meaning "everything needs a connection."
The cache is the library
The word "cloud" makes people reach for "needs internet." Here it doesn't, and the reason is the cache. CloudMirror keeps the core library and the operator's user library synced into a local folder; the Host serves that folder to viewers. On the LAN, browse, load, and flip all read the cache — zero network calls, no /api. The /api path with its login exists only for the WAN case, where you're remote and online anyway by definition.
This is why the design survives the studio with no internet. A folder of gradients you reached for last week is already on disk; the engine loads it whether the building's uplink is up or not. The only thing that genuinely needs a connection is refreshing the cache and the very first seed of the rich core library. And even the first-seed gap is covered: a small essentials pack ships in the installer, so a machine that has never once been online still has the basics. The full library arrives on the first online run and then stays. During-show offline — the case we actually hit at venues — is fully covered by what's already on disk.
"Cloud-backed" is a statement about where the source of truth lives. "Offline" is a statement about where the bytes are when you need them. The cache is how both are true at once.
Fetch-on-flip
Over the LAN the cache makes loading trivial. Over the WAN it's the interesting part, because we don't want a remote pick to drag an entire folder of video across the wire just so you can play one clip. The mechanism that avoids that is fetch-on-flip, and it leans on a piece the engine already publishes.
A FileLoader is folder-backed. When you set it to a file, the engine enumerates that file's whole folder and publishes the list as *FolderContents — the dropdown every client sees. Changing the selection is just writing *File.Value to another entry; that's a parameter write, and any connected client can do it. The trick is making that work when the folder lives in someone's cloud library and the Host doesn't have the bytes.
Figure 2 · Pick once, fetch each clip on demand Open full size · print A3 landscape ↗
On a pick, the browser sends the Host a manifest: every file in the folder, by name, each with a presigned GET URL. That's metadata — kilobytes — so it's instant. The Host downloads only the file you actually picked, then publishes *FolderContents from the manifest's names, so the dropdown is complete and correct immediately, not a folder-of-one that grows. From then on, anyone flipping to another clip — including a not-logged-in QR joiner — triggers the Host to fetch that clip on demand from its manifest URL and load it. Bytes only ever move for files someone plays, the first touch of each is a brief spinner, and every flip after that is instant off the cache. A QR joiner can do this without any credential of their own: they're riding the presigned URLs the logged-in picker already brokered, and the manifest lives on the Host, so it keeps working even if that picker closes their tab. When a session runs long enough to outlast the URLs' lifetime, the browser quietly re-sends a fresh manifest.
Getting files in
Picking covers getting a file out of the library and onto the show. The other half — the one that's easy to forget until you're staring at a folder of images you found on a server somewhere — is getting a file in. The answer is deliberately small: you add it to Your Library, and you never add it anywhere else.
Logged in, with Your Library open, you drag the files onto the browser, or use the Upload button. They stream straight to MinIO by a presigned PUT — the bytes don't pass through the api server, they go directly to object storage, so an upload isn't bounded by some proxy's request limit. The instant they land, CloudMirror pulls them down into the Host's cache, exactly like everything else in the library. So "add these images" and "these images are sitting on the show machine, ready to load, with the internet unplugged" are the same gesture, one sync apart. There is no separate publish or deploy step — the library is the upload target and the load source at once.
The Core library takes no part in this. It's curated, read-only, the same for everyone — so while you're on the Core tab the browser shows no ingest affordance at all: no drop zone, no Upload button, no New Folder. Upload, drag-and-drop, and the management actions live only on Your Library. (The backend agrees — the api refuses non-admin writes to the core space regardless — but the point is the gesture should never even be offered there.) Which library an upload lands in follows the same identity rule as browsing: on the LAN it's the operator's library, on the WAN it's your own.
Today that gesture is an explicit drag onto the browser. The way it wants to grow is the two-way sync — a watched local folder the Host quietly mirrors up to your user library, so dropping a file into it on your desktop is enough and the upload becomes ambient. That's deferred, but it's the same pipe pointed the other way: the cache that pulls the library down also becomes the thing that pushes your additions up.
Who can do what, and from where
Two boundaries fall out of this design, and neither is a knob we get to turn — both are forced by the architecture, which is the comfortable kind of constraint.
The first is browse versus flip. Flipping a selection within an already-set folder is a parameter write against a folder a logged-in user already chose; it's open to anyone with the link, QR joiner included. Browsing — opening the picker, seeing Core and Your Library, choosing a new folder or file — is logged-in only. That single line keeps the system to one access mode and needs no public read surface on the api: a guest never browses, they only flip what someone with an account has set.
Figure 3 · The boundary, and where identity comes from Open full size · print A3 landscape ↗
The second boundary, in the strip along the bottom of Figure 3, is whose library you see — and it's forced by where the page is served from, not by a preference. A LAN viewer loads from the Host at a LAN IP, an origin that carries no .spacemusic.tv cookie, and its /api is proxied with the Host's own login. So on the LAN you get the operator's library plus core, from the cache, offline. A WAN viewer at ui.spacemusic.tv carries its own Authentik cookie and reaches its own /api, so each remote person sees their own library plus core. Fabian in Berlin browsing his files and the operator at the desk browsing theirs is the same code path resolving against two different identities — and a co-located guest who genuinely needs their own cloud library just opens the WAN URL like a remote user would.
Login itself stays deliberately plain. The show runs on the Host, so a viewer reloading costs nothing real; "sign in" is a redirect to Authentik and back to the exact session URL. We fold the active page into that URL so the round-trip lands you back where you were — the same deep-linking that makes a shared link reopen on the right page does double duty as the thing that makes login feel seamless.
Why this matters
Step back to the collision we opened with: a public control link that could read the operator's private files. The instinct was to gate the browser. The better move was to notice that the only reason a public link could see those files is that the files were on a disk the relay served blind — and to make that disk stop being a library.
One reframe does four things at once. The leak closes by construction, because there's no arbitrary-disk surface left to expose. The system runs offline, because the cache is the real library on the LAN and the bundled essentials cover the cold start. The installer stays lean, because the rich library lives in the cloud and arrives on first run. And multi-user browsing simply works, because each person's identity resolves to their own library without any special-casing. The thing we give up is "browse the show PC's raw disk over the internet" — which was never safe, and which an upload into your user library replaces cleanly.
What this asks of us next is real but bounded: a shared-read core library in the api, the folder-manifest fetch path on the Host, an SSO-gated /api on the public origin, and a browser that shows Core and Your Library instead of Local and Cloud. Each is a contained piece, and none of it fights the rest — which is the tell that the model underneath is the right one.
Glossary
Terms used in this document, in plain language.
- Core library
- One global, read-only set of default assets (gradients, clips, primitives), the same for everyone. Bundled essentials ship in the installer; the rich set syncs from the cloud.
- User library
- A person's own cloud library in MinIO (
userlibs/{uuid}), isolated from everyone else's and editable only by its owner. - Cache / mirror
- A local folder on the Host that
CloudMirrorkeeps in sync with the core library and the operator's user library. On the LAN it is the library — read directly, no internet. - Relay
- The Centrifugo channel layer that carries a WAN session. It's identity-less by design: it knows a session token, not who you are — which is why the local-disk library couldn't be gated on it.
- *FolderContents
- A channel the engine publishes per FileLoader: the list of files in the current selection's folder. It's what fills the flippable dropdown every client sees.
- *File.Value
- The channel holding a FileLoader's current file. Writing it is "flipping" — a parameter write any connected client can make.
- Manifest
- On a remote pick, the list the browser hands the Host: every file in the folder by name, each with a presigned URL. Metadata only, so it's instant.
- Fetch-on-flip
- Materializing a folder's files one at a time, on demand, the first time someone flips to each — instead of downloading the whole folder up front.
- Presigned URL
- A short-lived, signed link that grants direct access to one MinIO object without a login — a GET for downloads (fetch-on-flip), a PUT for uploads (adding to Your Library). The api mints them; the bytes flow straight to or from object storage.
- QR joiner
- Someone who scanned the share link but isn't logged in. They can control the show and flip set folders, but not browse.
- Authentik
- The single sign-on provider. A logged-in browser carries a cookie on
.spacemusic.tv, which is what authenticates the WAN/api. - LAN / WAN
- Local network (same building as the Host, served over its LAN IP) versus wide-area (remote, over
ui.spacemusic.tvand the relay). - MinIO
- The S3-compatible object store behind
storage-apiwhere all library bytes actually live.
Settled · grilled 2026-06-24
The model
Two cloud libraries (Core + User), no local-disk library. The cache is the offline truth on the LAN. Fetch-on-flip via a manifest. Browse = login, flip = open. LAN = operator, WAN = per-user.
Next · build
The four pieces
- Shared-read
corelibapi + essentials pack - Folder manifest + fetch-on-flip on the Host
- SSO-gated
/apionui.spacemusic.tv - Core + Your Library tabs; retire the disk subsystem
Later · deferred
Not yet
Installable packs, two-way sync (drag a local file into your library), pre-load folder, popup login, per-team core libraries.