## What a person sees Since #4, the Fleet page shows subscription move reports, but the Observatory still said moves weren't set up. Its Subscriptions panel says "Move history is shown in the Fleet page when installed", while two other places said the opposite: - The **"Not set up yet" panel** said "The oversight team's readings and subscription moves are not connected." - The **starter agent's limits** said "Productivity, the oversight team's readings and subscription moves are not set up yet." Both now say "Move history is shown in the Fleet page when installed." Productivity and the oversight team's readings are still listed as not connected, because they aren't. ## The change Two commits, 5 files, +38/−4: - `5b56136`, tests only. A seat with no usage windows sampled reads "no usage windows sampled", and a seat whose use is withheld reads "not attributed" plus the daemon's note. Neither shows a sample time. These cover the two cases from the earlier unsampled/withheld fix, which shipped without tests. - `eee1019`. The moves wording in `apps/observatory/app/observatory.html` (`renderLater`) and `rigs/observatory/observatory-startup.md`, with three tests: - the "Not set up yet" panel's new text; - the page never calls moves "not connected" or "not set up"; - the starter agent is told moves are in the Fleet page. ## Checked - **Review:** an independent review found nothing to change. In an export of this exact head, the five new tests pass. With the old product text put back, exactly the three moves tests fail, and both LOW-1 tests still pass. - **Tests on this head:** Observatory 116/116, Fleet 93/93, and the whole suite (`node --test test/*.test.mjs`) 320/320, on Node v22.23.4. The same red check with the old product text fails exactly the three moves tests. - **Merge:** merges cleanly with current `main` (`8edd4b9`). - **Not done:** the change wasn't checked in a browser. ## Cost Wording only, plus tests. No behaviour, routes, permissions or settings change. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
openrig-studio-apps
The apps and providers for OpenRig Studio. Every app here is a surface that
runs in the SDK's shell (@openrig/studio) — the shell is a rail of tabs, a
stage, and an agent sidebar, plus the contract a surface speaks to reach them.
Layout
apps/<id>/
app.json the manifest: surface, provider, roots, verbs
app/<id>.html the surface
vendor/ assets that travel with THIS app
providers/<name>/
package.json declares roots (as KINDS), binaries, first-run, peers
*.mjs the server(s)
Ultralight vs heavy — the only distinction that matters
Who serves the bytes.
| ultralight | heavy | |
|---|---|---|
| ships | HTML + a script or two | its own service |
| served by | the shell (path: row) |
itself (url: row) |
| install | drop the file, register a row | npm / docker / git — normal distribution |
| needs the SDK | yes — the shell is its runtime | only to appear as a tab |
That inverts the intuitive reading: the ultralight app is the one that needs
the SDK. files is ultralight. media-manager, canvas and mini-nle are
clients of @openrig/studio-video. cutdown is a client of
@openrig/studio-cutdown.
Manifests declare; they do not imply
Every field in app.json and in a provider's openrig block exists because
leaving it out is how bespokeness happens. Roots are declared as kinds
(project, media, footage) and bound to real directories at install. Every
hardcoded path removed from this code — content-piece-1/assembly,
workspace/<project>, $HOME/studio/app — existed because there was nowhere to
say this. If there is a field for it, nobody inlines it.
A manifest authored on a machine that already has everything installed will always under-declare. Test one by installing it somewhere that has nothing.
The video provider's app-contract and media-root regressions run after its dependencies are installed:
cd providers/studio-video
npm ci
npm test
Provenance
Ported from the frozen studio-box spike, then de-bespoked: the original
project each tool was built for was still wired in as a default. The code is
ported, not extracted — hand-extracting it silently broke three contracts that
answered HTTP 200 with real data while the pane rendered empty.