openvibers/openvibe.bench Commits
Benchmark page on phones: section chips, compact tasks, one-row runs; no badges on a private benchmark (#42)
Changes
@@@ -493,3 +493,74 @@ html, body { margin: 0; background: var(--bg-primary, #0a0f1c); color: var(--tex.br-prompt-table .rt-col-band { width: 5em; }.br-prompt-table .rt-col-raw { width: 7em; }.br-prompt-table .rt-col-votes { width: 3.2em; }++++/* ---- B11 v2: phone chips for the benchmark page, a compact task list and one-row runs ---- */++.br-task-n { display: none; }++.br-sec-pick { display: none; }++.br-usethis-details { border: 1px solid var(--border); border-radius: 16px; background: var(--bg-card); }++.br-usethis-details > summary { display: flex; align-items: center; gap: 10px; min-height: 48px; padding: 8px 16px; cursor: pointer; list-style: none; }++.br-usethis-details > summary::-webkit-details-marker { display: none; }++.br-usethis-details > summary h2 { margin: 0; }++.br-usethis-details > summary::after { content: "+"; margin-left: auto; font-size: 1.2rem; font-weight: 800; color: var(--room-strong); }++.br-usethis-details[open] > summary::after { content: "\2212"; }++.br-usethis-details > .br-fine { padding: 0 16px; }++.br-usethis-details .br-usethis-box { margin: 12px 16px; }++.br-usethis-details .br-follow { margin: 0 16px 14px; }++++@supports selector(:has(*)) {++ @media (max-width: 640px) {++ .br-sec-pick { display: flex; gap: 8px; overflow-x: auto; margin: 14px -16px 0; padding: 2px 16px 4px; scrollbar-width: none; overscroll-behavior-x: contain; }++ .br-sec-pick::-webkit-scrollbar { display: none; }++ .br-sec-pick input { position: absolute; opacity: 0; width: 1px; height: 1px; pointer-events: none; }++ .br-sec-pick label { display: inline-flex; align-items: center; flex: none; min-height: 44px; padding: 0 16px; border-radius: 999px; border: 1px solid var(--border); color: var(--text-secondary); font-size: .86rem; font-weight: 700; white-space: nowrap; cursor: pointer; }++ .br-sec-pick input:checked + label { color: var(--text-primary); border-color: var(--room-accent); background: color-mix(in srgb, var(--room-accent) 14%, transparent); }++ .br-sec-pick input:focus-visible + label { outline: 2px solid var(--room-accent); outline-offset: 2px; }++ .br-sections > section { display: none; margin-top: 16px; }++ .br-sections:has(#br-view-rank:checked) #br-rank,++ .br-sections:has(#br-view-tasks:checked) #br-tasks,++ .br-sections:has(#br-view-runs:checked) #br-runs { display: block; }++++ /* Tasks: one compact line each, number and text, no card, two lines at most. */++ #br-tasks .br-tasks { gap: 0; margin-top: 4px; }++ #br-tasks .br-task { display: grid; grid-template-columns: 1.35em minmax(0, 1fr); gap: 8px; align-items: baseline; padding: 7px 0; border: 0; border-bottom: 1px solid var(--border); border-radius: 0; background: transparent; }++ #br-tasks .br-task:last-child { border-bottom: 0; }++ #br-tasks .br-task-id { display: none; }++ #br-tasks .br-task-n { display: inline; font-size: .78rem; font-weight: 800; color: var(--text-muted); text-align: right; }++ #br-tasks .br-task p { font-size: .9rem; line-height: 1.35; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }++ #br-tasks .br-task-expected { display: none; }++ #br-tasks .br-task.is-hidden { grid-template-columns: minmax(0, 1fr); }++ #br-tasks .br-task.is-hidden .br-task-id { display: inline-flex; }++++ /* Runs: one row each, with the model, harness, date, score bar and Verified on it. */++ #br-runs .br-runs { gap: 0; margin-top: 4px; }++ #br-runs .br-run { border: 0; border-bottom: 1px solid var(--border); border-radius: 0; background: transparent; padding: 0; gap: 6px; }++ #br-runs .br-run:last-child { border-bottom: 0; }++ #br-runs .br-run-link { min-height: 40px; padding: 5px 0; gap: 8px; }++ #br-runs .br-run-main { display: flex; align-items: baseline; gap: 6px; flex: 1; min-width: 0; }++ #br-runs .br-run-model { font-size: .9rem; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }++ #br-runs .br-run-sub { display: inline; font-size: .74rem; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-width: 0; }++ #br-runs .br-score { min-width: 0; flex: none; flex-wrap: nowrap; }++ #br-runs .br-score-n { font-size: .9rem; }++ #br-runs .br-score-of { font-size: .66rem; }++ #br-runs .br-score .br-bar { flex: 0 0 24px; height: 3px; margin: 0 0 0 3px; align-self: center; }++ #br-runs .br-prov { flex: none; padding: 1px 6px; font-size: .68rem; }++ }++}++.br-fold-toggle { position: absolute; opacity: 0; width: 1px; height: 1px; pointer-events: none; }++.br-fold-h { display: flex; align-items: center; gap: 10px; cursor: pointer; }++.br-fold-mark { margin-left: auto; }++.br-fold-mark::after { content: "+"; font-size: 1.15rem; font-weight: 800; color: var(--room-strong); }++.br-fold-toggle:checked ~ .br-fold-h .br-fold-mark::after { content: "\2212"; }++@media (max-width: 640px) {++ .br-fold-body { display: none; }++ .br-fold-toggle:checked ~ .br-fold-body { display: block; }++}++@media (min-width: 641px) {++ .br-fold-toggle { display: none; }++ .br-fold-h { cursor: default; pointer-events: none; }++ .br-fold-mark { display: none; }++ .br-fold-body { display: block; }++}++@media (max-width: 640px) {++ .br-sections #br-rank .br-chart { display: none; }++}@@@ -69,6 +69,8 @@ async function badgeFor(ctx, benchmarkId, model) {const registry = ctx.registry;const benchmark = await registry.getBenchmark(String(benchmarkId || ''));if (!benchmark) return noData('unknown benchmark');++ // A private benchmark has no publishable badge for anyone, its owner included: the badge is a public URL.++ if (benchmark.visibility === 'private') return noData('private benchmark');const rankings = await registry.rankings({ benchmark: benchmark.id, metric: 'score' });const entries = (rankings && rankings.entries) || [];const row = entries.find((e) => String(e.model) === String(model || ''));@@@ -184,11 +184,14 @@ ${ownEntries.length ? valueChart(ownEntries) : ''}${promptLeaderboard(ownEntries, { emptyText: 'No runs yet. Start it on 3 to 6 models; every run lands here, whatever it scores.' })}</section><section class="br-duo-pane" aria-labelledby="br-public-h">--<div class="bm-head-row"><h2 id="br-public-h">The public board for this kind</h2>${publicBoard && publicBoard.benchmark ? html`<a class="bm-more" href="${benchmarkHref(publicBoard.benchmark.id)}">${publicBoard.benchmark.title}</a>` : ''}</div>++<div class="br-fold br-public-details"><input class="br-fold-toggle" type="checkbox" id="br-public-open"><label class="br-fold-h" for="br-public-open"><h2 id="br-public-h">The public board for this kind</h2><span class="br-fold-mark" aria-hidden="true"></span></label>++<div class="br-fold-body">++${publicBoard && publicBoard.benchmark ? html`<p class="br-fine"><a href="${benchmarkHref(publicBoard.benchmark.id)}">${publicBoard.benchmark.title}</a></p>` : ''}<p class="br-fine">Other people's runs of the public set, ranked the same way. Your prompts are not in it.</p>${publicEntries.length? promptLeaderboard(publicEntries, { emptyText: '' }): empty('No public board yet', 'No public benchmark of this kind has verified runs yet, so there is nothing to stand beside. Your own board still counts your runs.')}++</div></div></section></div></section>`;@@@ -258,7 +261,8 @@ function shareBox({ benchmark, shareUrl = null, error = null }) {? 'Unlisted: anyone with the link can see it. It is not in any list, search, sitemap or the open data.': 'Public: it is in the benchmark list, the sitemap and the open data, with every run published.';return html`<section class="bm-section br-share" id="br-share" aria-labelledby="br-share-h">--<div class="bm-head-row"><h2 id="br-share-h">Sharing</h2><span class="br-chip br-vis">${visibility}</span></div>++<div class="br-fold br-share-details"><input class="br-fold-toggle" type="checkbox" id="br-share-open"><label class="br-fold-h" for="br-share-open"><h2 id="br-share-h">Sharing</h2><span class="br-chip br-vis">${visibility}</span><span class="br-fold-mark" aria-hidden="true"></span></label>++<div class="br-fold-body"><p class="br-fine">${note}</p>${error ? html`<div class="bm-note is-bad" role="alert">${error}</div>` : ''}${shareUrl ? html`<div class="bm-note br-share-now" role="status"><p>Your unlisted link is ready. It is shown once; copy it now. Share it again for a new link; the old one stops working.</p><label class="br-field"><span>The link</span><input class="br-share-link" type="text" readonly value="${shareUrl}" onfocus="this.select()"></label></div>` : ''}@@@ -268,6 +272,7 @@ ${shareUrl ? html`<div class="bm-note br-share-now" role="status"><p>Your unlist<button class="bm-btn bm-btn-quiet" type="submit" name="visibility" value="private">Make private</button></form><p class="br-fine">A person with the unlisted link can read the prompts and the runs. Only you can start a run on it.</p>++</div></div></section>`;}@@@ -332,17 +337,24 @@ function benchmarkPage({ benchmark, definition, version, rankings, metric = 'sco${privateNote}<dl class="br-facts"><div><dt>Tasks</dt><dd>${tasks.length}${hidden ? html` <span class="br-fine">(${hidden} hidden)</span>` : ''}</dd></div><div><dt>Judged by</dt><dd>${judges.length ? judges.map((j) => String(j.type).replace(/_/g, ' ')).join(', ') : '—'}</dd></div><div><dt>Limits</dt><dd>${[limits.ms ? fmtMs(limits.ms) : null, limits.tokens ? `${fmtTokens(limits.tokens)} tokens` : null, limits.usd ? fmtUsd(limits.usd) : null].filter(Boolean).join(' · ') || '—'}</dd></div><div><dt>Verified runs</dt><dd>${entries.reduce((n, e) => n + e.runs, 0)}</dd></div></dl>${owner ? shareBox({ benchmark, shareUrl: share && share.shareUrl, error: share && share.error }) : ''}++<div class="br-sections">++<div class="br-sec-pick" role="radiogroup" aria-label="Benchmark sections">++<input type="radio" name="br-view" id="br-view-rank" value="rank" checked><label for="br-view-rank">Rankings</label>++<input type="radio" name="br-view" id="br-view-tasks" value="tasks"><label for="br-view-tasks">Tasks</label>++<input type="radio" name="br-view" id="br-view-runs" value="runs"><label for="br-view-runs">Runs</label>++</div>${ownBoards? promptBoards(ownBoards): html`<section class="bm-section" id="br-rank" aria-labelledby="br-rank-h"><div class="bm-head-row"><h2 id="br-rank-h">Rankings</h2>${metricTabs(base, metric)}</div>${valueChart(entries)}${entries.length ? rankingsTable(entries, metric) : empty('No verified runs yet', 'A run counts here once openvibe.codes, Run or a Node worker ran it and published it. Submitted runs are listed below but never ranked.')}</section>`}--<section class="bm-section" aria-labelledby="br-tasks-h"><div class="bm-head-row"><h2 id="br-tasks-h">Tasks</h2><span class="bm-count">${tasks.length}</span></div>--<ol class="br-tasks">${shown.map((t) => html`<li class="br-task"><span class="br-task-id br-mono">${t.id}</span><p>${t.prompt}</p>${t.expected && t.expected.answer ? html`<p class="br-fine">Expected: ${String(t.expected.answer).slice(0, 300)}</p>` : ''}</li>`)}${hidden ? html`<li class="br-task is-hidden"><span class="br-task-id br-mono">${plural(hidden, 'task')}</span><p>Held out: only the executor sees ${hidden === 1 ? 'it' : 'them'}, so nobody can tune for the answer.</p></li>` : ''}</ol></section>--<section class="bm-section" aria-labelledby="br-latest-h"><div class="bm-head-row"><h2 id="br-latest-h">Latest runs</h2>${runs.length > 1 ? html`<a class="bm-more" href="${compareHref(runs.slice(0, 2).map((r) => r.id))}">Compare the newest two</a>` : ''}</div>++<section class="bm-section" id="br-tasks" aria-labelledby="br-tasks-h"><div class="bm-head-row"><h2 id="br-tasks-h">Tasks</h2><span class="bm-count">${tasks.length}</span></div>++<ol class="br-tasks">${shown.map((t, i) => html`<li class="br-task"><span class="br-task-n">${i + 1}</span><span class="br-task-id br-mono">${t.id}</span><p>${t.prompt}</p>${t.expected && t.expected.answer ? html`<p class="br-fine br-task-expected">Expected: ${String(t.expected.answer).slice(0, 300)}</p>` : ''}</li>`)}${hidden ? html`<li class="br-task is-hidden"><span class="br-task-id br-mono">${plural(hidden, 'task')}</span><p>Held out: only the executor sees ${hidden === 1 ? 'it' : 'them'}, so nobody can tune for the answer.</p></li>` : ''}</ol></section>++<section class="bm-section" id="br-runs" aria-labelledby="br-latest-h"><div class="bm-head-row"><h2 id="br-latest-h">Latest runs</h2>${runs.length > 1 ? html`<a class="bm-more" href="${compareHref(runs.slice(0, 2).map((r) => r.id))}">Compare the newest two</a>` : ''}</div>${runs.length ? runsList(runs) : empty('No runs yet', 'The first run shows up here, verified or submitted.')}</section>--${benchmarkUseThis({ baseUrl, benchmark, entries, viewer })}++</div>++${benchmark.visibility === 'private' ? '' : benchmarkUseThis({ baseUrl, benchmark, entries, viewer })}<p class="br-fine br-api">The same data as JSON: <a href="/api/v1/benchmarks/${encodeURIComponent(benchmark.id)}">definition</a> · <a href="/api/v1/rankings?benchmark=${encodeURIComponent(benchmark.id)}&metric=${metric}">rankings</a></p></div>`;}@@@ -53,11 +53,12 @@ function benchmarkUseThis({ baseUrl, benchmark, entries = [], viewer = null, bacconst s = badgeSnippets(baseUrl, benchmark.id, e.model, benchmark.title);return copyBox({ heading: e.model, pairs: [{ label: 'Markdown', value: s.markdown }, { label: 'HTML', value: s.html }] });});-- return html`<section class="bm-section" aria-labelledby="br-use-h"><h2 id="br-use-h">Use this</h2>++ // Closed by default: the badge snippets are useful, but they are not the page's reason to exist.++ return html`<section class="bm-section br-usethis" aria-labelledby="br-use-h"><details class="br-usethis-details"><summary><h2 id="br-use-h">Badges and embeds</h2></summary><p class="br-fine">A badge for your README, and a watch that tells you when this benchmark moves.</p>${boxes}${followForm({ benchmarkId: benchmark.id, back: back || ('/benchmarks/' + benchmark.id), signedIn: Boolean(viewer && viewer.kind === 'user') })}--</section>`;++</details></section>`;}/** The model page: a badge for each benchmark the model ran, and a watch for each. */@@@ -24,6 +24,23 @@ const { badgeSvg } = require('../server/http/badge');const t = await boot();const list = (await t.get('/api/v1/benchmarks')).json();const benchmark = (list.benchmarks || list)[0];++ const alice = t.network.addUser('alice');++ const publisher = t.network.serviceToken({ sub: 'svc:codes', audience: 'openvibe.bench', cap: ['bench.run.publish', 'bench.benchmark.write'] });++ const madePrivate = await t.get('/api/v1/benchmarks', { as: alice, json: {++ title: 'Private badge arena', description: 'Not for publication.', kind: 'design.page', tags: [], visibility: 'private',++ tasks: [{ id: 't1', prompt: 'A private task.', files: [], expected: null, holdout: false }],++ judges: [{ type: 'human_pairwise', version: '1.0', config: {} }], scoring: { method: 'mean', scale: { min: 0, max: 100 } },++ limits: { ms: 60000, tokens: 0, usd: 0 }, matrix: { models: [], harnesses: [] },++ } });++ assert.strictEqual(madePrivate.status, 201, madePrivate.text);++ const privateId = madePrivate.json().benchmark.id;++ assert.strictEqual((await t.get(`/api/v1/benchmarks/${privateId}/versions`, { as: alice, json: {} })).status, 201);++ assert.strictEqual((await t.get('/api/v1/runs', { bearer: publisher, json: {++ benchmark: `${privateId}@1`, model: 'model-private', route: { provider: 'acme' }, provider: 'acme',++ harness: { name: 'claude-code', version: '1.0' }, settings: {}, executor: { kind: 'codes', id: 'tsk_badge' },++ tasks: [{ task_id: 't1', score: 90, cost_usd: 0.01, ms: 1000, tokens: 100, artifacts: [] }],++ score: 90, cost_usd: 0.01, ms: 1000, tokens: 100,++ } })).status, 201);await check('an unknown benchmark gets a grey badge and a 404', async () => {const r = await t.get('/badge/bmk_nope/nope.svg');@@@ -36,6 +53,12 @@ const { badgeSvg } = require('../server/http/badge');assert.ok(r.text.includes('<svg'), 'and it is still a picture');});++ await check('a private benchmark has no badge, even when it has runs', async () => {++ const r = await t.get(`/badge/${privateId}/model-private.svg`);++ assert.strictEqual(r.status, 404, r.text.slice(0, 200));++ assert.ok(r.text.includes('private benchmark'), 'the badge says why');++ });++await check('a model with no runs gets a grey badge and a 404', async () => {const r = await t.get('/badge/' + benchmark.id + '/no-such-model.svg');assert.strictEqual(r.status, 404);@@@ -80,6 +80,8 @@ function runDoc(benchmark, over = {}) {await publish(`${pub.id}@${pub.version}`, { model: 'model-worse', score: 55, cost_usd: 0.04, harness: { name: 'opencode', version: '1.4.2' } });const priv = await create({ title: 'Alice unlisted prompts', visibility: 'unlisted' }, alice);await publish(`${priv.id}@${priv.version}`, { model: 'model-best', score: 12, cost_usd: 0.01 });++ const privatePage = await create({ title: 'Alice private prompts', visibility: 'private', tags: ['your-prompts'] }, alice);++ await publish(`${privatePage.id}@${privatePage.version}`, { model: 'model-best', score: 77, cost_usd: 0.01 });await check('the benchmarks page lists the public benchmarks and escapes their titles', async () => {const res = await t.get('/benchmarks');@@@ -101,6 +103,34 @@ function runDoc(benchmark, over = {}) {assert.ok(value.text.includes('aria-current="page"') && value.text.includes('/ $'), 'the value tab is current and shows score per dollar');});++ await check('the phone sections are chips, one per section, with Rankings picked first', async () => {++ const res = await t.get(`/benchmarks/${pub.id}`);++ const chips = [...res.text.matchAll(/<input type="radio" name="br-view" id="br-view-([a-z]+)" value="[a-z]+"( checked)?>/g)];++ assert.deepStrictEqual(chips.map((m) => m[1]), ['rank', 'tasks', 'runs'], 'one chip per section, in order');++ assert.deepStrictEqual(chips.filter((m) => m[2]).map((m) => m[1]), ['rank'], 'Rankings is picked first');++ assert.match(res.text, /<div class="br-sections">[\s\S]*id="br-rank"[\s\S]*id="br-tasks"[\s\S]*id="br-runs"[\s\S]*<\/div>/, 'every section stays in the page');++ const css = await t.get('/css/bench.css');++ assert.ok(css.text.includes('@supports selector(:has(*))'), 'the chips are gated on :has()');++ assert.ok(css.text.includes('.br-sections:has(#br-view-rank:checked) #br-rank'), 'Rankings shows when picked');++ assert.ok(css.text.includes('.br-sections:has(#br-view-tasks:checked) #br-tasks'), 'Tasks shows when picked');++ assert.ok(css.text.includes('.br-sections:has(#br-view-runs:checked) #br-runs'), 'Runs shows when picked');++ });++++ await check('badges and embeds are one closed details on a public page, and absent on a private one', async () => {++ const pubPage = await t.get(`/benchmarks/${pub.id}`);++ assert.ok(pubPage.text.includes('<details class="br-usethis-details">'), 'one details');++ assert.ok(!pubPage.text.includes('<details class="br-usethis-details" open'), 'closed by default');++ assert.ok(pubPage.text.includes('Badges and embeds'), 'the summary');++ assert.ok(pubPage.text.includes('model-best'), 'the models are inside');++ const mine = await t.get(`/benchmarks/${privatePage.id}`, { as: alice });++ assert.strictEqual(mine.status, 200, mine.text.slice(0, 200));++ assert.ok(!mine.text.includes('Badges and embeds'), 'no badge section on a private benchmark');++ assert.ok(!mine.text.includes('br-usethis'), 'no use-this markup at all');++ const badge = await t.get(`/badge/${privatePage.id}/model-best.svg`);++ assert.strictEqual(badge.status, 404, badge.text.slice(0, 200));++ assert.ok(badge.text.includes('private benchmark'), 'the badge says why');++ });++await check('a run page shows the environment, the score, the provenance and the outputs', async () => {const res = await t.get(`/runs/${best}`);assert.strictEqual(res.status, 200, res.text.slice(0, 300));
@@ -493,3 +493,74 @@ html, body { margin: 0; background: var(--bg-primary, #0a0f1c); color: var(--tex
@@ -493,3 +493,74 @@ html, body { margin: 0; background: var(--bg-primary, #0a0f1c); color: var(--tex
.br-prompt-table .rt-col-band { width: 5em; }
.br-prompt-table .rt-col-band { width: 5em; }
.br-prompt-table .rt-col-raw { width: 7em; }
.br-prompt-table .rt-col-raw { width: 7em; }
.br-prompt-table .rt-col-votes { width: 3.2em; }
.br-prompt-table .rt-col-votes { width: 3.2em; }
+
+/* ---- B11 v2: phone chips for the benchmark page, a compact task list and one-row runs ---- */
+.br-task-n { display: none; }
+.br-sec-pick { display: none; }
+.br-usethis-details { border: 1px solid var(--border); border-radius: 16px; background: var(--bg-card); }
+.br-usethis-details > summary { display: flex; align-items: center; gap: 10px; min-height: 48px; padding: 8px 16px; cursor: pointer; list-style: none; }
+.br-usethis-details > summary::-webkit-details-marker { display: none; }
+.br-usethis-details > summary h2 { margin: 0; }
+.br-usethis-details > summary::after { content: "+"; margin-left: auto; font-size: 1.2rem; font-weight: 800; color: var(--room-strong); }
+.br-usethis-details[open] > summary::after { content: "\2212"; }
+.br-usethis-details > .br-fine { padding: 0 16px; }
+.br-usethis-details .br-usethis-box { margin: 12px 16px; }
+.br-usethis-details .br-follow { margin: 0 16px 14px; }
+
+@supports selector(:has(*)) {
+ @media (max-width: 640px) {
+ .br-sec-pick { display: flex; gap: 8px; overflow-x: auto; margin: 14px -16px 0; padding: 2px 16px 4px; scrollbar-width: none; overscroll-behavior-x: contain; }
+ .br-sec-pick::-webkit-scrollbar { display: none; }
+ .br-sec-pick input { position: absolute; opacity: 0; width: 1px; height: 1px; pointer-events: none; }
+ .br-sec-pick label { display: inline-flex; align-items: center; flex: none; min-height: 44px; padding: 0 16px; border-radius: 999px; border: 1px solid var(--border); color: var(--text-secondary); font-size: .86rem; font-weight: 700; white-space: nowrap; cursor: pointer; }
+ .br-sec-pick input:checked + label { color: var(--text-primary); border-color: var(--room-accent); background: color-mix(in srgb, var(--room-accent) 14%, transparent); }
+ .br-sec-pick input:focus-visible + label { outline: 2px solid var(--room-accent); outline-offset: 2px; }
+ .br-sections > section { display: none; margin-top: 16px; }
+ .br-sections:has(#br-view-rank:checked) #br-rank,
+ .br-sections:has(#br-view-tasks:checked) #br-tasks,
+ .br-sections:has(#br-view-runs:checked) #br-runs { display: block; }
+
+ /* Tasks: one compact line each, number and text, no card, two lines at most. */
+ #br-tasks .br-tasks { gap: 0; margin-top: 4px; }
+ #br-tasks .br-task { display: grid; grid-template-columns: 1.35em minmax(0, 1fr); gap: 8px; align-items: baseline; padding: 7px 0; border: 0; border-bottom: 1px solid var(--border); border-radius: 0; background: transparent; }
+ #br-tasks .br-task:last-child { border-bottom: 0; }
+ #br-tasks .br-task-id { display: none; }
+ #br-tasks .br-task-n { display: inline; font-size: .78rem; font-weight: 800; color: var(--text-muted); text-align: right; }
+ #br-tasks .br-task p { font-size: .9rem; line-height: 1.35; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
+ #br-tasks .br-task-expected { display: none; }
+ #br-tasks .br-task.is-hidden { grid-template-columns: minmax(0, 1fr); }
+ #br-tasks .br-task.is-hidden .br-task-id { display: inline-flex; }
+
+ /* Runs: one row each, with the model, harness, date, score bar and Verified on it. */
+ #br-runs .br-runs { gap: 0; margin-top: 4px; }
+ #br-runs .br-run { border: 0; border-bottom: 1px solid var(--border); border-radius: 0; background: transparent; padding: 0; gap: 6px; }
+ #br-runs .br-run:last-child { border-bottom: 0; }
+ #br-runs .br-run-link { min-height: 40px; padding: 5px 0; gap: 8px; }
+ #br-runs .br-run-main { display: flex; align-items: baseline; gap: 6px; flex: 1; min-width: 0; }
+ #br-runs .br-run-model { font-size: .9rem; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
+ #br-runs .br-run-sub { display: inline; font-size: .74rem; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-width: 0; }
+ #br-runs .br-score { min-width: 0; flex: none; flex-wrap: nowrap; }
+ #br-runs .br-score-n { font-size: .9rem; }
+ #br-runs .br-score-of { font-size: .66rem; }
+ #br-runs .br-score .br-bar { flex: 0 0 24px; height: 3px; margin: 0 0 0 3px; align-self: center; }
+ #br-runs .br-prov { flex: none; padding: 1px 6px; font-size: .68rem; }
+ }
+}
+.br-fold-toggle { position: absolute; opacity: 0; width: 1px; height: 1px; pointer-events: none; }
+.br-fold-h { display: flex; align-items: center; gap: 10px; cursor: pointer; }
+.br-fold-mark { margin-left: auto; }
+.br-fold-mark::after { content: "+"; font-size: 1.15rem; font-weight: 800; color: var(--room-strong); }
+.br-fold-toggle:checked ~ .br-fold-h .br-fold-mark::after { content: "\2212"; }
+@media (max-width: 640px) {
+ .br-fold-body { display: none; }
+ .br-fold-toggle:checked ~ .br-fold-body { display: block; }
+}
+@media (min-width: 641px) {
+ .br-fold-toggle { display: none; }
+ .br-fold-h { cursor: default; pointer-events: none; }
+ .br-fold-mark { display: none; }
+ .br-fold-body { display: block; }
+}
+@media (max-width: 640px) {
+ .br-sections #br-rank .br-chart { display: none; }
+}
@@ -69,6 +69,8 @@ async function badgeFor(ctx, benchmarkId, model) {
@@ -69,6 +69,8 @@ async function badgeFor(ctx, benchmarkId, model) {
const registry = ctx.registry;
const registry = ctx.registry;
const benchmark = await registry.getBenchmark(String(benchmarkId || ''));
const benchmark = await registry.getBenchmark(String(benchmarkId || ''));
if (!benchmark) return noData('unknown benchmark');
if (!benchmark) return noData('unknown benchmark');
+ // A private benchmark has no publishable badge for anyone, its owner included: the badge is a public URL.
+ if (benchmark.visibility === 'private') return noData('private benchmark');
const rankings = await registry.rankings({ benchmark: benchmark.id, metric: 'score' });
const rankings = await registry.rankings({ benchmark: benchmark.id, metric: 'score' });
const entries = (rankings && rankings.entries) || [];
const entries = (rankings && rankings.entries) || [];
const row = entries.find((e) => String(e.model) === String(model || ''));
const row = entries.find((e) => String(e.model) === String(model || ''));
@@ -184,11 +184,14 @@ ${ownEntries.length ? valueChart(ownEntries) : ''}
@@ -184,11 +184,14 @@ ${ownEntries.length ? valueChart(ownEntries) : ''}
${promptLeaderboard(ownEntries, { emptyText: 'No runs yet. Start it on 3 to 6 models; every run lands here, whatever it scores.' })}
${promptLeaderboard(ownEntries, { emptyText: 'No runs yet. Start it on 3 to 6 models; every run lands here, whatever it scores.' })}
</section>
</section>
<section class="br-duo-pane" aria-labelledby="br-public-h">
<section class="br-duo-pane" aria-labelledby="br-public-h">
-<div class="bm-head-row"><h2 id="br-public-h">The public board for this kind</h2>${publicBoard && publicBoard.benchmark ? html`<a class="bm-more" href="${benchmarkHref(publicBoard.benchmark.id)}">${publicBoard.benchmark.title}</a>` : ''}</div>
+<div class="br-fold br-public-details"><input class="br-fold-toggle" type="checkbox" id="br-public-open"><label class="br-fold-h" for="br-public-open"><h2 id="br-public-h">The public board for this kind</h2><span class="br-fold-mark" aria-hidden="true"></span></label>
+<div class="br-fold-body">
+${publicBoard && publicBoard.benchmark ? html`<p class="br-fine"><a href="${benchmarkHref(publicBoard.benchmark.id)}">${publicBoard.benchmark.title}</a></p>` : ''}
<p class="br-fine">Other people's runs of the public set, ranked the same way. Your prompts are not in it.</p>
<p class="br-fine">Other people's runs of the public set, ranked the same way. Your prompts are not in it.</p>
${publicEntries.length
${publicEntries.length
? promptLeaderboard(publicEntries, { emptyText: '' })
? promptLeaderboard(publicEntries, { emptyText: '' })
: empty('No public board yet', 'No public benchmark of this kind has verified runs yet, so there is nothing to stand beside. Your own board still counts your runs.')}
: empty('No public board yet', 'No public benchmark of this kind has verified runs yet, so there is nothing to stand beside. Your own board still counts your runs.')}
+</div></div>
</section>
</section>
</div>
</div>
</section>`;
</section>`;
@@ -258,7 +261,8 @@ function shareBox({ benchmark, shareUrl = null, error = null }) {
@@ -258,7 +261,8 @@ function shareBox({ benchmark, shareUrl = null, error = null }) {
? 'Unlisted: anyone with the link can see it. It is not in any list, search, sitemap or the open data.'
? 'Unlisted: anyone with the link can see it. It is not in any list, search, sitemap or the open data.'
: 'Public: it is in the benchmark list, the sitemap and the open data, with every run published.';
: 'Public: it is in the benchmark list, the sitemap and the open data, with every run published.';
return html`<section class="bm-section br-share" id="br-share" aria-labelledby="br-share-h">
return html`<section class="bm-section br-share" id="br-share" aria-labelledby="br-share-h">
-<div class="bm-head-row"><h2 id="br-share-h">Sharing</h2><span class="br-chip br-vis">${visibility}</span></div>
+<div class="br-fold br-share-details"><input class="br-fold-toggle" type="checkbox" id="br-share-open"><label class="br-fold-h" for="br-share-open"><h2 id="br-share-h">Sharing</h2><span class="br-chip br-vis">${visibility}</span><span class="br-fold-mark" aria-hidden="true"></span></label>
+<div class="br-fold-body">
<p class="br-fine">${note}</p>
<p class="br-fine">${note}</p>
${error ? html`<div class="bm-note is-bad" role="alert">${error}</div>` : ''}
${error ? html`<div class="bm-note is-bad" role="alert">${error}</div>` : ''}
${shareUrl ? html`<div class="bm-note br-share-now" role="status"><p>Your unlisted link is ready. It is shown once; copy it now. Share it again for a new link; the old one stops working.</p><label class="br-field"><span>The link</span><input class="br-share-link" type="text" readonly value="${shareUrl}" onfocus="this.select()"></label></div>` : ''}
${shareUrl ? html`<div class="bm-note br-share-now" role="status"><p>Your unlisted link is ready. It is shown once; copy it now. Share it again for a new link; the old one stops working.</p><label class="br-field"><span>The link</span><input class="br-share-link" type="text" readonly value="${shareUrl}" onfocus="this.select()"></label></div>` : ''}
@@ -268,6 +272,7 @@ ${shareUrl ? html`<div class="bm-note br-share-now" role="status"><p>Your unlist
@@ -268,6 +272,7 @@ ${shareUrl ? html`<div class="bm-note br-share-now" role="status"><p>Your unlist
<button class="bm-btn bm-btn-quiet" type="submit" name="visibility" value="private">Make private</button>
<button class="bm-btn bm-btn-quiet" type="submit" name="visibility" value="private">Make private</button>
</form>
</form>
<p class="br-fine">A person with the unlisted link can read the prompts and the runs. Only you can start a run on it.</p>
<p class="br-fine">A person with the unlisted link can read the prompts and the runs. Only you can start a run on it.</p>
+</div></div>
</section>`;
</section>`;
}
}
@@ -332,17 +337,24 @@ function benchmarkPage({ benchmark, definition, version, rankings, metric = 'sco
@@ -332,17 +337,24 @@ function benchmarkPage({ benchmark, definition, version, rankings, metric = 'sco
${privateNote}
${privateNote}
<dl class="br-facts"><div><dt>Tasks</dt><dd>${tasks.length}${hidden ? html` <span class="br-fine">(${hidden} hidden)</span>` : ''}</dd></div><div><dt>Judged by</dt><dd>${judges.length ? judges.map((j) => String(j.type).replace(/_/g, ' ')).join(', ') : '—'}</dd></div><div><dt>Limits</dt><dd>${[limits.ms ? fmtMs(limits.ms) : null, limits.tokens ? `${fmtTokens(limits.tokens)} tokens` : null, limits.usd ? fmtUsd(limits.usd) : null].filter(Boolean).join(' · ') || '—'}</dd></div><div><dt>Verified runs</dt><dd>${entries.reduce((n, e) => n + e.runs, 0)}</dd></div></dl>
<dl class="br-facts"><div><dt>Tasks</dt><dd>${tasks.length}${hidden ? html` <span class="br-fine">(${hidden} hidden)</span>` : ''}</dd></div><div><dt>Judged by</dt><dd>${judges.length ? judges.map((j) => String(j.type).replace(/_/g, ' ')).join(', ') : '—'}</dd></div><div><dt>Limits</dt><dd>${[limits.ms ? fmtMs(limits.ms) : null, limits.tokens ? `${fmtTokens(limits.tokens)} tokens` : null, limits.usd ? fmtUsd(limits.usd) : null].filter(Boolean).join(' · ') || '—'}</dd></div><div><dt>Verified runs</dt><dd>${entries.reduce((n, e) => n + e.runs, 0)}</dd></div></dl>
${owner ? shareBox({ benchmark, shareUrl: share && share.shareUrl, error: share && share.error }) : ''}
${owner ? shareBox({ benchmark, shareUrl: share && share.shareUrl, error: share && share.error }) : ''}
+<div class="br-sections">
+<div class="br-sec-pick" role="radiogroup" aria-label="Benchmark sections">
+<input type="radio" name="br-view" id="br-view-rank" value="rank" checked><label for="br-view-rank">Rankings</label>
+<input type="radio" name="br-view" id="br-view-tasks" value="tasks"><label for="br-view-tasks">Tasks</label>
+<input type="radio" name="br-view" id="br-view-runs" value="runs"><label for="br-view-runs">Runs</label>
+</div>
${ownBoards
${ownBoards
? promptBoards(ownBoards)
? promptBoards(ownBoards)
: html`<section class="bm-section" id="br-rank" aria-labelledby="br-rank-h"><div class="bm-head-row"><h2 id="br-rank-h">Rankings</h2>${metricTabs(base, metric)}</div>
: html`<section class="bm-section" id="br-rank" aria-labelledby="br-rank-h"><div class="bm-head-row"><h2 id="br-rank-h">Rankings</h2>${metricTabs(base, metric)}</div>
${valueChart(entries)}
${valueChart(entries)}
${entries.length ? rankingsTable(entries, metric) : empty('No verified runs yet', 'A run counts here once openvibe.codes, Run or a Node worker ran it and published it. Submitted runs are listed below but never ranked.')}
${entries.length ? rankingsTable(entries, metric) : empty('No verified runs yet', 'A run counts here once openvibe.codes, Run or a Node worker ran it and published it. Submitted runs are listed below but never ranked.')}
</section>`}
</section>`}
-<section class="bm-section" aria-labelledby="br-tasks-h"><div class="bm-head-row"><h2 id="br-tasks-h">Tasks</h2><span class="bm-count">${tasks.length}</span></div>
-<ol class="br-tasks">${shown.map((t) => html`<li class="br-task"><span class="br-task-id br-mono">${t.id}</span><p>${t.prompt}</p>${t.expected && t.expected.answer ? html`<p class="br-fine">Expected: ${String(t.expected.answer).slice(0, 300)}</p>` : ''}</li>`)}${hidden ? html`<li class="br-task is-hidden"><span class="br-task-id br-mono">${plural(hidden, 'task')}</span><p>Held out: only the executor sees ${hidden === 1 ? 'it' : 'them'}, so nobody can tune for the answer.</p></li>` : ''}</ol></section>
-<section class="bm-section" aria-labelledby="br-latest-h"><div class="bm-head-row"><h2 id="br-latest-h">Latest runs</h2>${runs.length > 1 ? html`<a class="bm-more" href="${compareHref(runs.slice(0, 2).map((r) => r.id))}">Compare the newest two</a>` : ''}</div>
+<section class="bm-section" id="br-tasks" aria-labelledby="br-tasks-h"><div class="bm-head-row"><h2 id="br-tasks-h">Tasks</h2><span class="bm-count">${tasks.length}</span></div>
+<ol class="br-tasks">${shown.map((t, i) => html`<li class="br-task"><span class="br-task-n">${i + 1}</span><span class="br-task-id br-mono">${t.id}</span><p>${t.prompt}</p>${t.expected && t.expected.answer ? html`<p class="br-fine br-task-expected">Expected: ${String(t.expected.answer).slice(0, 300)}</p>` : ''}</li>`)}${hidden ? html`<li class="br-task is-hidden"><span class="br-task-id br-mono">${plural(hidden, 'task')}</span><p>Held out: only the executor sees ${hidden === 1 ? 'it' : 'them'}, so nobody can tune for the answer.</p></li>` : ''}</ol></section>
+<section class="bm-section" id="br-runs" aria-labelledby="br-latest-h"><div class="bm-head-row"><h2 id="br-latest-h">Latest runs</h2>${runs.length > 1 ? html`<a class="bm-more" href="${compareHref(runs.slice(0, 2).map((r) => r.id))}">Compare the newest two</a>` : ''}</div>
${runs.length ? runsList(runs) : empty('No runs yet', 'The first run shows up here, verified or submitted.')}</section>
${runs.length ? runsList(runs) : empty('No runs yet', 'The first run shows up here, verified or submitted.')}</section>
-${benchmarkUseThis({ baseUrl, benchmark, entries, viewer })}
+</div>
+${benchmark.visibility === 'private' ? '' : benchmarkUseThis({ baseUrl, benchmark, entries, viewer })}
<p class="br-fine br-api">The same data as JSON: <a href="/api/v1/benchmarks/${encodeURIComponent(benchmark.id)}">definition</a> · <a href="/api/v1/rankings?benchmark=${encodeURIComponent(benchmark.id)}&metric=${metric}">rankings</a></p>
<p class="br-fine br-api">The same data as JSON: <a href="/api/v1/benchmarks/${encodeURIComponent(benchmark.id)}">definition</a> · <a href="/api/v1/rankings?benchmark=${encodeURIComponent(benchmark.id)}&metric=${metric}">rankings</a></p>
</div>`;
</div>`;
}
}
@@ -53,11 +53,12 @@ function benchmarkUseThis({ baseUrl, benchmark, entries = [], viewer = null, bac
@@ -53,11 +53,12 @@ function benchmarkUseThis({ baseUrl, benchmark, entries = [], viewer = null, bac
const s = badgeSnippets(baseUrl, benchmark.id, e.model, benchmark.title);
const s = badgeSnippets(baseUrl, benchmark.id, e.model, benchmark.title);
return copyBox({ heading: e.model, pairs: [{ label: 'Markdown', value: s.markdown }, { label: 'HTML', value: s.html }] });
return copyBox({ heading: e.model, pairs: [{ label: 'Markdown', value: s.markdown }, { label: 'HTML', value: s.html }] });
});
});
- return html`<section class="bm-section" aria-labelledby="br-use-h"><h2 id="br-use-h">Use this</h2>
+ // Closed by default: the badge snippets are useful, but they are not the page's reason to exist.
+ return html`<section class="bm-section br-usethis" aria-labelledby="br-use-h"><details class="br-usethis-details"><summary><h2 id="br-use-h">Badges and embeds</h2></summary>
<p class="br-fine">A badge for your README, and a watch that tells you when this benchmark moves.</p>
<p class="br-fine">A badge for your README, and a watch that tells you when this benchmark moves.</p>
${boxes}
${boxes}
${followForm({ benchmarkId: benchmark.id, back: back || ('/benchmarks/' + benchmark.id), signedIn: Boolean(viewer && viewer.kind === 'user') })}
${followForm({ benchmarkId: benchmark.id, back: back || ('/benchmarks/' + benchmark.id), signedIn: Boolean(viewer && viewer.kind === 'user') })}
-</section>`;
+</details></section>`;
}
}
/** The model page: a badge for each benchmark the model ran, and a watch for each. */
/** The model page: a badge for each benchmark the model ran, and a watch for each. */
@@ -24,6 +24,23 @@ const { badgeSvg } = require('../server/http/badge');
@@ -24,6 +24,23 @@ const { badgeSvg } = require('../server/http/badge');
const t = await boot();
const t = await boot();
const list = (await t.get('/api/v1/benchmarks')).json();
const list = (await t.get('/api/v1/benchmarks')).json();
const benchmark = (list.benchmarks || list)[0];
const benchmark = (list.benchmarks || list)[0];
+ const alice = t.network.addUser('alice');
+ const publisher = t.network.serviceToken({ sub: 'svc:codes', audience: 'openvibe.bench', cap: ['bench.run.publish', 'bench.benchmark.write'] });
+ const madePrivate = await t.get('/api/v1/benchmarks', { as: alice, json: {
+ title: 'Private badge arena', description: 'Not for publication.', kind: 'design.page', tags: [], visibility: 'private',
+ tasks: [{ id: 't1', prompt: 'A private task.', files: [], expected: null, holdout: false }],
+ judges: [{ type: 'human_pairwise', version: '1.0', config: {} }], scoring: { method: 'mean', scale: { min: 0, max: 100 } },
+ limits: { ms: 60000, tokens: 0, usd: 0 }, matrix: { models: [], harnesses: [] },
+ } });
+ assert.strictEqual(madePrivate.status, 201, madePrivate.text);
+ const privateId = madePrivate.json().benchmark.id;
+ assert.strictEqual((await t.get(`/api/v1/benchmarks/${privateId}/versions`, { as: alice, json: {} })).status, 201);
+ assert.strictEqual((await t.get('/api/v1/runs', { bearer: publisher, json: {
+ benchmark: `${privateId}@1`, model: 'model-private', route: { provider: 'acme' }, provider: 'acme',
+ harness: { name: 'claude-code', version: '1.0' }, settings: {}, executor: { kind: 'codes', id: 'tsk_badge' },
+ tasks: [{ task_id: 't1', score: 90, cost_usd: 0.01, ms: 1000, tokens: 100, artifacts: [] }],
+ score: 90, cost_usd: 0.01, ms: 1000, tokens: 100,
+ } })).status, 201);
await check('an unknown benchmark gets a grey badge and a 404', async () => {
await check('an unknown benchmark gets a grey badge and a 404', async () => {
const r = await t.get('/badge/bmk_nope/nope.svg');
const r = await t.get('/badge/bmk_nope/nope.svg');
@@ -36,6 +53,12 @@ const { badgeSvg } = require('../server/http/badge');
@@ -36,6 +53,12 @@ const { badgeSvg } = require('../server/http/badge');
assert.ok(r.text.includes('<svg'), 'and it is still a picture');
assert.ok(r.text.includes('<svg'), 'and it is still a picture');
});
});
+ await check('a private benchmark has no badge, even when it has runs', async () => {
+ const r = await t.get(`/badge/${privateId}/model-private.svg`);
+ assert.strictEqual(r.status, 404, r.text.slice(0, 200));
+ assert.ok(r.text.includes('private benchmark'), 'the badge says why');
+ });
+
await check('a model with no runs gets a grey badge and a 404', async () => {
await check('a model with no runs gets a grey badge and a 404', async () => {
const r = await t.get('/badge/' + benchmark.id + '/no-such-model.svg');
const r = await t.get('/badge/' + benchmark.id + '/no-such-model.svg');
assert.strictEqual(r.status, 404);
assert.strictEqual(r.status, 404);
@@ -80,6 +80,8 @@ function runDoc(benchmark, over = {}) {
@@ -80,6 +80,8 @@ function runDoc(benchmark, over = {}) {
await publish(`${pub.id}@${pub.version}`, { model: 'model-worse', score: 55, cost_usd: 0.04, harness: { name: 'opencode', version: '1.4.2' } });
await publish(`${pub.id}@${pub.version}`, { model: 'model-worse', score: 55, cost_usd: 0.04, harness: { name: 'opencode', version: '1.4.2' } });
const priv = await create({ title: 'Alice unlisted prompts', visibility: 'unlisted' }, alice);
const priv = await create({ title: 'Alice unlisted prompts', visibility: 'unlisted' }, alice);
await publish(`${priv.id}@${priv.version}`, { model: 'model-best', score: 12, cost_usd: 0.01 });
await publish(`${priv.id}@${priv.version}`, { model: 'model-best', score: 12, cost_usd: 0.01 });
+ const privatePage = await create({ title: 'Alice private prompts', visibility: 'private', tags: ['your-prompts'] }, alice);
+ await publish(`${privatePage.id}@${privatePage.version}`, { model: 'model-best', score: 77, cost_usd: 0.01 });
await check('the benchmarks page lists the public benchmarks and escapes their titles', async () => {
await check('the benchmarks page lists the public benchmarks and escapes their titles', async () => {
const res = await t.get('/benchmarks');
const res = await t.get('/benchmarks');
@@ -101,6 +103,34 @@ function runDoc(benchmark, over = {}) {
@@ -101,6 +103,34 @@ function runDoc(benchmark, over = {}) {
assert.ok(value.text.includes('aria-current="page"') && value.text.includes('/ $'), 'the value tab is current and shows score per dollar');
assert.ok(value.text.includes('aria-current="page"') && value.text.includes('/ $'), 'the value tab is current and shows score per dollar');
});
});
+ await check('the phone sections are chips, one per section, with Rankings picked first', async () => {
+ const res = await t.get(`/benchmarks/${pub.id}`);
+ const chips = [...res.text.matchAll(/<input type="radio" name="br-view" id="br-view-([a-z]+)" value="[a-z]+"( checked)?>/g)];
+ assert.deepStrictEqual(chips.map((m) => m[1]), ['rank', 'tasks', 'runs'], 'one chip per section, in order');
+ assert.deepStrictEqual(chips.filter((m) => m[2]).map((m) => m[1]), ['rank'], 'Rankings is picked first');
+ assert.match(res.text, /<div class="br-sections">[\s\S]*id="br-rank"[\s\S]*id="br-tasks"[\s\S]*id="br-runs"[\s\S]*<\/div>/, 'every section stays in the page');
+ const css = await t.get('/css/bench.css');
+ assert.ok(css.text.includes('@supports selector(:has(*))'), 'the chips are gated on :has()');
+ assert.ok(css.text.includes('.br-sections:has(#br-view-rank:checked) #br-rank'), 'Rankings shows when picked');
+ assert.ok(css.text.includes('.br-sections:has(#br-view-tasks:checked) #br-tasks'), 'Tasks shows when picked');
+ assert.ok(css.text.includes('.br-sections:has(#br-view-runs:checked) #br-runs'), 'Runs shows when picked');
+ });
+
+ await check('badges and embeds are one closed details on a public page, and absent on a private one', async () => {
+ const pubPage = await t.get(`/benchmarks/${pub.id}`);
+ assert.ok(pubPage.text.includes('<details class="br-usethis-details">'), 'one details');
+ assert.ok(!pubPage.text.includes('<details class="br-usethis-details" open'), 'closed by default');
+ assert.ok(pubPage.text.includes('Badges and embeds'), 'the summary');
+ assert.ok(pubPage.text.includes('model-best'), 'the models are inside');
+ const mine = await t.get(`/benchmarks/${privatePage.id}`, { as: alice });
+ assert.strictEqual(mine.status, 200, mine.text.slice(0, 200));
+ assert.ok(!mine.text.includes('Badges and embeds'), 'no badge section on a private benchmark');
+ assert.ok(!mine.text.includes('br-usethis'), 'no use-this markup at all');
+ const badge = await t.get(`/badge/${privatePage.id}/model-best.svg`);
+ assert.strictEqual(badge.status, 404, badge.text.slice(0, 200));
+ assert.ok(badge.text.includes('private benchmark'), 'the badge says why');
+ });
+
await check('a run page shows the environment, the score, the provenance and the outputs', async () => {
await check('a run page shows the environment, the score, the provenance and the outputs', async () => {
const res = await t.get(`/runs/${best}`);
const res = await t.get(`/runs/${best}`);
assert.strictEqual(res.status, 200, res.text.slice(0, 300));
assert.strictEqual(res.status, 200, res.text.slice(0, 300));