Watch
0
0
Fork
You've already forked hyperhive
0

swarm-ui: try-out rows keyed by position, distinct names, and accurate storage wording

Two classifications sharing a name made the result table's row key collide, so the form now refuses duplicate names and the table keys rows by position anyway. The page said it stores nothing in the browser, but the result table remembers its column filters like every table does; the header comment and the docs now say that. The row cap's comment says to keep it in step with the endpoint's own cap.
This commit is contained in:
iris 2026-10-12 01:08:58 +02:00 • committed by mara
commit a4a4381cf6
4 changed files with 26 additions and 7 deletions

View file

@ -64,8 +64,9 @@ and the head commit the run read. A failed run shows its error, for example when
has no API key yet.
What you typed stays after a run, so you can edit the classification and run it
again. The page saves nothing and posts nothing to the forge. It shows no token
counts, cache counts or cost.
again. The page saves no classification and no result, and posts nothing to the
forge; like every table in the UI, the result table remembers its column
filters in the browser. It shows no token counts, cache counts or cost.
### Scheduled prompts

View file

@ -7,8 +7,9 @@
// `failed`. A run takes a minute or more, so the page says it is running.
//
// The inputs stay as typed after a run, so the operator can edit and run
// again. The page stores nothing (not even in the browser) and posts nothing
// to the forge.
// again. The page keeps nothing you typed and no result, and posts nothing to
// the forge; the only thing the browser remembers is the result table's
// column filters, as for every table in the UI.
import { useEffect, useState } from "preact/hooks";
import { ApiErrorPanel } from "@hive/shared/api-error-panel.js";
import { readApiError, type ProblemDetails } from "@hive/shared/api-error.js";
@ -19,6 +20,7 @@ import { ClassificationEditor } from "./ClassificationEditor.js";
import { TryOutResult } from "./TryOutResult.js";
import {
draftProblem,
duplicateName,
prNumber,
repoProblem,
toBody,
@ -29,8 +31,9 @@ import "./ReviewTryOutPage.css";
const TRY_OUT = "/api/review-classifier/try-out";
const POLL_MS = 2000;
// More classifications than this are not offered a row; the backend has its
// own cap and says so with a 400, which the page shows as it is.
// More classifications than this are not offered a row. Keep it in step with
// the try-out endpoint's own cap, which answers a longer list with a 400 that
// the page shows as it is; the backend has no way to report it yet.
const MAX_CLASSIFICATIONS = 10;
let nextKey = 0;
@ -115,6 +118,7 @@ export function ReviewTryOutPage() {
repoProblem(repo) ??
(prNumber(pr) === null ? "a PR number is a whole number above 0" : null) ??
drafts.map(draftProblem).find((p) => p !== null) ??
duplicateName(drafts) ??
null;
async function run(e: Event) {

View file

@ -51,7 +51,9 @@ export function TryOutResult({ result }: { result: Result }) {
<Table
columns={COLUMNS}
rows={rows}
rowKey={(r) => r.name}
// By position: two classifications may share a name, and a shared key
// would let one result render over the other.
rowKey={(r) => String(rows.indexOf(r))}
storageKey="swarm-ui:review-tryout:table-filters"
emptyMessage="the run returned no classifications"
/>

View file

@ -81,6 +81,18 @@ export function draftProblem(d: ClassificationDraft): string | null {
return null;
}
/** A name two classifications share, as a problem, or null: the result table
* names its rows by it, so a shared name leaves two rows nobody can tell apart. */
export function duplicateName(drafts: ClassificationDraft[]): string | null {
const seen = new Set<string>();
for (const d of drafts) {
const name = d.name.trim();
if (seen.has(name)) return `two classifications are named ${name}`;
seen.add(name);
}
return null;
}
export function toBody(d: ClassificationDraft): ClassificationBody {
return {
name: d.name.trim(),