swarm-ui try-out: stable live region, reasons for disabled buttons
The running line is a live region whose text changed every second, so a screen reader announced the counter again each time. The sentence is now stable and the elapsed seconds sit outside it. The run and open-a-PR buttons point at the hint that says why they are off, and add-a-classification says why it is off at the cap.
This commit is contained in:
parent
e81aaef7e3
commit
e03bd4caa4
5 changed files with 53 additions and 11 deletions
|
|
@ -483,7 +483,10 @@ rows as the review table.
|
|||
A page for writing classification prompts without touching the repo. Pick a
|
||||
repo and a PR, write or edit a classification, and run it against that PR;
|
||||
change the prompt and run it again until it answers the way you want.
|
||||
Nothing reaches the forge while you iterate.
|
||||
Nothing reaches the forge while you iterate. A button the page disables
|
||||
points at a line that says why, such as the 10-classification cap on **add a
|
||||
classification**, and a screen reader announces the run's progress once, with
|
||||
the elapsed seconds outside the announcement.
|
||||
|
||||
The page posts the run to `swarm-controller` (`POST
|
||||
/api/review-classifier/try-out`), which reads the PR with its own forge token
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ import { TextField } from "../../ui/text-field/TextField.js";
|
|||
import { ClassificationEditor } from "./ClassificationEditor.js";
|
||||
import { TryOutResult } from "./TryOutResult.js";
|
||||
import {
|
||||
addProblem,
|
||||
draftFromRule,
|
||||
hasTypedText,
|
||||
prNumber,
|
||||
|
|
@ -57,10 +58,9 @@ const PULLS = "/api/pulls";
|
|||
// does not ask for the PRs of each prefix of it.
|
||||
const PICK_DELAY_MS = 300;
|
||||
const POLL_MS = 2000;
|
||||
// More classifications than this are not offered a row. It is the try-out
|
||||
// endpoint's own cap, which answers a longer list with a 400 that the page
|
||||
// shows as it is.
|
||||
const MAX_CLASSIFICATIONS = 10;
|
||||
// Ids the buttons' `aria-describedby` points at.
|
||||
const HINT_ID = "tryout-hint";
|
||||
const CAP_ID = "tryout-cap";
|
||||
|
||||
let nextKey = 0;
|
||||
function blankDraft(): ClassificationDraft {
|
||||
|
|
@ -221,6 +221,7 @@ export function ReviewTryOut() {
|
|||
|
||||
const openPrProblem = rowsProblem(repo, drafts);
|
||||
const problem = runProblem(repo, pr, drafts);
|
||||
const capProblem = addProblem(drafts.length);
|
||||
|
||||
async function run(e: Event) {
|
||||
e.preventDefault();
|
||||
|
|
@ -398,7 +399,8 @@ export function ReviewTryOut() {
|
|||
<div class="ui-tryout-actions">
|
||||
<Button
|
||||
onClick={() => setDrafts((ds) => [...ds, blankDraft()])}
|
||||
disabled={drafts.length >= MAX_CLASSIFICATIONS}
|
||||
disabled={capProblem !== null}
|
||||
describedBy={capProblem !== null ? CAP_ID : undefined}
|
||||
>
|
||||
add a classification
|
||||
</Button>
|
||||
|
|
@ -406,17 +408,28 @@ export function ReviewTryOut() {
|
|||
variant="primary"
|
||||
type="submit"
|
||||
disabled={running || problem !== null}
|
||||
describedBy={problem !== null ? HINT_ID : undefined}
|
||||
>
|
||||
{running ? "running…" : "run"}
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => void openPr()}
|
||||
disabled={opening || openPrProblem !== null}
|
||||
describedBy={openPrProblem !== null ? HINT_ID : undefined}
|
||||
>
|
||||
{opening ? "pushing…" : "open a PR"}
|
||||
</Button>
|
||||
</div>
|
||||
{problem !== null ? <p class="ui-tryout-hint">{problem}</p> : null}
|
||||
{capProblem !== null ? (
|
||||
<p id={CAP_ID} class="ui-tryout-hint">
|
||||
{capProblem}
|
||||
</p>
|
||||
) : null}
|
||||
{problem !== null ? (
|
||||
<p id={HINT_ID} class="ui-tryout-hint">
|
||||
{problem}
|
||||
</p>
|
||||
) : null}
|
||||
{openError ? (
|
||||
<ApiErrorPanel
|
||||
context="the branch for the PR could not be pushed"
|
||||
|
|
@ -450,10 +463,13 @@ export function ReviewTryOut() {
|
|||
{running || result || error ? (
|
||||
<Panel title="result" icon="📋">
|
||||
{running ? (
|
||||
<>
|
||||
<p class="ui-tryout-running" role="status">
|
||||
Running — a run takes a minute or more, and up to half an hour for
|
||||
a long list ({elapsed}s so far).
|
||||
Running — a run takes a minute or more, and up to half an hour
|
||||
for a long list.
|
||||
</p>
|
||||
<p class="ui-tryout-running">{elapsed}s so far</p>
|
||||
</>
|
||||
) : null}
|
||||
{error ? (
|
||||
<ApiErrorPanel context="the try-out failed" problem={error} />
|
||||
|
|
|
|||
|
|
@ -150,6 +150,17 @@ export function duplicateName(drafts: ClassificationDraft[]): string | null {
|
|||
return null;
|
||||
}
|
||||
|
||||
/** More classifications than this are not offered a row. It is the try-out
|
||||
* endpoint's own cap, which answers a longer list with a 400. */
|
||||
export const MAX_CLASSIFICATIONS = 10;
|
||||
|
||||
/** Why another row cannot be added, or null while there is room. */
|
||||
export function addProblem(count: number): string | null {
|
||||
return count >= MAX_CLASSIFICATIONS
|
||||
? `a run takes at most ${MAX_CLASSIFICATIONS} classifications`
|
||||
: null;
|
||||
}
|
||||
|
||||
/** What keeps the rows from going to the repo as a PR, or null: the repo, then
|
||||
* each row, then a shared name. Unlike a run, it needs no PR number. */
|
||||
export function rowsProblem(
|
||||
|
|
|
|||
|
|
@ -14,12 +14,15 @@ export function Button({
|
|||
type = "button",
|
||||
disabled,
|
||||
onClick,
|
||||
describedBy,
|
||||
children,
|
||||
}: {
|
||||
variant?: ButtonVariant;
|
||||
type?: "button" | "submit";
|
||||
disabled?: boolean;
|
||||
onClick?: (e: Event) => void;
|
||||
/** Id of the element that says why the button is disabled. */
|
||||
describedBy?: string;
|
||||
children: ComponentChildren;
|
||||
}) {
|
||||
return (
|
||||
|
|
@ -28,6 +31,7 @@ export function Button({
|
|||
type={type}
|
||||
disabled={disabled}
|
||||
onClick={onClick}
|
||||
aria-describedby={describedBy}
|
||||
>
|
||||
{children}
|
||||
</button>
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@
|
|||
import assert from "node:assert/strict";
|
||||
import { test } from "node:test";
|
||||
import {
|
||||
MAX_CLASSIFICATIONS,
|
||||
addProblem,
|
||||
draftFromRule,
|
||||
draftProblem,
|
||||
duplicateName,
|
||||
|
|
@ -17,6 +19,12 @@ import {
|
|||
toBody,
|
||||
} from "../src/pages/review-tryout/TryOutTypes.ts";
|
||||
|
||||
test("a row is offered until the endpoint's cap is reached", () => {
|
||||
assert.equal(addProblem(1), null);
|
||||
assert.equal(addProblem(MAX_CLASSIFICATIONS - 1), null);
|
||||
assert.match(addProblem(MAX_CLASSIFICATIONS), /at most 10 classifications/);
|
||||
});
|
||||
|
||||
// A row ready to send, with `over` replacing any of its fields.
|
||||
function row(over = {}) {
|
||||
return {
|
||||
|
|
|
|||
Loading…
Reference in a new issue