openvibers/openvibe.codes Commits
The Codes home is built from the showcase kit (plan T11, D92; openvibe-shared v2.3.1): a hero with where to start and a working SDK sample, what an integration gets (credentials, grants, events, generated reference, playgrounds, the SDK), the path from account to integration, the limits a project really gets read live from OpenVibe.Events' /limits.json (never restated; the section is left out when Events does not answer), the reference, recent releases and a closing call to action. The kit's stylesheet is linked (cached, loaded styles-first on a boost swap). Home budgets raised as a decision: HTML 30 KB (measured 27.0: sections and six inline ring icons), CSS 19 KB with the shared stylesheet. 33/33.
Changes
@@@ -11,7 +11,7 @@"packages": ["openvibe-contracts v0.79.0","openvibe-sdk v0.25.0",-- "openvibe-shared v2.2.0"++ "openvibe-shared v2.3.1"],"summary": "Deployed and public since 2026-09-23: https://openvibe.codes is served by this portal (deployed with `sudo ovhost deploy codes`, unit openvibe-codes on 127.0.0.1:4900 behind nginx), and the domain left OpenVibe.Sites. Sign-in uses the registered Network OAuth client `codes` (PKCE S256); the principal `codes` holds events.event.publish and the Events relay is configured. On 2026-09-23 production held 0 manifests, 0 releases and 0 playground runs. The external-developer path (account, project, sandbox app, grants, Media upload, app events) was run once on production on 2026-09-23 with public endpoints and curl, not through these playgrounds; that check is not committed to a repository or CI.","works": [@@@ -18,7 +18,7 @@"iovalkey": "^0.4.0","openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.79.0","openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.25.0",-- "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v2.2.0",++ "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v2.3.1","pg": "^8.23.0"},"devDependencies": {@@@ -1202,9 +1202,9 @@}},"node_modules/openvibe-shared": {-- "version": "2.2.0",-- "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v2.2.0",-- "integrity": "sha512-1q+iUuUxzPeylTYmithW4jR++Jccvn5GEOrB1B3D18Dgn6OXQhPpvhPQAa1D1k1p+dWIa1/SmggyJwSGFnTNtw==",++ "version": "2.3.1",++ "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v2.3.1",++ "integrity": "sha512-K+MXnB4O+8xIFi+NQGC2SDTvDq3RkzfxQ3bTVx76Y/Rbuq7kC/L5kL3c/06xr9sPYcjyzF3Bhpv5yH2YNfR4Zw==","license": "MIT","dependencies": {"jsonwebtoken": "^9.0.0"@@@ -30,7 +30,7 @@"iovalkey": "^0.4.0","openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.79.0","openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.25.0",-- "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v2.2.0",++ "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v2.3.1","pg": "^8.23.0"},"engines": {@@@ -74,3 +74,8 @@ fieldset { border: 1px solid var(--border, #1f2d47); border-radius: 8px; display@media (max-width: 640px) { .facts { grid-template-columns: 1fr; } .facts dt { margin-top: 6px; } .toc ul { columns: 1; } }.num { font-variant-numeric: tabular-nums; white-space: nowrap; }meter.headroom { width: 8rem; max-width: 100%; vertical-align: middle; }++++/* The home page's showcase sections (openvibe-shared/showcase.css) sit inside .page, which has its own gutter. */++.page .sc-hero, .page .sc-sec, .page .sc-cta { padding-left: 0; padding-right: 0; }++.page .sc-ic .ov-icon { --ovi-size: 28px !important; }++.codes-hero-code { margin: 0; padding: 16px; border-radius: 14px; border: 1px solid var(--border, #1f2d47); background: var(--bg-primary, #0a0f1c); font-size: 13px; line-height: 1.6; overflow: auto; }@@@ -14,6 +14,7 @@const fs = require('fs');const ovServe = require('openvibe-shared/serve');const frame = require('openvibe-shared/frame');++const showcase = require('openvibe-shared/showcase');const path = require('path');const express = require('express');const { asyncRouter } = require('./router');@@@ -50,6 +51,13 @@ function loadGovernance() {});}++/** A limit as its service states it: a number with its unit, or unlimited/none. */++function limitAmount(v, unit) {++ if (v === null || v === undefined) return 'unlimited';++ if (v === 0) return 'none';++ return unit ? `${Number(v).toLocaleString('en-US')} ${unit}` : Number(v).toLocaleString('en-US');++}++function createPageRoutes(ctx) {const { config, docs, releases, trust, network, sso } = ctx;const r = asyncRouter();@@@ -60,28 +68,67 @@ function createPageRoutes(ctx) {r.get('/', async (req, res) => {const recent = await releases.recentPublic(10);++ // The limits a developer's project really gets, from the service that enforces them (never restated here).++ const eventsSource = ctx.limits && ctx.limits.bySource ? ctx.limits.bySource('events') : null;++ const eventsLimits = eventsSource ? await ctx.limits.read(eventsSource) : null;++ const limitRows = eventsLimits && eventsLimits.body ? eventsLimits.body.limits.slice(0, 6).map((l) => ({ label: l.label, values: [limitAmount(l.sandbox, l.unit), limitAmount(l.production, l.unit)] })) : [];++ const snippet = `// openvibe-sdk v${docs.sdkVersion} (install: /docs/sdk)++const { createClient } = require('openvibe-sdk');++const { createServiceTokenClient } =++ require('openvibe-sdk/auth');++++const ov = createClient({++ tokenProvider: createServiceTokenClient({++ clientId, clientSecret,++ }),++});++const services = await ov.registry.services();`;page(req, res, {index: true, cache: PUBLIC_CACHE,jsonLd: homeJsonLd(config),-- body: html`<h1>Build on OpenVibe</h1>--<p class="lead">OpenVibe.Codes is the developer portal: create a project and apps in OpenVibe.Network, get scoped credentials, request capability grants, and read reference docs generated from the exact contract and SDK versions the platform runs.</p>--<div class="notice">Status: alpha. What works and what does not is listed on the <a href="/policy/transparency">transparency page</a>. Network owns projects, apps, credentials, grants and quotas; Codes is a portal over its API and never stores a secret.</div>--<h2>From account to integration</h2>--<ol class="steps">--<li><a href="/projects">Create a project</a> (signed in with your OpenVibe account). New projects are sandbox-only; staff enable production.</li>--<li>Add a <strong>sandbox app</strong>. A confidential app gets a client secret, shown once.</li>--<li><a href="/docs/capabilities?grantable=1">Pick capabilities</a> and request them for the app. They are approved only inside the project's allowance.</li>--<li>Install <a href="/docs/sdk">openvibe-sdk</a>; get a token with <code>createServiceTokenClient()</code> (client credentials) or sign people in with <a href="/oauth">authorization code + PKCE</a>.</li>--<li>Receive events: subscribe, then verify each delivery's signature (<a href="/tools/webhooks">tester</a>).</li>--<li>Try calls in the app's playground with the app's own credentials: it cannot do anything the app is not granted.</li>--<li>Publish release metadata with a <a href="/manifests/validate">validated manifest</a>; rotate or revoke credentials any time.</li>--</ol>--<h2>Reference</h2>--<p>Generated from <code>openvibe-contracts v${docs.contractsVersion}</code> and <code>openvibe-sdk v${docs.sdkVersion}</code>: <a href="/docs/api">API explorer</a> · <a href="/docs/contracts">contracts</a> · <a href="/docs/capabilities">capabilities</a> · <a href="/docs/events">events</a> · <a href="/docs/services">services</a> · <a href="/docs/tools">tools</a> · <a href="/docs/sdk">SDK</a>.</p>--<h2>Recent releases</h2>++ styles: [showcase.STYLESHEET],++ body: html`${raw(showcase.hero({++ eyebrow: 'OpenVibe.Codes · the developer portal · alpha',++ title: 'Build on', accent: 'OpenVibe',++ lede: 'Create a project, get scoped credentials, request capability grants, and read reference docs generated from the exact contract and SDK versions the platform runs.',++ actions: [{ label: 'Create a project', href: '/projects', primary: true }, { label: 'Read the docs', href: '/docs' }],++ note: 'Network owns projects, apps, credentials, grants and quotas; Codes is a portal over its API and never stores a secret.',++ aside: { html: `<pre class="codes-hero-code"><code>${showcase.esc(snippet)}</code></pre>` },++ }))}++<div class="notice">Status: alpha. What works and what does not is listed on the <a href="/policy/transparency">transparency page</a>.</div>++${raw(showcase.features({++ title: 'Everything an integration needs', lede: 'Each piece is a public API with a reference generated from what production runs.',++ items: [++ { icon: 'ov:account', title: 'Scoped credentials', text: 'A confidential app gets a client secret, shown once; rotate or revoke it any time.', href: '/projects' },++ { icon: 'ov:check', title: 'Capability grants', text: 'Ask for exactly what your app does; grants are approved inside the project\'s allowance.', href: '/docs/capabilities?grantable=1' },++ { icon: 'ov:bell', title: 'Events and webhooks', text: 'Subscribe to the network and verify every delivery\'s signature.', href: '/tools/webhooks' },++ { icon: 'ov:docs', title: 'Generated reference', text: `Contracts, capabilities, events and APIs from openvibe-contracts v${docs.contractsVersion}.`, href: '/docs' },++ { icon: 'ov:code', title: 'Playgrounds', text: 'Try calls with your app\'s own credentials: it cannot do anything the app is not granted.', href: '/oauth' },++ { icon: 'ov:json', title: 'The SDK', text: `openvibe-sdk v${docs.sdkVersion}: one package for browser and server, typed.`, href: '/docs/sdk' },++ ],++ }))}++${raw(showcase.steps({++ title: 'From account to integration',++ items: [++ { title: 'Create a project', text: 'Signed in with your OpenVibe account. New projects are sandbox-only; staff enable production.', href: '/projects' },++ { title: 'Add a sandbox app', text: 'A confidential app gets a client secret, shown once.' },++ { title: 'Pick capabilities', text: 'Request them for the app; they are approved only inside the project\'s allowance.', href: '/docs/capabilities?grantable=1' },++ { title: 'Get a token', text: 'createServiceTokenClient() for client credentials, or sign people in with authorization code + PKCE.', href: '/oauth' },++ { title: 'Receive events', text: 'Subscribe, then verify each delivery\'s signature.', href: '/tools/webhooks' },++ { title: 'Publish a release', text: 'Validated release metadata; rotate or revoke credentials any time.', href: '/manifests/validate' },++ ],++ }))}++${raw(showcase.limits({++ title: 'What a project gets', lede: 'Pricing is the limits: these are the numbers OpenVibe.Events enforces for every project today.',++ columns: ['Sandbox', 'Production'], rows: limitRows, source: eventsSource && eventsSource.public,++ }))}++<section class="sc-sec" aria-labelledby="h-ref"><h2 id="h-ref">Reference</h2>++<p class="sc-lede">Generated from <code>openvibe-contracts v${docs.contractsVersion}</code> and <code>openvibe-sdk v${docs.sdkVersion}</code>: <a href="/docs/api">API explorer</a> · <a href="/docs/contracts">contracts</a> · <a href="/docs/capabilities">capabilities</a> · <a href="/docs/events">events</a> · <a href="/docs/services">services</a> · <a href="/docs/tools">tools</a> · <a href="/docs/sdk">SDK</a> · <a href="/docs/limits">all limits</a>.</p></section>++<section class="sc-sec" aria-labelledby="h-rel"><h2 id="h-rel">Recent releases</h2>${table(['App', 'Kind', 'Version', 'Status', 'Trust', 'Published'], recent.map((x) => [html`<a href="/apps/${x.app_id}">${x.name}</a>`, x.kind, html`<a href="/releases/${x.id}">${x.version}</a>`, x.status, x.trust.tier, time(x.published_at),-- ]), { empty: 'No releases have been published yet.' })}`,++ ]), { empty: 'No releases have been published yet.' })}</section>++${raw(showcase.cta({ title: 'Start building', text: 'Sign in with your OpenVibe account; your first project takes a minute.', actions: [{ label: 'Create a project', href: '/projects', primary: true }] }))}`,});});@@@ -46,7 +46,7 @@ function setRelease(id) { if (id) RELEASE = String(id); }/*** o: title, description, body (html), viewer, config, path, index (default false), scripts [rel],-- * crumbs [{ label, href }], jsonLd [nodes] (serialised by openvibe-shared/seo)++ * crumbs [{ label, href }], jsonLd [nodes] (serialised by openvibe-shared/seo), styles [openvibe-shared stylesheet names]*/function renderPage(o) {const viewer = o.viewer || { kind: 'anonymous' };@@@ -90,6 +90,7 @@ function renderPage(o) {${appIcon.headTags({ site: 'codes' })}${(o.jsonLd || []).filter(Boolean).map(seo.jsonLdTag).join('\n')}<link rel="stylesheet" href="${asset('css/codes.css')}">++${(o.styles || []).map((name) => `<link rel="stylesheet" href="${esc(ovServe.url(name))}">`).join('\n')}<script src="${ovServe.url('theme-loader.js')}" defer></script><script src="${ovServe.url('navbar.js')}" defer></script><script src="${ovServe.url('footer.js')}" defer></script>@@@ -12,14 +12,14 @@ const { spawn } = require('child_process');const { measure, check, format } = require('openvibe-shared/perf-budget');const BUDGETS = {-- htmlRawKB: 21, // measured 19.5 (fresh database)-- htmlBrotliKB: 5.6, // 5.1++ htmlRawKB: 30, // measured 27.0 (fresh database; the showcase home, 2026-09-29: sections + six inline ring icons)++ htmlBrotliKB: 7.5, // 6.7jsFiles: 5, // 4jsRawKB: 225, // 212.2jsBrotliKB: 53, // 49.9cssFiles: 2, // 1-- cssRawKB: 7.5, // 6.6-- cssBrotliKB: 2, // 1.6++ cssRawKB: 19, // 17.3 (codes.css + the cached /shared/showcase.css)++ cssBrotliKB: 5, // 4.1externalFiles: 2, // 0};
@@ -11,7 +11,7 @@
@@ -11,7 +11,7 @@
"packages": [
"packages": [
"openvibe-contracts v0.79.0",
"openvibe-contracts v0.79.0",
"openvibe-sdk v0.25.0",
"openvibe-sdk v0.25.0",
- "openvibe-shared v2.2.0"
+ "openvibe-shared v2.3.1"
],
],
"summary": "Deployed and public since 2026-09-23: https://openvibe.codes is served by this portal (deployed with `sudo ovhost deploy codes`, unit openvibe-codes on 127.0.0.1:4900 behind nginx), and the domain left OpenVibe.Sites. Sign-in uses the registered Network OAuth client `codes` (PKCE S256); the principal `codes` holds events.event.publish and the Events relay is configured. On 2026-09-23 production held 0 manifests, 0 releases and 0 playground runs. The external-developer path (account, project, sandbox app, grants, Media upload, app events) was run once on production on 2026-09-23 with public endpoints and curl, not through these playgrounds; that check is not committed to a repository or CI.",
"summary": "Deployed and public since 2026-09-23: https://openvibe.codes is served by this portal (deployed with `sudo ovhost deploy codes`, unit openvibe-codes on 127.0.0.1:4900 behind nginx), and the domain left OpenVibe.Sites. Sign-in uses the registered Network OAuth client `codes` (PKCE S256); the principal `codes` holds events.event.publish and the Events relay is configured. On 2026-09-23 production held 0 manifests, 0 releases and 0 playground runs. The external-developer path (account, project, sandbox app, grants, Media upload, app events) was run once on production on 2026-09-23 with public endpoints and curl, not through these playgrounds; that check is not committed to a repository or CI.",
"works": [
"works": [
@@ -18,7 +18,7 @@
@@ -18,7 +18,7 @@
"iovalkey": "^0.4.0",
"iovalkey": "^0.4.0",
"openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.79.0",
"openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.79.0",
"openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.25.0",
"openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.25.0",
- "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v2.2.0",
+ "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v2.3.1",
"pg": "^8.23.0"
"pg": "^8.23.0"
},
},
"devDependencies": {
"devDependencies": {
@@ -1202,9 +1202,9 @@
@@ -1202,9 +1202,9 @@
}
}
},
},
"node_modules/openvibe-shared": {
"node_modules/openvibe-shared": {
- "version": "2.2.0",
- "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v2.2.0",
- "integrity": "sha512-1q+iUuUxzPeylTYmithW4jR++Jccvn5GEOrB1B3D18Dgn6OXQhPpvhPQAa1D1k1p+dWIa1/SmggyJwSGFnTNtw==",
+ "version": "2.3.1",
+ "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v2.3.1",
+ "integrity": "sha512-K+MXnB4O+8xIFi+NQGC2SDTvDq3RkzfxQ3bTVx76Y/Rbuq7kC/L5kL3c/06xr9sPYcjyzF3Bhpv5yH2YNfR4Zw==",
"license": "MIT",
"license": "MIT",
"dependencies": {
"dependencies": {
"jsonwebtoken": "^9.0.0"
"jsonwebtoken": "^9.0.0"
@@ -30,7 +30,7 @@
@@ -30,7 +30,7 @@
"iovalkey": "^0.4.0",
"iovalkey": "^0.4.0",
"openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.79.0",
"openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.79.0",
"openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.25.0",
"openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.25.0",
- "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v2.2.0",
+ "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v2.3.1",
"pg": "^8.23.0"
"pg": "^8.23.0"
},
},
"engines": {
"engines": {
@@ -74,3 +74,8 @@ fieldset { border: 1px solid var(--border, #1f2d47); border-radius: 8px; display
@@ -74,3 +74,8 @@ fieldset { border: 1px solid var(--border, #1f2d47); border-radius: 8px; display
@media (max-width: 640px) { .facts { grid-template-columns: 1fr; } .facts dt { margin-top: 6px; } .toc ul { columns: 1; } }
@media (max-width: 640px) { .facts { grid-template-columns: 1fr; } .facts dt { margin-top: 6px; } .toc ul { columns: 1; } }
.num { font-variant-numeric: tabular-nums; white-space: nowrap; }
.num { font-variant-numeric: tabular-nums; white-space: nowrap; }
meter.headroom { width: 8rem; max-width: 100%; vertical-align: middle; }
meter.headroom { width: 8rem; max-width: 100%; vertical-align: middle; }
+
+/* The home page's showcase sections (openvibe-shared/showcase.css) sit inside .page, which has its own gutter. */
+.page .sc-hero, .page .sc-sec, .page .sc-cta { padding-left: 0; padding-right: 0; }
+.page .sc-ic .ov-icon { --ovi-size: 28px !important; }
+.codes-hero-code { margin: 0; padding: 16px; border-radius: 14px; border: 1px solid var(--border, #1f2d47); background: var(--bg-primary, #0a0f1c); font-size: 13px; line-height: 1.6; overflow: auto; }
@@ -14,6 +14,7 @@
@@ -14,6 +14,7 @@
const fs = require('fs');
const fs = require('fs');
const ovServe = require('openvibe-shared/serve');
const ovServe = require('openvibe-shared/serve');
const frame = require('openvibe-shared/frame');
const frame = require('openvibe-shared/frame');
+const showcase = require('openvibe-shared/showcase');
const path = require('path');
const path = require('path');
const express = require('express');
const express = require('express');
const { asyncRouter } = require('./router');
const { asyncRouter } = require('./router');
@@ -50,6 +51,13 @@ function loadGovernance() {
@@ -50,6 +51,13 @@ function loadGovernance() {
});
});
}
}
+/** A limit as its service states it: a number with its unit, or unlimited/none. */
+function limitAmount(v, unit) {
+ if (v === null || v === undefined) return 'unlimited';
+ if (v === 0) return 'none';
+ return unit ? `${Number(v).toLocaleString('en-US')} ${unit}` : Number(v).toLocaleString('en-US');
+}
+
function createPageRoutes(ctx) {
function createPageRoutes(ctx) {
const { config, docs, releases, trust, network, sso } = ctx;
const { config, docs, releases, trust, network, sso } = ctx;
const r = asyncRouter();
const r = asyncRouter();
@@ -60,28 +68,67 @@ function createPageRoutes(ctx) {
@@ -60,28 +68,67 @@ function createPageRoutes(ctx) {
r.get('/', async (req, res) => {
r.get('/', async (req, res) => {
const recent = await releases.recentPublic(10);
const recent = await releases.recentPublic(10);
+ // The limits a developer's project really gets, from the service that enforces them (never restated here).
+ const eventsSource = ctx.limits && ctx.limits.bySource ? ctx.limits.bySource('events') : null;
+ const eventsLimits = eventsSource ? await ctx.limits.read(eventsSource) : null;
+ const limitRows = eventsLimits && eventsLimits.body ? eventsLimits.body.limits.slice(0, 6).map((l) => ({ label: l.label, values: [limitAmount(l.sandbox, l.unit), limitAmount(l.production, l.unit)] })) : [];
+ const snippet = `// openvibe-sdk v${docs.sdkVersion} (install: /docs/sdk)
+const { createClient } = require('openvibe-sdk');
+const { createServiceTokenClient } =
+ require('openvibe-sdk/auth');
+
+const ov = createClient({
+ tokenProvider: createServiceTokenClient({
+ clientId, clientSecret,
+ }),
+});
+const services = await ov.registry.services();`;
page(req, res, {
page(req, res, {
index: true, cache: PUBLIC_CACHE,
index: true, cache: PUBLIC_CACHE,
jsonLd: homeJsonLd(config),
jsonLd: homeJsonLd(config),
- body: html`<h1>Build on OpenVibe</h1>
-<p class="lead">OpenVibe.Codes is the developer portal: create a project and apps in OpenVibe.Network, get scoped credentials, request capability grants, and read reference docs generated from the exact contract and SDK versions the platform runs.</p>
-<div class="notice">Status: alpha. What works and what does not is listed on the <a href="/policy/transparency">transparency page</a>. Network owns projects, apps, credentials, grants and quotas; Codes is a portal over its API and never stores a secret.</div>
-<h2>From account to integration</h2>
-<ol class="steps">
-<li><a href="/projects">Create a project</a> (signed in with your OpenVibe account). New projects are sandbox-only; staff enable production.</li>
-<li>Add a <strong>sandbox app</strong>. A confidential app gets a client secret, shown once.</li>
-<li><a href="/docs/capabilities?grantable=1">Pick capabilities</a> and request them for the app. They are approved only inside the project's allowance.</li>
-<li>Install <a href="/docs/sdk">openvibe-sdk</a>; get a token with <code>createServiceTokenClient()</code> (client credentials) or sign people in with <a href="/oauth">authorization code + PKCE</a>.</li>
-<li>Receive events: subscribe, then verify each delivery's signature (<a href="/tools/webhooks">tester</a>).</li>
-<li>Try calls in the app's playground with the app's own credentials: it cannot do anything the app is not granted.</li>
-<li>Publish release metadata with a <a href="/manifests/validate">validated manifest</a>; rotate or revoke credentials any time.</li>
-</ol>
-<h2>Reference</h2>
-<p>Generated from <code>openvibe-contracts v${docs.contractsVersion}</code> and <code>openvibe-sdk v${docs.sdkVersion}</code>: <a href="/docs/api">API explorer</a> · <a href="/docs/contracts">contracts</a> · <a href="/docs/capabilities">capabilities</a> · <a href="/docs/events">events</a> · <a href="/docs/services">services</a> · <a href="/docs/tools">tools</a> · <a href="/docs/sdk">SDK</a>.</p>
-<h2>Recent releases</h2>
+ styles: [showcase.STYLESHEET],
+ body: html`${raw(showcase.hero({
+ eyebrow: 'OpenVibe.Codes · the developer portal · alpha',
+ title: 'Build on', accent: 'OpenVibe',
+ lede: 'Create a project, get scoped credentials, request capability grants, and read reference docs generated from the exact contract and SDK versions the platform runs.',
+ actions: [{ label: 'Create a project', href: '/projects', primary: true }, { label: 'Read the docs', href: '/docs' }],
+ note: 'Network owns projects, apps, credentials, grants and quotas; Codes is a portal over its API and never stores a secret.',
+ aside: { html: `<pre class="codes-hero-code"><code>${showcase.esc(snippet)}</code></pre>` },
+ }))}
+<div class="notice">Status: alpha. What works and what does not is listed on the <a href="/policy/transparency">transparency page</a>.</div>
+${raw(showcase.features({
+ title: 'Everything an integration needs', lede: 'Each piece is a public API with a reference generated from what production runs.',
+ items: [
+ { icon: 'ov:account', title: 'Scoped credentials', text: 'A confidential app gets a client secret, shown once; rotate or revoke it any time.', href: '/projects' },
+ { icon: 'ov:check', title: 'Capability grants', text: 'Ask for exactly what your app does; grants are approved inside the project\'s allowance.', href: '/docs/capabilities?grantable=1' },
+ { icon: 'ov:bell', title: 'Events and webhooks', text: 'Subscribe to the network and verify every delivery\'s signature.', href: '/tools/webhooks' },
+ { icon: 'ov:docs', title: 'Generated reference', text: `Contracts, capabilities, events and APIs from openvibe-contracts v${docs.contractsVersion}.`, href: '/docs' },
+ { icon: 'ov:code', title: 'Playgrounds', text: 'Try calls with your app\'s own credentials: it cannot do anything the app is not granted.', href: '/oauth' },
+ { icon: 'ov:json', title: 'The SDK', text: `openvibe-sdk v${docs.sdkVersion}: one package for browser and server, typed.`, href: '/docs/sdk' },
+ ],
+ }))}
+${raw(showcase.steps({
+ title: 'From account to integration',
+ items: [
+ { title: 'Create a project', text: 'Signed in with your OpenVibe account. New projects are sandbox-only; staff enable production.', href: '/projects' },
+ { title: 'Add a sandbox app', text: 'A confidential app gets a client secret, shown once.' },
+ { title: 'Pick capabilities', text: 'Request them for the app; they are approved only inside the project\'s allowance.', href: '/docs/capabilities?grantable=1' },
+ { title: 'Get a token', text: 'createServiceTokenClient() for client credentials, or sign people in with authorization code + PKCE.', href: '/oauth' },
+ { title: 'Receive events', text: 'Subscribe, then verify each delivery\'s signature.', href: '/tools/webhooks' },
+ { title: 'Publish a release', text: 'Validated release metadata; rotate or revoke credentials any time.', href: '/manifests/validate' },
+ ],
+ }))}
+${raw(showcase.limits({
+ title: 'What a project gets', lede: 'Pricing is the limits: these are the numbers OpenVibe.Events enforces for every project today.',
+ columns: ['Sandbox', 'Production'], rows: limitRows, source: eventsSource && eventsSource.public,
+ }))}
+<section class="sc-sec" aria-labelledby="h-ref"><h2 id="h-ref">Reference</h2>
+<p class="sc-lede">Generated from <code>openvibe-contracts v${docs.contractsVersion}</code> and <code>openvibe-sdk v${docs.sdkVersion}</code>: <a href="/docs/api">API explorer</a> · <a href="/docs/contracts">contracts</a> · <a href="/docs/capabilities">capabilities</a> · <a href="/docs/events">events</a> · <a href="/docs/services">services</a> · <a href="/docs/tools">tools</a> · <a href="/docs/sdk">SDK</a> · <a href="/docs/limits">all limits</a>.</p></section>
+<section class="sc-sec" aria-labelledby="h-rel"><h2 id="h-rel">Recent releases</h2>
${table(['App', 'Kind', 'Version', 'Status', 'Trust', 'Published'], recent.map((x) => [
${table(['App', 'Kind', 'Version', 'Status', 'Trust', 'Published'], recent.map((x) => [
html`<a href="/apps/${x.app_id}">${x.name}</a>`, x.kind, html`<a href="/releases/${x.id}">${x.version}</a>`, x.status, x.trust.tier, time(x.published_at),
html`<a href="/apps/${x.app_id}">${x.name}</a>`, x.kind, html`<a href="/releases/${x.id}">${x.version}</a>`, x.status, x.trust.tier, time(x.published_at),
- ]), { empty: 'No releases have been published yet.' })}`,
+ ]), { empty: 'No releases have been published yet.' })}</section>
+${raw(showcase.cta({ title: 'Start building', text: 'Sign in with your OpenVibe account; your first project takes a minute.', actions: [{ label: 'Create a project', href: '/projects', primary: true }] }))}`,
});
});
});
});
@@ -46,7 +46,7 @@ function setRelease(id) { if (id) RELEASE = String(id); }
@@ -46,7 +46,7 @@ function setRelease(id) { if (id) RELEASE = String(id); }
/**
/**
* o: title, description, body (html), viewer, config, path, index (default false), scripts [rel],
* o: title, description, body (html), viewer, config, path, index (default false), scripts [rel],
- * crumbs [{ label, href }], jsonLd [nodes] (serialised by openvibe-shared/seo)
+ * crumbs [{ label, href }], jsonLd [nodes] (serialised by openvibe-shared/seo), styles [openvibe-shared stylesheet names]
*/
*/
function renderPage(o) {
function renderPage(o) {
const viewer = o.viewer || { kind: 'anonymous' };
const viewer = o.viewer || { kind: 'anonymous' };
@@ -90,6 +90,7 @@ function renderPage(o) {
@@ -90,6 +90,7 @@ function renderPage(o) {
${appIcon.headTags({ site: 'codes' })}
${appIcon.headTags({ site: 'codes' })}
${(o.jsonLd || []).filter(Boolean).map(seo.jsonLdTag).join('\n')}
${(o.jsonLd || []).filter(Boolean).map(seo.jsonLdTag).join('\n')}
<link rel="stylesheet" href="${asset('css/codes.css')}">
<link rel="stylesheet" href="${asset('css/codes.css')}">
+${(o.styles || []).map((name) => `<link rel="stylesheet" href="${esc(ovServe.url(name))}">`).join('\n')}
<script src="${ovServe.url('theme-loader.js')}" defer></script>
<script src="${ovServe.url('theme-loader.js')}" defer></script>
<script src="${ovServe.url('navbar.js')}" defer></script>
<script src="${ovServe.url('navbar.js')}" defer></script>
<script src="${ovServe.url('footer.js')}" defer></script>
<script src="${ovServe.url('footer.js')}" defer></script>
@@ -12,14 +12,14 @@ const { spawn } = require('child_process');
@@ -12,14 +12,14 @@ const { spawn } = require('child_process');
const { measure, check, format } = require('openvibe-shared/perf-budget');
const { measure, check, format } = require('openvibe-shared/perf-budget');
const BUDGETS = {
const BUDGETS = {
- htmlRawKB: 21, // measured 19.5 (fresh database)
- htmlBrotliKB: 5.6, // 5.1
+ htmlRawKB: 30, // measured 27.0 (fresh database; the showcase home, 2026-09-29: sections + six inline ring icons)
+ htmlBrotliKB: 7.5, // 6.7
jsFiles: 5, // 4
jsFiles: 5, // 4
jsRawKB: 225, // 212.2
jsRawKB: 225, // 212.2
jsBrotliKB: 53, // 49.9
jsBrotliKB: 53, // 49.9
cssFiles: 2, // 1
cssFiles: 2, // 1
- cssRawKB: 7.5, // 6.6
- cssBrotliKB: 2, // 1.6
+ cssRawKB: 19, // 17.3 (codes.css + the cached /shared/showcase.css)
+ cssBrotliKB: 5, // 4.1
externalFiles: 2, // 0
externalFiles: 2, // 0
};
};