**Note** from Bead: Snow Globe Harvard Spheres · [canonical source](https://redfish.acequia.io/guerin/.agents/6a8f0a99-c255-4a5c-9c4a-9097d0ee9cd8/2026-06-11/notes/03-controller-flow-audit.md) · session 2026-06-11 · discussion: Talk: Snow Globe Harvard Spheres
Stephen flagged: "gallery and playlist had some weird flow patterns and may have been buggy too." Read-through of `sphere-controller.html` and `sphere-admin.html` (local dev copies, Apr 2026 — assumed current with the redfish deploy). **Confirmed bugs first, then flow weirdness.** Audit only — nothing fixed.
## Confirmed bugs 1. **Approved items stay invisible in the picker (`status:'pending'` never cleared).** Controller submits proposed items with `status:"pending"`. Admin's `handleApproveItem` PUTs the JSON into `playlists/items/` **without removing the status field**. The controller's picker filters `it.status !== 'pending'` — so any item that came through the visitor proposal flow is *permanently excluded* from the gallery/picker even after approval. This is very likely the "gallery seemed buggy" symptom. 2. **Approve never moves uploaded media (`src` vs `url` schema mismatch).** Controller writes uploaded items with `url: <absolute …/media/proposed/<id>.<ext>>` and no `src` field. Admin's approve checks `d.src && d.src.startsWith('media/proposed/')` — never true → media is never promoted out of `media/proposed/`; approved items reference `media/proposed/` forever. And were it ever true, it would set `d.src = 'media/…'` (a relative path) while the viewer only ever reads `item.url`. Dead code both ways. 3. **Picker contradicts its own intent for fresh uploads.** After a visitor uploads a new item, the code comments "Add to local allItems so it appears in picker immediately" — but the same pending-status filter (bug 1) hides it. A visitor who uploads content *for their playlist proposal* cannot add it to the proposal. Flow dead-end. 4. **Mixed relative/absolute fetches in `populatePlaylistSelect` / `onPlaylistSelectChange`.** They call `listJsonFiles('playlists/approved/')` and `loadJSON('playlists/active/current.json')` with *relative* paths (controller's `loadJSON` does not prefix `DATA`, unlike admin's which prefixes `BASE`). Works only when the page is served from the canonical directory; on the `harvardviz.live/sphere/` copies the PROPFIND hits static cPanel and fails silently → empty playlist dropdown. Everything else in the file uses absolute `DATA`. 5. **`loadActive` failure silently skips `populatePlaylistSelect`** (both inside one `try{}catch(e){}`), so a missing/corrupt `current.json` also empties the dropdown with no error surfaced.
## Flow weirdness (works, but the pattern is off) 6. **Browse vs. control conflated.** The "Now Playing" playlist dropdown *changes what plays on the physical sphere* (PUTs `active/current.json` + broadcasts) — duplicating admin's "Set Active" with no distinction, on a tab that reads like a local browser. Clicking a playlist row likewise instantly seeks the sphere. There is no "view this playlist without driving the display" mode — and no companion/control separation (→ [02-companion-controller-protocol.md](02-companion-controller-protocol.md)). 7. **`currentIndex` is never persisted.** Viewer auto-advance only patches group state; `active/current.json` keeps the index from whenever it was last PUT. A controller joining an empty group adopts a stale index until the next state patch. 8. **Rotation drift between integrators.** Viewer and controller each integrate `xRotation += xRotationVelocity·dt` locally; the controller never republishes its integrated rotation. Under nonzero velocity the two (and any second controller) drift apart, then a drag publishes the *controller's* drifted rotation — visible snap on the sphere. 9. **Reject leaks media.** Rejecting a proposed item deletes only the JSON; the uploaded file in `media/proposed/` and the thumbnail (written directly into the shared live `media/thumbnails/` *before* approval) are orphaned. 10. **GET+PUT+DELETE where WebDAV MOVE exists.** All admin promote/approve operations re-transfer content client-side instead of one atomic server-side `MOVE` — non-atomic (a failure between PUT and DELETE duplicates or loses state) and wasteful for big media. 11. **Preview limitations.** Iframe items can't be previewed (placeholder card); closing preview resets the controller globe to the default map rather than the actually-playing item; the globe tab never shows what's really on the sphere.
## Carry-forward into the snow-globe work - New item JSON written by *any* new tooling must not perpetuate the `status` / `src`-vs-`url` mismatches; treat **`url` as the one content pointer** and **strip `status` on approval** (or better: location-in-tree *is* the status, drop the field). - A snow-globe item is `type:"iframe"` → currently unpreviewable in the controller (weirdness 11). The companion-controller protocol gives it a real preview/control surface instead. - Any fix pass should use WebDAV `MOVE` for the promote flows (matches the workspace "think in WebDAV first" discipline).