openvibers/openvibe.codes Commits
Improve OpenVibe as a guided path: four steps, one search box, every repository as a row (#148)
Changes
@@@ -160,5 +160,5 @@ Part of the [OpenVibe network](https://openvibe.network). Built in the open by [<!-- versions:start -->- openvibe-contracts: v0.232.0- openvibe-sdk: v0.67.0--- openvibe-shared: v4.33.0++- openvibe-shared: v4.36.2<!-- versions:end -->@@@ -9,11 +9,11 @@"domain": "openvibe.codes","contracts": "openvibe-contracts v0.232.0","sdk": "openvibe-sdk v0.67.0",-- "shared": "openvibe-shared v4.33.0",++ "shared": "openvibe-shared v4.36.2","packages": ["openvibe-contracts v0.232.0","openvibe-sdk v0.67.0",-- "openvibe-shared v4.33.0"++ "openvibe-shared v4.36.2"],"summary": "Public at https://openvibe.codes (sudo ovhost deploy codes; unit openvibe-codes on 127.0.0.1:4900). Since 2026-10-08 Codes is the open coding-agent harness: openvibe-codes (a command-line tool and Node.js module, v0.2.0) runs a coding task on Claude Code, Codex, OpenCode, Command Code, Aider, the DeepSeek API or a person's own OpenAI-compatible model on their machine, hands it to the next agent when one fails or goes quiet, and keeps every run; the site carries the agent catalog with a public routing API, getting started, Improve OpenVibe and the community documents. The developer console that used to live here moved to OpenVibe.Services (https://openvibe.services) and every one of its addresses answers a permanent redirect there.","works": [@@@ -52,5 +52,5 @@"run a coding task on your own machine from any browser (/run; openvibe-codes job through OpenVibe Run and the person's own OpenVibe Node)","user JSON API for signed-in people to list, start, read and cancel their own tasks"],-- "updated": "2026-10-28"++ "updated": "2026-10-29"}@@@ -17,7 +17,7 @@"iovalkey": "^0.4.0","openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.232.0","openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.67.0",-- "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.34.0",++ "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.36.2","pg": "^8.23.0"},"bin": {@@@ -1193,9 +1193,9 @@}},"node_modules/openvibe-shared": {-- "version": "4.34.0",-- "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.34.0",-- "integrity": "sha512-Rj5uRO3MbWkyIYAm+vK4Y94ugDNYEjEgGkYnPjSej4yMqhalpaPCkU96dzphMGiOUGP1PNWsiKhwaBj8tewgBg==",++ "version": "4.36.2",++ "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.36.2",++ "integrity": "sha512-2BGqPZhNr3BhQL/C2pBCBo0zQ4RSu8EX0S2PnZL83Rb5IOZO2BOT/Avdz3AloqORt7X+91kIHLu6T4HPBubmKA==","license": "MIT","dependencies": {"jsonwebtoken": "^9.0.0",@@@ -32,7 +32,7 @@"iovalkey": "^0.4.0","openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.232.0","openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.67.0",-- "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.34.0",++ "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.36.2","pg": "^8.23.0"},"engines": {@@@ -22,6 +22,7 @@ const cache = require('openvibe-shared/cache-policy');const { asyncRouter } = require('./router');const { createDiscoveryRoutes, homeJsonLd, SERVICES_ORIGIN } = require('./discovery');const { html, raw, table, notice } = require('../render/html');++const kit = require('openvibe-shared/kit');const { send } = require('../render/layout');const { markdown } = require('../render/markdown');const placement = require('../domain/harness-placement');@@@ -315,30 +316,41 @@ ${offers.map((o) => html`<h3>${o.name} <code>${o.id}</code></h3>${o.agents.lengtconst repoForgejoUrl = (owner, name) => `${config.git.publicUrl}/${owner}/${name}`;const repoCloneUrl = (owner, name) => `${config.git.publicUrl}/${owner}/${name}.git`;-- function improvePage() {++ // The four steps of every change; the index is step 1, a repository's page is step 2.++ const IMPROVE_STEPS = [++ { label: 'Pick a repository', href: '/improve' },++ { label: 'Describe the change', note: 'the harness runs it on your machine' },++ { label: 'Review the result', note: 'then open the pull request' },++ { label: 'A maintainer reviews', note: 'the owner approves what ships' },++ ];++ const improveSteps = (at) => kit.steps({ label: 'From a change to a pull request', current: at, items: IMPROVE_STEPS });++ const shortName = (m) => m.repository.split('/')[1].replace(/^OpenVibe\./, '');++ const tagline = (m) => (m.site && (m.site.tagline || m.site.what)) || m.name;++++ /** Step 1: pick the repository. One search box (it works without JavaScript: ?q=), then every repository as a row. */++ function improvePage(q = '') {const { running, libraries } = repoGroups();-- const repoCards = (list) => html`<ul class="cards">${list.map((m) => html`<li><a href="/improve/${m.repository}"><strong>${m.repository.split('/')[1]}</strong></a><span>${(m.site && (m.site.tagline || m.site.what)) || m.name}</span></li>`)}</ul>`;++ const needle = String(q || '').trim().toLowerCase().slice(0, 60);++ const hit = (m) => !needle || [m.repository, m.name, tagline(m)].some((t) => String(t || '').toLowerCase().includes(needle));++ const rows = (list) => list.filter(hit).map((m) => kit.row({ href: `/improve/${m.repository}`, title: shortName(m), sub: tagline(m), trail: kit.icon('chevron') }));++ const services = rows(running), libs = rows(libraries);++ const found = services.length + libs.length;++ const lists = !found++ ? kit.empty({ icon: 'search', title: `No repository matches “${needle}”`, text: 'Try a site\'s name (chat, video, games) or what it does.', action: { href: '/improve', label: 'Every repository' } })++ : (services.length ? kit.list(services, { title: `Services · ${services.length}` }) : '') + (libs.length ? kit.list(libs, { title: `Shared libraries · ${libs.length}` }) : '');return {-- agent: true, index: true, cache: PUBLIC_CACHE,++ agent: true, index: !needle, cache: PUBLIC_CACHE,++ styles: ['kit.css'], sharedScripts: ['kit-web.js'],title: 'Improve OpenVibe',description: 'OpenVibe is open source: every service and library is on GitHub. Pick a repository, describe the change, and send a pull request; the harness makes the change on your machine.',-- crumbs: [{ label: 'Improve OpenVibe' }],-- body: html`<h1>Improve OpenVibe</h1>--<p>Every OpenVibe service and library is open source under <a href="${GITHUB}">OpenVibers</a>, and changes arrive as pull requests that the owner approves. Every change takes the same path: repository → change → tests → independent review → pull request, never straight to <code>main</code>. Pick a repository, describe the change, and start a run on your own machine.</p>--<ol class="steps">--<li><strong>Pick a repository</strong> from the lists below.</li>--<li><strong>Describe the change</strong> in plain language; the harness runs the tests your way.</li>--<li><strong>Review the result</strong> on the task page, then open a pull request from the branch it pushed.</li>--<li><strong>A maintainer reviews</strong>; the owner approves what ships. See the <a href="/policy/contributor-ladder">contributor ladder</a>.</li>--</ol>--<p>Before you start: the <a href="/policy/contributing">contributing guide</a> and the <a href="/policy/code-of-conduct">code of conduct</a>. Building your own app? That is <a href="${SERVICES_ORIGIN}">OpenVibe.Services</a>.</p>--<h2>Services that run</h2>--${repoCards(running)}--<h2>Shared libraries</h2>--${repoCards(libraries)}`,++ body: html`${raw(kit.head({ title: 'Improve OpenVibe', sub: 'All open source. Pick one, describe a change.', sticky: false }))}++${raw(improveSteps(0))}++${raw(kit.find({ action: '/improve', name: 'q', value: needle, placeholder: 'Find: chat, video, games…', label: 'Find a repository' }))}++${raw(lists)}++<p class="ovk-muted">Changes arrive as pull requests the owner approves — never straight to <code>main</code>. Before you start: the <a href="/policy/contributing">contributing guide</a>, the <a href="/policy/code-of-conduct">code of conduct</a> and the <a href="/policy/contributor-ladder">contributor ladder</a>. Building your own app instead? That is <a href="${SERVICES_ORIGIN}">OpenVibe.Services</a>.</p>`,};}-- r.get('/improve', (req, res) => page(req, res, improvePage()));++ r.get('/improve', (req, res) => page(req, res, improvePage(req.query && req.query.q)));async function improveRepoPage(owner, name, { signed = false } = {}) {const meta = repoMeta().find((m) => m.repository === `${owner}/${name}`);@@@ -363,23 +375,18 @@ ${forgejoError ? html`<p class="run-notice run-notice-error" role="alert">OpenVi<label class="run-check"><input type="checkbox" name="push" value="1" checked> Push the change to a new branch on OpenVibe Git <small>(needs a token on your machine; never the branch it started from)</small></label><label class="run-check"><input type="checkbox" name="pull_request" value="1" checked> and open a pull request</label><button class="run-btn run-btn-primary" type="submit">Start on my machine</button>--</form>` : html`<p><a class="run-btn run-btn-primary" href="/auth/login?next=${encodeURIComponent(`/improve/${owner}/${name}`)}">Sign in to start a run</a></p>`;++</form>` : html`<p class="ovk-muted">Say what should change, in plain words. The harness makes the change on your machine, runs the tests, and can open the pull request.</p><p>${raw(kit.button({ href: `/auth/login?next=${encodeURIComponent(`/improve/${owner}/${name}`)}`, label: 'Sign in to describe a change', icon: 'user' }))}</p>`;return {agent: true, index: true, cache: PUBLIC_CACHE,title: `Improve ${name}`,description: `Contribute to ${meta.name}: describe a change and run it on your machine through OpenVibe.Codes.`,crumbs: [{ label: 'Improve OpenVibe', href: '/improve' }, { label: meta.name }],-- body: html`<h1>${meta.name}</h1>--<p class="lead">${(meta.site && (meta.site.tagline || meta.site.what)) || meta.name}. The source lives at <a href="${repoForgejoUrl(owner, name)}">${repoForgejoUrl(owner, name)}</a> and is mirrored from <a href="${GITHUB}/${name}">GitHub</a>.</p>--<pre tabindex="0" class="codes-clone"><code>git clone ${repoCloneUrl(owner, name)}</code></pre>--<h2>The path from a change to a pull request</h2>--<ol class="steps">--<li>The harness clones the repository onto <strong>your machine</strong> and checks out the default branch.</li>--<li>The agent makes the change you described and runs the tests.</li>--<li>If you asked it to push, it commits to a new branch and opens a pull request on OpenVibe Git.</li>--<li>You review the result, then the maintainers review the pull request.</li>--</ol>--${form}`,++ styles: ['kit.css'], sharedScripts: ['kit-web.js'],++ body: html`${raw(kit.head({ title: meta.name, sub: tagline(meta), sticky: false }))}++${raw(improveSteps(1))}++${form}++${raw(kit.fold({ title: 'How the run works', body: '<ol class="steps"><li>The harness clones the repository onto <strong>your machine</strong> and checks out the default branch.</li><li>The agent makes the change you described and runs the tests.</li><li>If you asked it to push, it commits to a new branch and opens a pull request on OpenVibe Git.</li><li>You review the result, then the maintainers review the pull request.</li></ol>' }))}++${raw(kit.fold({ title: 'Clone it yourself', meta: 'git clone', body: `${kit.snippet({ code: `git clone ${repoCloneUrl(owner, name)}`, id: 'clone-cmd' })}<p class="ovk-muted">Browse it at <a href="${kit.esc(repoForgejoUrl(owner, name))}">${kit.esc(repoForgejoUrl(owner, name))}</a> or on <a href="${kit.esc(`${GITHUB}/${name}`)}">GitHub</a>.</p>` }))}`,};}r.get('/improve/:owner/:name', async (req, res) => {@@@ -29,6 +29,26 @@ const { boot, check, done } = require('./helpers/boot');assert.ok(r.text.includes('OpenVibe.Codes'));});++ await check('the landing is step 1 of 4, and one search box narrows the repositories (no JavaScript needed)', async () => {++ const all = await t.get('/improve');++ assert.ok(all.text.includes('<nav class="ovk-steps"') && all.text.includes('Step 1 of 4') && all.text.includes('aria-current="step"'), 'the steps');++ assert.ok(all.text.includes('<form class="ovk-find"') && all.text.includes('name="q"'), 'the search box');++ assert.ok(all.text.includes('Services · ') && all.text.includes('Shared libraries · '), 'both groups, counted');++ const some = await t.get('/improve?q=codes');++ assert.ok(some.text.includes('href="/improve/OpenVibers/OpenVibe.Codes"'), 'a match stays');++ assert.ok(!some.text.includes('href="/improve/OpenVibers/OpenVibe.Shared"'), 'a non-match goes');++ assert.ok(/<meta name="robots" content="noindex/.test(some.text), 'a search result is not indexed');++ const none = await t.get('/improve?q=zzzz-nothing');++ assert.ok(none.text.includes('No repository matches') && none.text.includes('href="/improve"'), 'an empty search says so and offers every repository');++ });++++ await check('a repository page is step 2: describe the change; the clone command and the run explanation fold', async () => {++ const r = await t.get('/improve/OpenVibers/OpenVibe.Codes', { as: ada });++ assert.ok(r.text.includes('Step 2 of 4') && r.text.includes('href="/improve"'), 'step 2, with step 1 linking back');++ assert.ok(r.text.includes('Clone it yourself') && r.text.includes('git clone '), 'the clone command, folded');++ assert.ok(r.text.indexOf('name="prompt"') < r.text.indexOf('How the run works'), 'the form comes before the explanation');++ });++await check('a repository page shows the guided path and asks guests to sign in', async () => {const r = await t.get('/improve/OpenVibers/OpenVibe.Codes');assert.strictEqual(r.status, 200);
@@ -160,5 +160,5 @@ Part of the [OpenVibe network](https://openvibe.network). Built in the open by [
@@ -160,5 +160,5 @@ Part of the [OpenVibe network](https://openvibe.network). Built in the open by [
<!-- versions:start -->
<!-- versions:start -->
- openvibe-contracts: v0.232.0
- openvibe-contracts: v0.232.0
- openvibe-sdk: v0.67.0
- openvibe-sdk: v0.67.0
-- openvibe-shared: v4.33.0
+- openvibe-shared: v4.36.2
<!-- versions:end -->
<!-- versions:end -->
@@ -9,11 +9,11 @@
@@ -9,11 +9,11 @@
"domain": "openvibe.codes",
"domain": "openvibe.codes",
"contracts": "openvibe-contracts v0.232.0",
"contracts": "openvibe-contracts v0.232.0",
"sdk": "openvibe-sdk v0.67.0",
"sdk": "openvibe-sdk v0.67.0",
- "shared": "openvibe-shared v4.33.0",
+ "shared": "openvibe-shared v4.36.2",
"packages": [
"packages": [
"openvibe-contracts v0.232.0",
"openvibe-contracts v0.232.0",
"openvibe-sdk v0.67.0",
"openvibe-sdk v0.67.0",
- "openvibe-shared v4.33.0"
+ "openvibe-shared v4.36.2"
],
],
"summary": "Public at https://openvibe.codes (sudo ovhost deploy codes; unit openvibe-codes on 127.0.0.1:4900). Since 2026-10-08 Codes is the open coding-agent harness: openvibe-codes (a command-line tool and Node.js module, v0.2.0) runs a coding task on Claude Code, Codex, OpenCode, Command Code, Aider, the DeepSeek API or a person's own OpenAI-compatible model on their machine, hands it to the next agent when one fails or goes quiet, and keeps every run; the site carries the agent catalog with a public routing API, getting started, Improve OpenVibe and the community documents. The developer console that used to live here moved to OpenVibe.Services (https://openvibe.services) and every one of its addresses answers a permanent redirect there.",
"summary": "Public at https://openvibe.codes (sudo ovhost deploy codes; unit openvibe-codes on 127.0.0.1:4900). Since 2026-10-08 Codes is the open coding-agent harness: openvibe-codes (a command-line tool and Node.js module, v0.2.0) runs a coding task on Claude Code, Codex, OpenCode, Command Code, Aider, the DeepSeek API or a person's own OpenAI-compatible model on their machine, hands it to the next agent when one fails or goes quiet, and keeps every run; the site carries the agent catalog with a public routing API, getting started, Improve OpenVibe and the community documents. The developer console that used to live here moved to OpenVibe.Services (https://openvibe.services) and every one of its addresses answers a permanent redirect there.",
"works": [
"works": [
@@ -52,5 +52,5 @@
@@ -52,5 +52,5 @@
"run a coding task on your own machine from any browser (/run; openvibe-codes job through OpenVibe Run and the person's own OpenVibe Node)",
"run a coding task on your own machine from any browser (/run; openvibe-codes job through OpenVibe Run and the person's own OpenVibe Node)",
"user JSON API for signed-in people to list, start, read and cancel their own tasks"
"user JSON API for signed-in people to list, start, read and cancel their own tasks"
],
],
- "updated": "2026-10-28"
+ "updated": "2026-10-29"
}
}
@@ -17,7 +17,7 @@
@@ -17,7 +17,7 @@
"iovalkey": "^0.4.0",
"iovalkey": "^0.4.0",
"openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.232.0",
"openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.232.0",
"openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.67.0",
"openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.67.0",
- "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.34.0",
+ "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.36.2",
"pg": "^8.23.0"
"pg": "^8.23.0"
},
},
"bin": {
"bin": {
@@ -1193,9 +1193,9 @@
@@ -1193,9 +1193,9 @@
}
}
},
},
"node_modules/openvibe-shared": {
"node_modules/openvibe-shared": {
- "version": "4.34.0",
- "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.34.0",
- "integrity": "sha512-Rj5uRO3MbWkyIYAm+vK4Y94ugDNYEjEgGkYnPjSej4yMqhalpaPCkU96dzphMGiOUGP1PNWsiKhwaBj8tewgBg==",
+ "version": "4.36.2",
+ "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.36.2",
+ "integrity": "sha512-2BGqPZhNr3BhQL/C2pBCBo0zQ4RSu8EX0S2PnZL83Rb5IOZO2BOT/Avdz3AloqORt7X+91kIHLu6T4HPBubmKA==",
"license": "MIT",
"license": "MIT",
"dependencies": {
"dependencies": {
"jsonwebtoken": "^9.0.0",
"jsonwebtoken": "^9.0.0",
@@ -32,7 +32,7 @@
@@ -32,7 +32,7 @@
"iovalkey": "^0.4.0",
"iovalkey": "^0.4.0",
"openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.232.0",
"openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.232.0",
"openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.67.0",
"openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.67.0",
- "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.34.0",
+ "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.36.2",
"pg": "^8.23.0"
"pg": "^8.23.0"
},
},
"engines": {
"engines": {
@@ -22,6 +22,7 @@ const cache = require('openvibe-shared/cache-policy');
@@ -22,6 +22,7 @@ const cache = require('openvibe-shared/cache-policy');
const { asyncRouter } = require('./router');
const { asyncRouter } = require('./router');
const { createDiscoveryRoutes, homeJsonLd, SERVICES_ORIGIN } = require('./discovery');
const { createDiscoveryRoutes, homeJsonLd, SERVICES_ORIGIN } = require('./discovery');
const { html, raw, table, notice } = require('../render/html');
const { html, raw, table, notice } = require('../render/html');
+const kit = require('openvibe-shared/kit');
const { send } = require('../render/layout');
const { send } = require('../render/layout');
const { markdown } = require('../render/markdown');
const { markdown } = require('../render/markdown');
const placement = require('../domain/harness-placement');
const placement = require('../domain/harness-placement');
@@ -315,30 +316,41 @@ ${offers.map((o) => html`<h3>${o.name} <code>${o.id}</code></h3>${o.agents.lengt
@@ -315,30 +316,41 @@ ${offers.map((o) => html`<h3>${o.name} <code>${o.id}</code></h3>${o.agents.lengt
const repoForgejoUrl = (owner, name) => `${config.git.publicUrl}/${owner}/${name}`;
const repoForgejoUrl = (owner, name) => `${config.git.publicUrl}/${owner}/${name}`;
const repoCloneUrl = (owner, name) => `${config.git.publicUrl}/${owner}/${name}.git`;
const repoCloneUrl = (owner, name) => `${config.git.publicUrl}/${owner}/${name}.git`;
- function improvePage() {
+ // The four steps of every change; the index is step 1, a repository's page is step 2.
+ const IMPROVE_STEPS = [
+ { label: 'Pick a repository', href: '/improve' },
+ { label: 'Describe the change', note: 'the harness runs it on your machine' },
+ { label: 'Review the result', note: 'then open the pull request' },
+ { label: 'A maintainer reviews', note: 'the owner approves what ships' },
+ ];
+ const improveSteps = (at) => kit.steps({ label: 'From a change to a pull request', current: at, items: IMPROVE_STEPS });
+ const shortName = (m) => m.repository.split('/')[1].replace(/^OpenVibe\./, '');
+ const tagline = (m) => (m.site && (m.site.tagline || m.site.what)) || m.name;
+
+ /** Step 1: pick the repository. One search box (it works without JavaScript: ?q=), then every repository as a row. */
+ function improvePage(q = '') {
const { running, libraries } = repoGroups();
const { running, libraries } = repoGroups();
- const repoCards = (list) => html`<ul class="cards">${list.map((m) => html`<li><a href="/improve/${m.repository}"><strong>${m.repository.split('/')[1]}</strong></a><span>${(m.site && (m.site.tagline || m.site.what)) || m.name}</span></li>`)}</ul>`;
+ const needle = String(q || '').trim().toLowerCase().slice(0, 60);
+ const hit = (m) => !needle || [m.repository, m.name, tagline(m)].some((t) => String(t || '').toLowerCase().includes(needle));
+ const rows = (list) => list.filter(hit).map((m) => kit.row({ href: `/improve/${m.repository}`, title: shortName(m), sub: tagline(m), trail: kit.icon('chevron') }));
+ const services = rows(running), libs = rows(libraries);
+ const found = services.length + libs.length;
+ const lists = !found
+ ? kit.empty({ icon: 'search', title: `No repository matches “${needle}”`, text: 'Try a site\'s name (chat, video, games) or what it does.', action: { href: '/improve', label: 'Every repository' } })
+ : (services.length ? kit.list(services, { title: `Services · ${services.length}` }) : '') + (libs.length ? kit.list(libs, { title: `Shared libraries · ${libs.length}` }) : '');
return {
return {
- agent: true, index: true, cache: PUBLIC_CACHE,
+ agent: true, index: !needle, cache: PUBLIC_CACHE,
+ styles: ['kit.css'], sharedScripts: ['kit-web.js'],
title: 'Improve OpenVibe',
title: 'Improve OpenVibe',
description: 'OpenVibe is open source: every service and library is on GitHub. Pick a repository, describe the change, and send a pull request; the harness makes the change on your machine.',
description: 'OpenVibe is open source: every service and library is on GitHub. Pick a repository, describe the change, and send a pull request; the harness makes the change on your machine.',
- crumbs: [{ label: 'Improve OpenVibe' }],
- body: html`<h1>Improve OpenVibe</h1>
-<p>Every OpenVibe service and library is open source under <a href="${GITHUB}">OpenVibers</a>, and changes arrive as pull requests that the owner approves. Every change takes the same path: repository → change → tests → independent review → pull request, never straight to <code>main</code>. Pick a repository, describe the change, and start a run on your own machine.</p>
-<ol class="steps">
-<li><strong>Pick a repository</strong> from the lists below.</li>
-<li><strong>Describe the change</strong> in plain language; the harness runs the tests your way.</li>
-<li><strong>Review the result</strong> on the task page, then open a pull request from the branch it pushed.</li>
-<li><strong>A maintainer reviews</strong>; the owner approves what ships. See the <a href="/policy/contributor-ladder">contributor ladder</a>.</li>
-</ol>
-<p>Before you start: the <a href="/policy/contributing">contributing guide</a> and the <a href="/policy/code-of-conduct">code of conduct</a>. Building your own app? That is <a href="${SERVICES_ORIGIN}">OpenVibe.Services</a>.</p>
-<h2>Services that run</h2>
-${repoCards(running)}
-<h2>Shared libraries</h2>
-${repoCards(libraries)}`,
+ body: html`${raw(kit.head({ title: 'Improve OpenVibe', sub: 'All open source. Pick one, describe a change.', sticky: false }))}
+${raw(improveSteps(0))}
+${raw(kit.find({ action: '/improve', name: 'q', value: needle, placeholder: 'Find: chat, video, games…', label: 'Find a repository' }))}
+${raw(lists)}
+<p class="ovk-muted">Changes arrive as pull requests the owner approves — never straight to <code>main</code>. Before you start: the <a href="/policy/contributing">contributing guide</a>, the <a href="/policy/code-of-conduct">code of conduct</a> and the <a href="/policy/contributor-ladder">contributor ladder</a>. Building your own app instead? That is <a href="${SERVICES_ORIGIN}">OpenVibe.Services</a>.</p>`,
};
};
}
}
- r.get('/improve', (req, res) => page(req, res, improvePage()));
+ r.get('/improve', (req, res) => page(req, res, improvePage(req.query && req.query.q)));
async function improveRepoPage(owner, name, { signed = false } = {}) {
async function improveRepoPage(owner, name, { signed = false } = {}) {
const meta = repoMeta().find((m) => m.repository === `${owner}/${name}`);
const meta = repoMeta().find((m) => m.repository === `${owner}/${name}`);
@@ -363,23 +375,18 @@ ${forgejoError ? html`<p class="run-notice run-notice-error" role="alert">OpenVi
@@ -363,23 +375,18 @@ ${forgejoError ? html`<p class="run-notice run-notice-error" role="alert">OpenVi
<label class="run-check"><input type="checkbox" name="push" value="1" checked> Push the change to a new branch on OpenVibe Git <small>(needs a token on your machine; never the branch it started from)</small></label>
<label class="run-check"><input type="checkbox" name="push" value="1" checked> Push the change to a new branch on OpenVibe Git <small>(needs a token on your machine; never the branch it started from)</small></label>
<label class="run-check"><input type="checkbox" name="pull_request" value="1" checked> and open a pull request</label>
<label class="run-check"><input type="checkbox" name="pull_request" value="1" checked> and open a pull request</label>
<button class="run-btn run-btn-primary" type="submit">Start on my machine</button>
<button class="run-btn run-btn-primary" type="submit">Start on my machine</button>
-</form>` : html`<p><a class="run-btn run-btn-primary" href="/auth/login?next=${encodeURIComponent(`/improve/${owner}/${name}`)}">Sign in to start a run</a></p>`;
+</form>` : html`<p class="ovk-muted">Say what should change, in plain words. The harness makes the change on your machine, runs the tests, and can open the pull request.</p><p>${raw(kit.button({ href: `/auth/login?next=${encodeURIComponent(`/improve/${owner}/${name}`)}`, label: 'Sign in to describe a change', icon: 'user' }))}</p>`;
return {
return {
agent: true, index: true, cache: PUBLIC_CACHE,
agent: true, index: true, cache: PUBLIC_CACHE,
title: `Improve ${name}`,
title: `Improve ${name}`,
description: `Contribute to ${meta.name}: describe a change and run it on your machine through OpenVibe.Codes.`,
description: `Contribute to ${meta.name}: describe a change and run it on your machine through OpenVibe.Codes.`,
crumbs: [{ label: 'Improve OpenVibe', href: '/improve' }, { label: meta.name }],
crumbs: [{ label: 'Improve OpenVibe', href: '/improve' }, { label: meta.name }],
- body: html`<h1>${meta.name}</h1>
-<p class="lead">${(meta.site && (meta.site.tagline || meta.site.what)) || meta.name}. The source lives at <a href="${repoForgejoUrl(owner, name)}">${repoForgejoUrl(owner, name)}</a> and is mirrored from <a href="${GITHUB}/${name}">GitHub</a>.</p>
-<pre tabindex="0" class="codes-clone"><code>git clone ${repoCloneUrl(owner, name)}</code></pre>
-<h2>The path from a change to a pull request</h2>
-<ol class="steps">
-<li>The harness clones the repository onto <strong>your machine</strong> and checks out the default branch.</li>
-<li>The agent makes the change you described and runs the tests.</li>
-<li>If you asked it to push, it commits to a new branch and opens a pull request on OpenVibe Git.</li>
-<li>You review the result, then the maintainers review the pull request.</li>
-</ol>
-${form}`,
+ styles: ['kit.css'], sharedScripts: ['kit-web.js'],
+ body: html`${raw(kit.head({ title: meta.name, sub: tagline(meta), sticky: false }))}
+${raw(improveSteps(1))}
+${form}
+${raw(kit.fold({ title: 'How the run works', body: '<ol class="steps"><li>The harness clones the repository onto <strong>your machine</strong> and checks out the default branch.</li><li>The agent makes the change you described and runs the tests.</li><li>If you asked it to push, it commits to a new branch and opens a pull request on OpenVibe Git.</li><li>You review the result, then the maintainers review the pull request.</li></ol>' }))}
+${raw(kit.fold({ title: 'Clone it yourself', meta: 'git clone', body: `${kit.snippet({ code: `git clone ${repoCloneUrl(owner, name)}`, id: 'clone-cmd' })}<p class="ovk-muted">Browse it at <a href="${kit.esc(repoForgejoUrl(owner, name))}">${kit.esc(repoForgejoUrl(owner, name))}</a> or on <a href="${kit.esc(`${GITHUB}/${name}`)}">GitHub</a>.</p>` }))}`,
};
};
}
}
r.get('/improve/:owner/:name', async (req, res) => {
r.get('/improve/:owner/:name', async (req, res) => {
@@ -29,6 +29,26 @@ const { boot, check, done } = require('./helpers/boot');
@@ -29,6 +29,26 @@ const { boot, check, done } = require('./helpers/boot');
assert.ok(r.text.includes('OpenVibe.Codes'));
assert.ok(r.text.includes('OpenVibe.Codes'));
});
});
+ await check('the landing is step 1 of 4, and one search box narrows the repositories (no JavaScript needed)', async () => {
+ const all = await t.get('/improve');
+ assert.ok(all.text.includes('<nav class="ovk-steps"') && all.text.includes('Step 1 of 4') && all.text.includes('aria-current="step"'), 'the steps');
+ assert.ok(all.text.includes('<form class="ovk-find"') && all.text.includes('name="q"'), 'the search box');
+ assert.ok(all.text.includes('Services · ') && all.text.includes('Shared libraries · '), 'both groups, counted');
+ const some = await t.get('/improve?q=codes');
+ assert.ok(some.text.includes('href="/improve/OpenVibers/OpenVibe.Codes"'), 'a match stays');
+ assert.ok(!some.text.includes('href="/improve/OpenVibers/OpenVibe.Shared"'), 'a non-match goes');
+ assert.ok(/<meta name="robots" content="noindex/.test(some.text), 'a search result is not indexed');
+ const none = await t.get('/improve?q=zzzz-nothing');
+ assert.ok(none.text.includes('No repository matches') && none.text.includes('href="/improve"'), 'an empty search says so and offers every repository');
+ });
+
+ await check('a repository page is step 2: describe the change; the clone command and the run explanation fold', async () => {
+ const r = await t.get('/improve/OpenVibers/OpenVibe.Codes', { as: ada });
+ assert.ok(r.text.includes('Step 2 of 4') && r.text.includes('href="/improve"'), 'step 2, with step 1 linking back');
+ assert.ok(r.text.includes('Clone it yourself') && r.text.includes('git clone '), 'the clone command, folded');
+ assert.ok(r.text.indexOf('name="prompt"') < r.text.indexOf('How the run works'), 'the form comes before the explanation');
+ });
+
await check('a repository page shows the guided path and asks guests to sign in', async () => {
await check('a repository page shows the guided path and asks guests to sign in', async () => {
const r = await t.get('/improve/OpenVibers/OpenVibe.Codes');
const r = await t.get('/improve/OpenVibers/OpenVibe.Codes');
assert.strictEqual(r.status, 200);
assert.strictEqual(r.status, 200);