openvibers/openvibe.design AGENTS.md
AGENTS.md
| 1 | # AGENTS.md — OpenVibe.Design |
| 2 | |
| 3 | ## Project overview |
| 4 | |
| 5 | The Design room: the theme studio where a person browses OpenVibe's themes, tries them on previews, edits |
| 6 | one with WCAG contrast checks, uses it, submits it to the gallery and rolls back to an earlier setting; |
| 7 | **Design Studio v1** (`/create`) where one prompt becomes three model variants; and the **Design Arena** |
| 8 | where two outputs are judged blind at phone and desktop sizes and an Elo leaderboard per category ranks |
| 9 | the designers with cost and time. Node.js/Express, PostgreSQL (or PGlite in development), CommonJS, **no |
| 10 | build step**. |
| 11 | |
| 12 | - Service id `design`, port `5090`, origin `https://openvibe.design`, unit `openvibe-design.service`, |
| 13 | release root `/opt/openvibe.design`. |
| 14 | - It stores no accounts and no tokens: the person's Network session lives in httpOnly cookies and their |
| 15 | theme lives in **OpenVibe.Network**; Arena rounds, entries, judgements and ratings live in |
| 16 | **OpenVibe.Bench**. The legacy `arena_*` tables remain only for historical account events/migration. |
| 17 | - Sign-in is an OAuth 2 client of Network (`design`, authorization code + PKCE S256, state check, server- |
| 18 | side exchange). Every theme read/write is proxied to Network's `/api/themes` with the person's token. |
| 19 | |
| 20 | ## What it owns |
| 21 | |
| 22 | - The studio UI and its derived token set, the contrast checks, previews (Live channel, Chat room, shop |
| 23 | grid), colour-vision views, gallery submission, history and restore. |
| 24 | - The token files (`/tokens/<theme>.<ext>`) every tool can read, the icon/font/typeface endpoints, the |
| 25 | component registry, patterns and vectors, brand motion and the `changes` history. |
| 26 | - Design Studio (`/create`, `/d/:id`): three model variants through AI route pins, an HTML sanitizer, |
| 27 | phone/tablet/desktop canvases, guest drafts (private cookie, max three) and per-day limits. |
| 28 | - The Design Arena surface, its sanitized HTML in sandboxed CSP-capped iframes, blind voting and the |
| 29 | open JSONL dataset; Bench is the rating/storage authority. |
| 30 | - The design-owned rows: designs, components, gallery/element records, deep-dive proposals, account-data |
| 31 | receipts and the migration's legacy tables. |
| 32 | - The pages (`/`, `/studio`, `/t/:slug`, `/arena/*`, `/create`, `/d/:id`, `/developers`, `/brand`), the |
| 33 | MCP endpoint, the API and discovery files. |
| 34 | |
| 35 | Not owned: theme selection/storage (Network), Arena ratings/rounds (Bench), model execution (AI), the |
| 36 | shared Frame/kit (`openvibe-shared`), identity. |
| 37 | |
| 38 | ## Who calls it, and whom it calls |
| 39 | |
| 40 | - Callers: people in the browser; MCP clients; sites/tools reading `/tokens/*`, icons, fonts; services |
| 41 | with a Network service token audience `openvibe.design` and the route's capability. |
| 42 | - Depends on: **Network** (SSO, `/api/themes`, projects/grants); **AI** (generate jobs, `ai.run.create`, |
| 43 | Design's client credentials); **Bench** (Arena rounds/entries/judgements/ratings, including |
| 44 | `bench.entry.create`); **Events** (account export/deletion); **Publishing** (SEO/SSR/index); PostgreSQL |
| 45 | and `openvibe-shared` for the Frame/kit/legal/SEO. The Bench scope must include `bench.entry.create` in |
| 46 | addition to the Arena capabilities. |
| 47 | |
| 48 | ## Repository map |
| 49 | |
| 50 | - **Entry:** [server/index.js](server/index.js) — `createApp()`, listen, graceful stop. |
| 51 | - **App:** [server/app.js](server/app.js) — helmet/CSP, `/auth`, `/internal/events`, `/internal/config`, |
| 52 | `/shared`, `/css`, `/js`, `/brand/motion`, `/changes`, `/api/v1/designs`, `/api/v1/gallery`, `/api` |
| 53 | (themes proxy), `/api/create`, `/api/v1/arena`, `/api/v1/brand`, `/tokens`, icons/fonts. |
| 54 | - **Config/db:** [server/config.js](server/config.js), [server/db.js](server/db.js), |
| 55 | [server/settings.js](server/settings.js). |
| 56 | - **Studio/tokens:** [server/themes.js](server/themes.js), [server/tokens.js](server/tokens.js), |
| 57 | [server/tokens/](server/tokens/) (`kit.js`, `formats.js`, `exports.js`), [server/typefaces/](server/typefaces/), |
| 58 | [server/vectors/svg.js](server/vectors/svg.js), [server/patterns/catalog.js](server/patterns/catalog.js), |
| 59 | [server/kit/](server/kit/). |
| 60 | - **Arena/studio:** [server/arena/](server/arena/) (`ai.js`, `jobs.js`, `store.js`, `bench.js`, |
| 61 | `sanitize.js`, `kinds.js`), [server/designs.js](server/designs.js), |
| 62 | [server/create/](server/create/) (`api.js`, `options.js`, `billing.js`), [server/deepdive/](server/deepdive/). |
| 63 | - **Elements/components:** [server/elements/](server/elements/), [server/components/store.js](server/components/store.js), |
| 64 | [server/publish.js](server/publish.js), [server/design-edit.js](server/design-edit.js). |
| 65 | - **HTTP:** [server/http/](server/http/) — `pages.js`, `api.js`, `arena-api.js`, `design-api.js`, `tokens.js`, |
| 66 | `mcp.js`, `icons.js`, `fonts.js`, `brand-api.js`, `gallery-api.js`, `discovery.js`, `same-origin.js`. |
| 67 | - **Browser:** [public/js/studio.js](public/js/studio.js) and the page scripts/CSS in [public/](public/). |
| 68 | - **Deploy:** [deploy/systemd/openvibe-design.service](deploy/systemd/openvibe-design.service), |
| 69 | [deploy/nginx/openvibe.design.conf](deploy/nginx/openvibe.design.conf). |
| 70 | |
| 71 | ## Commands |
| 72 | |
| 73 | ```bash |
| 74 | npm ci |
| 75 | cp .env.example .env # BASE_URL, OV_OAUTH_CLIENT_SECRET; Network URLs optional |
| 76 | npm run dev # http://localhost:5090 |
| 77 | npm test # test/run.js, PGlite, mock Network/AI/Bench |
| 78 | npm test -- studio # filter by file name |
| 79 | ov test # the harness's sharded run of what the change reaches |
| 80 | node --check <file.js> |
| 81 | ``` |
| 82 | |
| 83 | ## Database and migrations |
| 84 | |
| 85 | Design's own PostgreSQL database on the host's data role (ADR-035); embedded PGlite in development and |
| 86 | tests. Async through `openvibe-sdk/db`; migrations `migrations/NNNN_*.sql` (through `0024_element_gallery.sql`) |
| 87 | applied at boot; never edit an applied one, add a new number. Themes are not stored here — Network owns |
| 88 | them; rounds/ratings are Bench's. The `arena_*` tables are legacy and must not be written. |
| 89 | |
| 90 | ## Auth and security |
| 91 | |
| 92 | - People sign in through Network SSO; session tokens are httpOnly cookies and are only ever sent as the |
| 93 | `Authorization` header to the configured Network origin. Nothing renders or logs them. |
| 94 | - A **theme write must come from this site's own origin** (`Origin` check); another site cannot make a |
| 95 | signed-in visitor change their theme. |
| 96 | - `next` after sign-in is a path on this site only; control characters and backslashes are refused. |
| 97 | - Model output is sanitized and rendered **only in sandboxed, CSP-capped iframes**; every dynamic value |
| 98 | in the placeholder views goes through the shared `esc`. |
| 99 | - Service callers need a Network service token audience `openvibe.design` and the route's capability. |
| 100 | - Secrets are environment-only; only variable names appear in code and docs. |
| 101 | |
| 102 | ## Frontend loading model |
| 103 | |
| 104 | Server-rendered pages plus no-build browser scripts ([public/js/studio.js](public/js/studio.js) derives the |
| 105 | token set from six colours); no SPA, bundler or transpiler. `openvibe-shared` supplies the Frame, kit and |
| 106 | `/shared/*` from the pinned node_modules; `/tokens/*`, icons and fonts are served by Design. Readiness is |
| 107 | truthful: `/api/ready` reports `degraded` (HTTP 200) when Network does not answer, and the pages and crawl |
| 108 | files still serve. |
| 109 | |
| 110 | ## Deploy |
| 111 | |
| 112 | `sudo ovhost deploy design` (checkout at `/opt/openvibe.design`, unit `openvibe-design.service` on |
| 113 | `127.0.0.1:5090`, env `/etc/openvibe/design.env`). nginx |
| 114 | [deploy/nginx/openvibe.design.conf](deploy/nginx/openvibe.design.conf) is installed with `ov-vhost-install` |
| 115 | and allows GET/POST/PUT on `/api` and GET on `/auth`. Migrations run at boot; no secret in a command |
| 116 | line, file or log. |
| 117 | |
| 118 | ## Tests |
| 119 | |
| 120 | `npm test` runs each `test/*.test.js` in its own process with PGlite and in-process Network/AI/Bench |
| 121 | mocks. It pins the theme proxy (token handling, 4xx pass-through, 502 on a Network failure), the Origin |
| 122 | check, the WCAG gate and token derivation, the studio/previews, the Arena sanitizer and sandboxed frames, |
| 123 | blind voting and Bench calls, designs/guest limits, account export/deletion and discovery. `ov test` runs |
| 124 | the same. |
| 125 | |
| 126 | ## Conventions |
| 127 | |
| 128 | - CommonJS, 4-space indent, single quotes, semicolons; factories with injectable config/db/clock/fetch/log. |
| 129 | - Colours/tokens are data: derive them, never hard-code a token set in a page. WCAG contrast is a gate, |
| 130 | not a warning, except "lines against cards", which is advisory. |
| 131 | - Errors are problem+json with stable codes; Network 4xx answers pass through, a Network failure is 502. |
| 132 | - Model output is untrusted: sanitize, sandbox, escape, cap. |
| 133 | |
| 134 | ## Pitfalls |
| 135 | |
| 136 | - **Network owns the theme.** Do not add a local theme store or a direct browser write; the proxy is the |
| 137 | only path, and the person's token never leaves the server header. |
| 138 | - **The Origin check protects writes.** A same-site-but-not-own-origin POST must fail. |
| 139 | - **The contrast checks gate "Use this theme".** Do not enable it when a required check fails; do not turn |
| 140 | the advisory "lines against cards" into a blocker. |
| 141 | - **Arena output is untrusted HTML.** Keep the sanitizer and the sandboxed CSP iframe; never render model |
| 142 | HTML in the page's own document. |
| 143 | - **Bench is the rating authority.** Do not add a local Elo table or count a vote twice; rounds/entries/ |
| 144 | judgements go to Bench, and the legacy `arena_*` tables stay read-only. |
| 145 | - **Guests are rate-limited and drafts are capped at three** in a private cookie; a signed-in design |
| 146 | belongs to its subject, not the cookie. |
| 147 | - **Do not log, render or echo a token**, including in an error path. |
| 148 | - **`/api/ready` degraded is not a failure** when Network is down; do not turn it into a 503 that removes |
| 149 | the studio from service. |
| 150 | |
| 151 | ## Documentation |
| 152 | |
| 153 | - [README.md](README.md) — what the studio/Arena are, pages, token files, API/MCP, sign-in, config, deploy, |
| 154 | security. |
| 155 | - [STATUS.json](STATUS.json) — the precise v2 summary. |
| 156 | - OpenVibe.Contracts holds `design.*`/`bench.*` shapes; Network owns `themes`, Bench owns ratings. |
| 157 |