openvibers/openvibe.studio AGENTS.md
AGENTS.md
| 1 | # AGENTS.md — OpenVibe.Studio |
| 2 | |
| 3 | ## Project overview |
| 4 | |
| 5 | OpenVibe.Studio is the network's creative-app host at `https://openvibe.studio`. It serves two |
| 6 | open-source native editors compiled to WebAssembly — **Photo** (PhotoCraft, a layered photo editor) |
| 7 | and **Light** (LightCraft, a photo library and RAW developer) — inside an OpenVibe account, plus the |
| 8 | signed-in hand-offs from Video and Cam and the Pics image tools. |
| 9 | |
| 10 | - **Entry:** `server/index.js` (`node server/index.js`); app in `server/app.js`; config |
| 11 | `server/config.js`; port `5155`, drill port `15155`, service id `studio`. |
| 12 | - **Pages and API:** `server/pages.js` (the app pages and `/edit`), `server/api.js` |
| 13 | (`/api/v1/studio/*`: file bytes, media bytes, publish, image edits), `server/app.js` mounts |
| 14 | health, `/auth`, `/shared`, discovery (`/robots.txt`, `/sitemap.xml`, `/llms.txt`, |
| 15 | `/llms-full.txt`, `/.well-known/agent.json`) and `/openapi.json`. |
| 16 | - **Adapters:** `server/adapters/` — `contract.js`, `index.js`, `photo.js`, `light.js`. One adapter |
| 17 | per app keeps the same contract (`open(file)`, `save()`, `commands()`) so a base can be swapped. |
| 18 | - **OpenVibe clients:** `server/openvibe/media.js`, `pics.js`, `video.js` — each call is scoped to |
| 19 | the person's own Network token. |
| 20 | - **Browser:** `public/js/drivers.js` (one driver per app), `studio.js`, `edit-video.js`, |
| 21 | `image-tools.js`; own CSS in `public/css`. The apps run in a same-origin iframe with the shared |
| 22 | navbar outside it. No bundler for our layer. |
| 23 | - **Bundles:** `scripts/build-bundles.sh`, `scripts/fetch-bundles.sh`, `scripts/smoke.js`; the |
| 24 | `bundles` GitHub workflow builds from pinned upstream tags and publishes an artifact with a |
| 25 | checksum file. A bundle is never committed. |
| 26 | |
| 27 | ## Owns, does not own |
| 28 | |
| 29 | Owns the service layer, the adapter contract, the signed-in hand-off routes, the Pics image-edit |
| 30 | endpoint and the `STUDIO_BUNDLE_ROOT` bundle upload/fetch path. It does not own the editors: Photo is |
| 31 | [PhotoCraft](https://github.com/storytold/photocraft), Light is |
| 32 | [LightCraft](https://github.com/storytold/lightcraft), both MIT OR Apache-2.0, built from pinned |
| 33 | release tags; the licence and attribution stay with every build (`ATTRIBUTION.md`, `NOTICE`, |
| 34 | `STATUS.json`). It does not own media bytes (Media/Video), pictures (Pics), the download drive |
| 35 | (Download) or identity (Network). |
| 36 | |
| 37 | ## Who calls it, what it calls |
| 38 | |
| 39 | A signed-in person opens the app pages; Video and Cam send them to |
| 40 | `GET /edit?media=<Video id>&from=video` and `GET /edit?pics=<Pics slug>&from=cam`. Studio asks the |
| 41 | source service with the person's own Network token whether that person may read the object, and a |
| 42 | private object of someone else answers 404 exactly like a missing id. It calls OpenVibe.Video |
| 43 | (`/api/v1/video/studio/publish` to send a trimmed range, cover, title and visibility), OpenVibe.Pics |
| 44 | (read bytes, save a separate private picture through its re-encoding pipeline), OpenVibe.Download |
| 45 | (hand an export to the person's drive) and Network (SSO/JWKS). The Pics image edit needs |
| 46 | `STUDIO_IMAGE_WORKER_URL`, an operator-controlled worker (`POST /edit`, image in/out); no person token |
| 47 | is sent to it, and without it the endpoint answers 503 and the UI disables submission. |
| 48 | |
| 49 | ## Auth |
| 50 | |
| 51 | Network SSO (RS256 JWT + session cookie). Every Studio API and hand-off is the signed-in person's own |
| 52 | call — never a service token on their behalf — and authorization is the source service's answer with |
| 53 | that token. A private object of another person is a 404, not a 403. The image worker receives only |
| 54 | the selected picture, never a token. |
| 55 | |
| 56 | ## Bundles and deploy |
| 57 | |
| 58 | The `bundles` workflow checks out the pinned upstream tags, builds with Rust stable, |
| 59 | `wasm32-unknown-unknown`, Trunk 0.21.14 (PhotoCraft) or `cargo xtask web` + wasm-bindgen 0.2.129 |
| 60 | (LightCraft), runs the first-frame smoke test in headless Chrome and publishes the bundle with a |
| 61 | checksum file. The deploy fetches that artifact, verifies the checksums and unpacks it into |
| 62 | `STUDIO_BUNDLE_ROOT` (default `public/apps`); without a bundle the page says so instead of showing an |
| 63 | empty frame. `sudo ovhost deploy studio` deploys the service; unit |
| 64 | `deploy/systemd/openvibe-studio.service`, env `/etc/openvibe/studio.env`, nginx |
| 65 | `deploy/nginx/openvibe.studio.conf`. Rollback with `sudo ovhost rollback studio --to <sha>`. |
| 66 | |
| 67 | ## Commands and tests |
| 68 | |
| 69 | ```bash |
| 70 | npm ci |
| 71 | npm start # http://127.0.0.1:5155 |
| 72 | npm test # node --test test/*.test.js |
| 73 | node scripts/smoke.js --app light --dir artifacts/light # first-frame check |
| 74 | node scripts/fetch-bundles.sh # fetch the built artifact in a deploy |
| 75 | ``` |
| 76 | |
| 77 | `npm test` uses the built-in Node test runner (not `openvibe-shared/test-runner`); tests use an |
| 78 | in-process fake image worker and stub services, never model calls or spending. `test/adapters.test.js` |
| 79 | enforces the one adapter contract; `test/studio.test.js`, `test/media.test.js` and |
| 80 | `test/image-tools.test.js` cover the pages, hand-off authorization, publish polling and the worker |
| 81 | fallback. |
| 82 | |
| 83 | ## Conventions and pitfalls |
| 84 | |
| 85 | - CommonJS, 4-space indent, single quotes, semicolons; no linter. Server-rendered pages with the |
| 86 | shared Frame; phone-first CSS. |
| 87 | - **A bundle is never committed.** Ship it as an artifact, verify the checksums, unpack into the |
| 88 | bundle root; a bundle that loads but never reaches its first frame is worse than no bundle. |
| 89 | - **Keep the adapter contract identical** across apps (`assertAdapter`/`assertDriver`); a new base is |
| 90 | an adapter, not a fork of the page. |
| 91 | - **The hand-off never trusts an id:** ask the source service with the person's token, and keep the |
| 92 | private-object 404 indistinguishable from missing. |
| 93 | - **Photo has no scriptable browser API:** a dropped file and no export the page can ask for — say so |
| 94 | on the page instead of faking a bridge. |
| 95 | - **Image edits need the operator worker**; without it, fail available/503, never call a model from |
| 96 | the service. |
| 97 | - **Honest limits stay documented:** the heavy first load, the 4 GB phone memory warning and |
| 98 | online-only use are product statements, not bugs to hide. |
| 99 |