From 5743c6812525fd64f52635af0dbceff616d3a2c6 Mon Sep 17 00:00:00 2001 From: Tim Disney Date: Sun, 26 Jul 2026 15:53:13 +0000 Subject: [PATCH] Say "review", not "verdict", in the UI (#5) The drawer asked for a "verdict". The button under it wrote a review request, the record it produced was a review, the icon was already the review glyph, and the project settings page called the same thing a review. So a reader had to learn that two words meant one concept before the screens agreed with each other: "Ask for a verdict" put a row in a queue described as verdicts owed, and what cleared it was a review. There is a real distinction underneath and it is worth keeping. The review record carries a field literally named `verdict`, whose value is `approve` or `request_changes`, and that field is protocol — it is in the lexicon, in the `--verdict` flag the CLI takes, and in the colour rules DESIGN.md rations sage and red by. So the line this draws is: A *review* is the thing — the act, the record, the request for one, and the queue of ones owed. All copy says review. A *verdict* is only the review record's `verdict` field. It stays in the lexicon, the flag, the types and the colour vocabulary, and it never appears as a bare word in copy: the UI already renders that field as the badges `approved` and `changes`. The rule is written down first, in `verdicts.ts`'s header and in the ui README, because that file is named for the field and the next person to touch it should not have to guess which word is which. Four of the sentences already contained the word "review", so they are rewritten rather than substituted — "a verdict owed … the moment a review pins" becomes "one exact version, put in front of one person"; `softPolicyNote` loses the noun instead of doubling it ("is not approved" rather than "has no approving review"). The badge is `not reviewed` rather than "no review", because it sits in a tail beside `approved` and `changes` and reads as a state. "judge" was a third word for the same act in the counters and the pie, so those go too: "N to review", "N of M reviewed", and `stateLabel.judged` as "reviewed". `UnitDetail`'s "Acceptance criteria — human-judged" stays, where it contrasts with machine checkruns rather than naming the act. Nothing on the wire moves. `verdictArgs`, the `Verdict` type, `unit.verdict`, `needsVerdict`, the filenames and the CSS section keep their names, and the sidecar and daemon are untouched. The copy was almost entirely unasserted, which is how the two vocabularies diverged in the first place, so two tests come with it: `askedForReview`'s two branches, and a landed-but-unpinned unit's badge tail. Both fail before the copy change and pass after. Co-authored-by: claudebot.disnetdev.com (did:plc:n6ku5xddiuguwze3f356evla) --- packages/ui/README.md | 7 ++++++- packages/ui/src/lib/admin.ts | 2 +- packages/ui/src/lib/requests.ts | 4 ++-- packages/ui/src/lib/space.test.ts | 11 ++++++++++- packages/ui/src/lib/space.ts | 2 +- packages/ui/src/lib/units.ts | 18 +++++++++--------- packages/ui/src/lib/verdicts.test.ts | 30 ++++++++++++++++++++---------- packages/ui/src/lib/verdicts.ts | 53 ++++++++++++++++++++++++++++++++++------------------- packages/ui/src/lib/write.test.ts | 8 ++++---- packages/ui/src/lib/components/AskReview.svelte | 6 +++--- packages/ui/src/lib/components/Badge.svelte | 2 +- packages/ui/src/lib/components/CrossRow.svelte | 2 +- packages/ui/src/lib/components/GoalRow.svelte | 2 +- packages/ui/src/lib/components/Pie.svelte | 4 ++-- packages/ui/src/lib/components/SmartList.svelte | 2 +- packages/ui/src/lib/components/StateDot.svelte | 4 ++-- packages/ui/src/lib/components/UnitDetail.svelte | 20 ++++++++++---------- packages/ui/src/lib/components/UnitRow.svelte | 2 +- packages/ui/src/lib/components/Verdict.svelte | 16 ++++++++-------- packages/ui/src/routes/me/+page.svelte | 14 +++++++------- packages/ui/src/routes/g/[did]/[rkey]/+page.svelte | 6 +++--- packages/ui/src/routes/p/[project]/settings/+page.svelte | 2 +- packages/ui/src/routes/p/[project]/system/+page.svelte | 2 +- 23 file(s) changed, 129 insertion(s)(+), 90 deletion(s)(-) diff --git a/packages/ui/README.md b/packages/ui/README.md --- a/packages/ui/README.md +++ b/packages/ui/README.md @@ -29,13 +29,18 @@ | `src/lib/store.ts`, `src/lib/idb.ts` | `RecordStore` and `SyncStateStore` over an in-memory mirror hydrated from IndexedDB. Synchronous reads, background writes, one database per space. | | `src/lib/identity.ts` | DID → PDS and DID → handle, cached. A handle is shown only when it resolves back to the DID that claimed it. | | `src/lib/units.ts` | presentation over `timeline()` — a row's line, its badges, whose disc ends it, and the two cross-target capture relations no single target's fold can see. | -| `src/lib/requests.ts`, `verdicts.ts`, `admin.ts` | what each surface may offer and what it writes, as pure functions: the ⊕ menu, the verdict and its findings, the space's own shape. Tested without rendering anything. | +| `src/lib/requests.ts`, `verdicts.ts`, `admin.ts` | what each surface may offer and what it writes, as pure functions: the ⊕ menu, the review and its findings, the space's own shape. Tested without rendering anything. | | `src/lib/diagnostics.ts` | `index.ignored` and `index.edits`, grouped for reading. | | `src/lib/keys.ts`, `focus.ts` | the keyboard map, and putting focus back where it was. | | `src/lib/directory.ts` | membership → the row an actor gets: kind, initials, disc colour. Colour is derived from the DID, never authored. | | `src/lib/prose.ts` | the comp's tiny `## ` + backtick grammar. Parses to a structure; the component interpolates it. No `{@html}` anywhere near an agent-authored body. | | `src/lib/components/` | rail, pane bar, space picker, state circle, pie, badges, discs, unit rows and drawers, compose cards, smart lists. | | `bin/serve.ts` | `radial-ui`: the built bundle on loopback, and nothing else. | + +One word for one thing: a **review** is the act, the record, the request for one and the queue of +ones owed, and that is the only word the copy uses. A **verdict** is just the review record's +`verdict` field — `approve` or `request_changes` — which is why `verdicts.ts` keeps its name and why +the UI shows that field as the badges `approved` and `changes` rather than as a bare noun. ### Opening a space diff --git a/packages/ui/src/lib/admin.ts b/packages/ui/src/lib/admin.ts --- a/packages/ui/src/lib/admin.ts +++ b/packages/ui/src/lib/admin.ts @@ -402,7 +402,7 @@ * for a review without anybody pressing anything. * * Every registry type gets a row, both scopes, because the daemon's trigger scans goals and - * projects alike — a project-scoped document lands and is owed a verdict the same way. + * projects alike — a project-scoped document lands and is owed a review the same way. */ export interface AutoReviewRow { type: string diff --git a/packages/ui/src/lib/requests.ts b/packages/ui/src/lib/requests.ts --- a/packages/ui/src/lib/requests.ts +++ b/packages/ui/src/lib/requests.ts @@ -203,7 +203,7 @@ .sort((left, right) => left.name.localeCompare(right.name)) } -/** Nothing has judged this version. Building something new on it is the soft-policy case (design §7). */ +/** Nothing has approved this version. Building something new on it is the soft-policy case (design §7). */ export const unapproved = (version: UnitVersion | undefined): boolean => version !== undefined && version.verdict !== 'approve' @@ -232,7 +232,7 @@ /** The one sentence both the drawer and the card say, so they cannot come to disagree. */ export const softPolicyNote = (label: string): string => - `${label} has no approving verdict. You can still build on it — reviews annotate, they don’t gate.` + `${label} is not approved. You can still build on it — reviews annotate, they don’t gate.` /** * The request a retraction would withdraw, or nothing. diff --git a/packages/ui/src/lib/space.test.ts b/packages/ui/src/lib/space.test.ts --- a/packages/ui/src/lib/space.test.ts +++ b/packages/ui/src/lib/space.test.ts @@ -113,7 +113,16 @@ expect(texts).toEqual(['approved', '3/3 checks', 'PR #241', 'merged']) }) - it('counts what is stopped on a person: one question, two verdicts owed', () => { + it('badges a landed artifact nothing has pinned as not reviewed', () => { + const ignored = space.index.goals.find( + (goal) => goal.target.value.title === 'Surface the records the fold ignored', + ) + const unit = ignored && unitsOf(space.index, ignored).find((u) => u.type === 'plan') + if (!unit) throw new Error('fixture unit missing') + expect(unitBadges(unit, { now: space.asOf }).map((badge) => badge.text)).toContain('not reviewed') + }) + + it('counts what is stopped on a person: one question, two reviews owed', () => { const all = unitsWithContext(space.index) expect(all.filter(({ unit }) => isAwaiting(unit))).toHaveLength(1) // Landing is not done. Both units of the ignored-records goal landed with nothing pinned to diff --git a/packages/ui/src/lib/space.ts b/packages/ui/src/lib/space.ts --- a/packages/ui/src/lib/space.ts +++ b/packages/ui/src/lib/space.ts @@ -120,7 +120,7 @@ /** * Deep link that opens one unit's drawer: what a cross-goal row points at. * - * `v` is carried only when the row stands for a particular version — a verdict owed on v1 must open + * `v` is carried only when the row stands for a particular version — a review owed on v1 must open * on v1, not on whatever has landed since. Without it the drawer follows the tip, which is what * every other row means. * diff --git a/packages/ui/src/lib/units.ts b/packages/ui/src/lib/units.ts --- a/packages/ui/src/lib/units.ts +++ b/packages/ui/src/lib/units.ts @@ -39,7 +39,7 @@ target: GoalView | ProjectView /** * The exact version this row stands for, when it stands for one rather than for the unit. A - * verdict owed is owed on one uri#cid (§3.7), so the queue's rows carry it and open the drawer + * review owed is owed on one uri#cid (§3.7), so the queue's rows carry it and open the drawer * there; every other list carries nothing and follows the tip. */ version?: UnitVersion @@ -82,12 +82,12 @@ * * Archiving says agents should skip a target, and `goalsOf()` already takes it at its word — an * archived goal is not on its project's page. A cross-goal list is the same claim seen from further - * away, so it has to honour the same rule: a verdict owed on an artifact under a shelved goal is not + * away, so it has to honour the same rule: a review owed on an artifact under a shelved goal is not * work stopped on anybody, and a list that keeps showing it is a list nobody can empty. * * Archiving a PROJECT shelves both kinds at once — its own system units, and every goal under it, the * same reach `activeGoals()` in core gives the daemon's dispatcher. Closure is deliberately NOT part - * of this: a closed goal can still owe somebody a verdict, and the daemon's rule sets it aside where + * of this: a closed goal can still owe somebody a review, and the daemon's rule sets it aside where * a reader's does not. */ export function liveUnits(index: MaterializedIndex): UnitContext[] { @@ -101,7 +101,7 @@ ) } -/** Landed, and no verdict pins the version being read. The queue's whole reason to exist. */ +/** Landed, and no review pins the version being read. The queue's whole reason to exist. */ export const needsVerdict = (unit: UnitView): boolean => unit.needsVerdict && unit.versions.length > 0 export const isAwaiting = (unit: UnitView): boolean => unit.state === 'awaiting' @@ -183,7 +183,7 @@ ? { kind: 'ok', glyph: '✓', text: 'approved' } : { kind: 'bad', glyph: '✕', text: 'changes' } -/** The tail of a unit row: state first, then verdict, then what the machines observed. */ +/** The tail of a unit row: state first, then the review, then what the machines observed. */ export function unitBadges(unit: UnitView, options: { now: string } = { now: '' }): RowBadge[] { const badges: RowBadge[] = [] if (unit.state === 'open') badges.push({ kind: 'flat', glyph: '○', text: 'open' }) @@ -196,7 +196,7 @@ if (unit.verdict) badges.push(verdictBadge(unit.verdict)) else if (unit.versions.length > 0 && !unit.retracted) - badges.push({ kind: 'flat', glyph: '◇', text: 'no verdict' }) + badges.push({ kind: 'flat', glyph: '◇', text: 'not reviewed' }) const checks = checkTally(unit.current) if (checks) @@ -212,7 +212,7 @@ return badges } -/** The state circle's shape. `landed` is the tip's state when nothing has judged it yet. */ +/** The state circle's shape. `landed` is the tip's state when nothing has reviewed it yet. */ export const dotState = (unit: UnitView): UnitState => unit.state export const stateLabel: Record = { @@ -220,8 +220,8 @@ assigned: 'assigned to an agent', claimed: 'claimed — a turn is under way', awaiting: 'awaiting your input', - landed: 'landed — awaiting your verdict', - judged: 'judged', + landed: 'landed — awaiting your review', + judged: 'reviewed', merged: 'merged', retracted: 'retracted', } diff --git a/packages/ui/src/lib/verdicts.test.ts b/packages/ui/src/lib/verdicts.test.ts --- a/packages/ui/src/lib/verdicts.test.ts +++ b/packages/ui/src/lib/verdicts.test.ts @@ -5,6 +5,7 @@ import { unitsOf } from './space.js' import { findVersion, liveUnits, unitsWithContext } from './units.js' import { + askedForReview, askReviewArgs, blankFinding, findingsOf, @@ -18,9 +19,9 @@ wroteVerdict, } from './verdicts.js' -// A verdict pins one exact version, and a request for one is a debt on that same version. Everything +// A review pins one exact version, and a request for one is a debt on that same version. Everything // here is that single rule seen from a different side: what the buttons produce, which request a -// verdict closes, and what the queue is a list of. +// review closes, and what the queue is a list of. const space = buildFixtureSpace() const goal = (title: string) => { @@ -67,7 +68,7 @@ }) }) -describe('what a verdict pins', () => { +describe('what a review pins', () => { it('names the version being read, by cid, and not the unit', () => { const plan = unit(QUEUE, 'plan') const [v1, v2] = plan.versions @@ -114,7 +115,7 @@ it('is offered on anything that landed, and on nothing that has not', () => { const plan = unit(IGNORED, 'plan') expect(judgeable(plan, plan.current)).toBe(true) - // An open request has no version to pin a verdict to. + // An open request has no version to pin a review to. const security = unit(QUEUE, 'security-review') expect(security.versions).toHaveLength(0) expect(judgeable(security, security.current)).toBe(false) @@ -136,7 +137,7 @@ }) }) -describe('the request a verdict answers', () => { +describe('the request a review answers', () => { const version = () => { const found = unit(IGNORED, 'implementation').current if (!found) throw new Error('fixture version missing') @@ -190,8 +191,8 @@ it('offers people and review-publishing agents, never the asker, never a producer of something else', () => { const reviewers = possibleReviewers(space.directory, FIXTURE_DIDS.tim).map((actor) => actor.name) - // Humans first — a verdict is a human judgement, and an agent one is a convenience. `jun` is on the - // list despite never having opened the space: asking for a verdict is membership, not attendance, + // Humans first — a review is a human judgement, and an agent one is a convenience. `jun` is on the + // list despite never having opened the space: asking for a review is membership, not attendance, // and being asked for one is a perfectly good reason to turn up. expect(reviewers).toEqual(['ana.dev', 'jun.example.com', 'reviewer']) // `builder` publishes implementations and `planner` publishes plans; neither judges. @@ -201,10 +202,19 @@ 'ana.dev', ) }) + + it('says which version was asked about, and whether anyone in particular was asked', () => { + const version = unit(IGNORED, 'plan').current + if (!version) throw new Error('fixture version missing') + expect(askedForReview(space.directory.get(FIXTURE_DIDS.ana), version)).toBe( + 'Review of v1 requested from ana.dev', + ) + expect(askedForReview(undefined, version)).toBe('Review of v1 requested — open to any member') + }) }) describe('the queue', () => { - it('lists the verdicts asked of one person, on the exact versions they were asked about', () => { + it('lists the reviews asked of one person, on the exact versions they were asked about', () => { const rows = reviewQueue(space.index, FIXTURE_DIDS.tim) expect(rows).toHaveLength(1) const [row] = rows @@ -214,7 +224,7 @@ expect(reviewQueue(space.index, FIXTURE_DIDS.ana)).toHaveLength(0) }) - it('falls back to every verdict owed by anyone when nobody is signed in', () => { + it('falls back to every review owed by anyone when nobody is signed in', () => { expect(reviewQueue(space.index)).toHaveLength(1) }) @@ -265,7 +275,7 @@ return materialize(store, { spaceUri: fixture.spaceUri, asOf: fixture.asOf }) } - it('drops both a verdict asked of somebody and one nobody asked for', () => { + it('drops both a review asked of somebody and one nobody asked for', () => { const index = archivedIndex() expect(index.goals.find((goal) => goal.target.uri === shelvedUri)?.archived).toBe(true) expect(reviewQueue(index, FIXTURE_DIDS.tim)).toEqual([]) diff --git a/packages/ui/src/lib/verdicts.ts b/packages/ui/src/lib/verdicts.ts --- a/packages/ui/src/lib/verdicts.ts +++ b/packages/ui/src/lib/verdicts.ts @@ -1,15 +1,30 @@ -// Judging one exact version. +// Reviewing one exact version. // -// A verdict is the human half of the loop: an agent lands an artifact, and nothing is done until a +// A review is the human half of the loop: an agent lands an artifact, and nothing is done until a // person says what they think of it. Everything in this file follows from one rule the fold already -// enforces (§3.7) — **a review pins a uri#cid, not a unit**. So the subject of a verdict is the +// enforces (§3.7) — **a review pins a uri#cid, not a unit**. So the subject of a review is the // version being *read*, never the unit's tip; browsing back to v1 and pressing Approve approves v1, // and a v3 landing mid-form cannot silently become what was signed. // -// The second rule is that a review request is not a unit either. It is a verdict owed on one exact +// The second rule is that a review request is not a unit either. It is a review owed on one exact // version, so it hangs off that version and the queue is a fold over versions rather than over // rows. That is why "For me" has two flavours: an artifact that landed with nothing pinned to it // (nobody asked; landing is not done), and a request that names a version and a person. +// +// ── One word for one thing ──────────────────────────────────────────────────────────────────── +// +// This file is named for the *field*, not for the act. The rule the UI keeps: +// +// A **review** is the thing — the act, the record, the request for one, and the queue of ones +// owed. All copy a person reads says review. +// +// A **verdict** is only the review record's `verdict` field, whose value is `approve` or +// `request_changes`. It stays in the lexicon, the `--verdict` flag, the types and the design +// system's colour vocabulary, and it never appears as a bare word in copy: the UI renders that +// field as the badges `approved` and `changes`. +// +// So `verdictArgs`, `Verdict`, `unit.verdict` and `needsVerdict` are right as they are, and a +// string in a template that says "verdict" is not. import type { ArtifactRequestRecord, @@ -25,11 +40,11 @@ import { liveUnits, typeLabel, type UnitContext } from './units.js' /** - * What a verdict calls the thing it is judging: the type and the version, always — `implementation + * What a review calls the thing it is judging: the type and the version, always — `implementation * v1`, not `implementation`. * * Everywhere else a lone version is written without its number, because "plan v1" where there has - * only ever been one plan is noise. Here it is the whole point: a verdict pins one uri#cid, and the + * only ever been one plan is noise. Here it is the whole point: a review pins one uri#cid, and the * card has to say which one even when saying it is redundant today and will not be tomorrow. */ export const pinnedLabel = (unit: UnitView, version: UnitVersion): string => @@ -84,7 +99,7 @@ * * Nothing about *who* is asking: anyone reading the row sees the buttons, and the card says why it * cannot be filled in — the same shape the compose card takes (6.4). What decides this is only - * whether there is something to judge: a request nobody has answered has no version to pin a verdict + * whether there is something to judge: a request nobody has answered has no version to pin a review * to, and a retracted unit was withdrawn. */ export const judgeable = (unit: UnitView, version: UnitVersion | undefined): boolean => @@ -106,7 +121,7 @@ * Preference order matters: one assigned to me, then an unassigned one, and never one assigned to * somebody else. The materializer marks a review request fulfilled when *any* trusted review names * it and pins the same subject — it does not check who wrote the review — so naming another agent's - * request would quietly cancel work that agent is doing. An unrequested verdict is always available + * request would quietly cancel work that agent is doing. An unrequested review is always available * and cancels nothing. */ export function owedBy( @@ -122,7 +137,7 @@ /** * `radial review post`. The subject is the version that was read, pinned by CID, and `--request` is - * present only when this verdict is answering one — that ref is what closes the request in the fold. + * present only when this review is answering one — that ref is what closes the request in the fold. */ export function verdictArgs( version: UnitVersion, @@ -147,7 +162,7 @@ return args } -/** What the toast says once the verdict is public. */ +/** What the toast says once the review is public. */ export const wroteVerdict = (verdict: Verdict, version: UnitVersion, fulfilled: boolean): string => { const what = verdict === 'approve' ? 'Approved' : 'Changes requested on' return `${what} v${version.version}${fulfilled ? ' — the review request is answered' : ''}` @@ -156,12 +171,12 @@ // ── asking somebody else for one ──────────────────────────────────────────────────────────────── /** - * Who can be asked for a verdict: every active member but the asker. + * Who can be asked for a review: every active member but the asker. * * Not the registry — `review` is deliberately absent from it, because it is a built-in of the turn * layer rather than a type an admin registers. So this list is *membership*, not capability: a human * judges because they are a person in this space, and an agent judges because its own - * self-description says it produces reviews. Asking yourself for a verdict is a no-op with extra + * self-description says it produces reviews. Asking yourself for a review is a no-op with extra * steps, so the asker is not on it. */ export function possibleReviewers(directory: Directory, did: string): Actor[] { @@ -177,7 +192,7 @@ * `radial request create --type review --subject …`. * * A review request carries a `subject` where every other request carries `basedOn`: it names the one - * version a verdict is owed on, rather than the provenance a new artifact is built from. That is + * version a review is owed on, rather than the provenance a new artifact is built from. That is * also why it never becomes a unit — there is no artifact coming, only a judgement. */ export function askReviewArgs(input: { @@ -205,20 +220,20 @@ export const askedForReview = (assignee: Actor | undefined, version: UnitVersion): string => assignee - ? `Verdict on v${version.version} requested from ${assignee.handle ?? assignee.name}` - : `Verdict on v${version.version} requested — open to any member` + ? `Review of v${version.version} requested from ${assignee.handle ?? assignee.name}` + : `Review of v${version.version} requested — open to any member` // ── the queue ─────────────────────────────────────────────────────────────────────────────────── /** - * Verdicts somebody was explicitly asked for: open review requests, resolved back to the unit and + * Reviews somebody was explicitly asked for: open review requests, resolved back to the unit and * the exact version each one pins. * * A fold over versions rather than over units, because that is where a review request lives. Two * requests on one version, or requests on two versions of one unit, are separate rows — each is a - * separate verdict owed. + * separate review owed. * - * With a DID it is that person's queue. Without one — nobody signed in — it is every verdict owed by + * With a DID it is that person's queue. Without one — nobody signed in — it is every review owed by * anyone, because a tab with no session still knows what this space is waiting on a human for, and * saying nothing would be less true than saying "these, by someone". */ @@ -239,7 +254,7 @@ } /** - * Verdicts nobody asked for: landed artifacts with nothing pinned to their current version and no + * Reviews nobody asked for: landed artifacts with nothing pinned to their current version and no * open review request on it either. This is the larger half of the queue in practice — an agent * delivers, and the work is with a person whether or not anyone wrote that down. */ diff --git a/packages/ui/src/lib/write.test.ts b/packages/ui/src/lib/write.test.ts --- a/packages/ui/src/lib/write.test.ts +++ b/packages/ui/src/lib/write.test.ts @@ -419,7 +419,7 @@ await write(verdictArgs(v1, { verdict: 'request_changes', findings })) - // The verdict pins v1 by cid, and the fold reads it back off the version rather than off the + // The review pins v1 by cid, and the fold reads it back off the version rather than off the // unit: this is what makes "judged" mean the same thing in a tab as it does in the daemon. const judged = unitOf('plan') expect(judged.versions[0]?.verdict).toBe('request_changes') @@ -442,7 +442,7 @@ ) // …and the turn that answers it is briefed with exactly what was said. This is the whole of the - // loop 6.5 closes: a verdict written in a browser tab is an input to the next turn, with no + // loop 6.5 closes: a review written in a browser tab is an input to the next turn, with no // daemon endpoint, no export step, and nothing between the two but the record stream. const bundle = buildTurnBundle(space().index, asked.primary.uri, asked.primary.cid) const mine = bundle.reviewFindings.filter( @@ -473,7 +473,7 @@ expect(space().index.ignored).toEqual([]) }) - it('asks somebody else for a verdict, and it is not a unit of its own', async () => { + it('asks somebody else for a review, and it is not a unit of its own', async () => { const harness = await open() signInAs(FIXTURE_DIDS.tim, harness.network) const version = unitOf('plan').current @@ -490,7 +490,7 @@ }), ) - // A verdict owed is a debt on one version, not a piece of work: it adds no row, it hangs off the + // A review owed is a debt on one version, not a piece of work: it adds no row, it hangs off the // version it pins, and it shows up in exactly one person's queue. expect(unitsOf(space().index, ignored())).toHaveLength(before) const owed = unitOf('plan').current?.reviewRequests ?? [] diff --git a/packages/ui/src/lib/components/AskReview.svelte b/packages/ui/src/lib/components/AskReview.svelte --- a/packages/ui/src/lib/components/AskReview.svelte +++ b/packages/ui/src/lib/components/AskReview.svelte @@ -49,10 +49,10 @@

- Ask for a verdict on {pinnedLabel(unit, version)} + Ask for a review of {pinnedLabel(unit, version)}

- A verdict owed on one exact version. It shows up in the assignee's queue and disappears from it + One exact version, put in front of one person. It shows up in the assignee's queue and leaves it the moment a review pins this version — it never becomes a row of its own, because nothing is being produced.

@@ -102,7 +102,7 @@ - +
{#if error} diff --git a/packages/ui/src/lib/components/Badge.svelte b/packages/ui/src/lib/components/Badge.svelte --- a/packages/ui/src/lib/components/Badge.svelte +++ b/packages/ui/src/lib/components/Badge.svelte @@ -1,7 +1,7 @@ diff --git a/packages/ui/src/lib/components/CrossRow.svelte b/packages/ui/src/lib/components/CrossRow.svelte --- a/packages/ui/src/lib/components/CrossRow.svelte +++ b/packages/ui/src/lib/components/CrossRow.svelte @@ -28,7 +28,7 @@ {entry.unit.type} - {#if entry.version && entry.unit.versions.length > 1} v{entry.version.version} diff --git a/packages/ui/src/lib/components/GoalRow.svelte b/packages/ui/src/lib/components/GoalRow.svelte --- a/packages/ui/src/lib/components/GoalRow.svelte +++ b/packages/ui/src/lib/components/GoalRow.svelte @@ -40,7 +40,7 @@ {/if} {#if toJudge > 0} - + {/if} {#each actors as actor (actor.did)} diff --git a/packages/ui/src/lib/components/Pie.svelte b/packages/ui/src/lib/components/Pie.svelte --- a/packages/ui/src/lib/components/Pie.svelte +++ b/packages/ui/src/lib/components/Pie.svelte @@ -3,7 +3,7 @@ // // It fills on the same rule the row circles use (`tally()` in core counts a unit that landed AND // was judged), so a goal cannot read "finished" while it still has an artifact waiting on a human - // verdict. + // review. interface Props { judged: number total: number @@ -14,7 +14,7 @@ const radius = 6.6 const circumference = Math.PI * radius const fraction = $derived(total ? judged / total : 0) - const label = $derived(total ? `${judged} of ${total} judged` : 'nothing requested yet') + const label = $derived(total ? `${judged} of ${total} reviewed` : 'nothing requested yet') diff --git a/packages/ui/src/lib/components/SmartList.svelte b/packages/ui/src/lib/components/SmartList.svelte --- a/packages/ui/src/lib/components/SmartList.svelte +++ b/packages/ui/src/lib/components/SmartList.svelte @@ -38,7 +38,7 @@

{group.hint}

{/if}
    - + {#each group.rows as entry (`${entry.unit.key}#${entry.version?.artifact.cid ?? ''}`)} {/each} diff --git a/packages/ui/src/lib/components/StateDot.svelte b/packages/ui/src/lib/components/StateDot.svelte --- a/packages/ui/src/lib/components/StateDot.svelte +++ b/packages/ui/src/lib/components/StateDot.svelte @@ -4,8 +4,8 @@ // The state circle: read-only. The network fills it in, the human judges it. // - // The ladder that matters: an OUTLINE tick means the work landed and nobody has judged it; a - // FILLED disc means a verdict is pinned to it. Landed is not done. + // The ladder that matters: an OUTLINE tick means the work landed and nobody has reviewed it; a + // FILLED disc means a review is pinned to it. Landed is not done. // // The one thing this draws that the comp did not: `assigned` and `claimed` are different circles. // A claim is the only signal the index has that a turn is genuinely under way, so it — and only diff --git a/packages/ui/src/lib/components/UnitDetail.svelte b/packages/ui/src/lib/components/UnitDetail.svelte --- a/packages/ui/src/lib/components/UnitDetail.svelte +++ b/packages/ui/src/lib/components/UnitDetail.svelte @@ -45,7 +45,7 @@ unit: UnitView view: GoalView | ProjectView space: Space - /** 1-based, from a link that stands for one version — a verdict owed on v1 opens on v1. */ + /** 1-based, from a link that stands for one version — a review owed on v1 opens on v1. */ startVersion?: number | undefined } const { unit, view, space, startVersion }: Props = $props() @@ -97,7 +97,7 @@ // an admin put `implementation` in the registry, not because this component knows what a plan is. // // They are asked against the version being READ, not the unit's tip — browsing back to v1 and - // asking for an implementation asks for one built on v1, which is the same rule the verdicts + // asking for an implementation asks for one built on v1, which is the same rule the reviews // follow. The card that opens carries that exact strongref. const scope = $derived<'goal' | 'project'>(isGoalView(view) ? 'goal' : 'project') const buildable = $derived(version && !unit.retracted ? buildableTypes(space.index, unit, scope) : []) @@ -124,19 +124,19 @@ } // ── judging it ──────────────────────────────────────────────────────────────────────────────── - // The verdict is on the version being READ, which is the same rule the successor buttons follow + // The review is on the version being READ, which is the same rule the successor buttons follow // and the rule the fold already enforces: a review pins a uri#cid (§3.7). One card at a time. let judging = $state(null) let asking = $state(false) const canJudge = $derived(judgeable(unit, version)) - // The verdict this reader was explicitly asked for, if any — what turns the button into an answer. + // The review this reader was explicitly asked for, if any — what turns the button into an answer. const owed = $derived(owedBy(version, account.did)) const judge = (verdict: Verdict): void => { asking = false judging = verdict } - // A written verdict re-reads the author's repo, so the version this drawer is showing already + // A written review re-reads the author's repo, so the version this drawer is showing already // holds it by the time the card closes. const closeCards = (): void => { judging = null @@ -361,14 +361,14 @@ /> by {reviewer.handle ?? reviewer.name} · {stamp(verdictReview.value.createdAt)} · pinned to - v{version.version}{#if !isCurrent} — superseded, but the verdict still stands on what it judged{/if} + v{version.version}{#if !isCurrent} — superseded, but the review still stands on what it judged{/if} {/if} {#if version?.reviewRequests.length}
    - + {#each version.reviewRequests as request, position (request.uri)} {@const from = space.directory.get(request.did)} @@ -382,7 +382,7 @@ {:else} any member {/if} - for a verdict on v{version.version} + for a review of v{version.version} {/each}
    @@ -391,7 +391,7 @@ {#if canJudge || canSucceed || retractable}
    {#if canJudge && version} - {#if canSucceed}{/if} {/if} diff --git a/packages/ui/src/lib/components/UnitRow.svelte b/packages/ui/src/lib/components/UnitRow.svelte --- a/packages/ui/src/lib/components/UnitRow.svelte +++ b/packages/ui/src/lib/components/UnitRow.svelte @@ -18,7 +18,7 @@ /** System rows lead with the record's identity rather than with its age. */ system?: boolean startOpen?: boolean - /** 1-based, from a link that stands for one version — a verdict owed on v1 opens on v1. */ + /** 1-based, from a link that stands for one version — a review owed on v1 opens on v1. */ startVersion?: number | undefined } const { unit, view, space, system = false, startOpen = false, startVersion }: Props = $props() diff --git a/packages/ui/src/lib/components/Verdict.svelte b/packages/ui/src/lib/components/Verdict.svelte --- a/packages/ui/src/lib/components/Verdict.svelte +++ b/packages/ui/src/lib/components/Verdict.svelte @@ -20,7 +20,7 @@ import UriChip from './UriChip.svelte' // The human half of the loop. It sits inside the drawer rather than at the top of the route the - // way a compose card does, because a verdict is not a new row — it is an annotation on the exact + // way a compose card does, because a review is not a new row — it is an annotation on the exact // version being read, and it belongs next to what it judges. interface Props { unit: UnitView @@ -32,7 +32,7 @@ } const { unit, version, space, initial, done }: Props = $props() - // Request-changes opens with a row waiting: a verdict that sends work back and says nothing about + // Request-changes opens with a row waiting: a review that sends work back and says nothing about // why is the one shape of review that is worse than no review. Approve opens with none, and an // approval with a note on it is a click away. let rows = $state( @@ -89,10 +89,10 @@ - Verdict on {pinned} + Review of {pinned}

    - Pinned to {pinned} by cid. A verdict judges one version and says nothing about any other: it does + Pinned to {pinned} by cid. A review judges one version and says nothing about any other: it does not lapse when a later one lands, and it does not reach back to an earlier one. {#if answering && asker} · answering {asker.handle ?? asker.name}'s request @@ -103,7 +103,7 @@

    - A verdict already pins this version. + A review already pins this version. Nothing is overwritten — reviews accumulate, and the newest one is the one the row shows.

    @@ -112,12 +112,12 @@ {#if !writable} {#if space.fixture}

    - These records are synthetic and there is no repo behind them. Open a live space to judge one. + These records are synthetic and there is no repo behind them. Open a live space to review one.

    {:else if account.status !== 'signed-in'}

    - to post it — a verdict is a record in your own repo, authored by you. + to post it — a review is a record in your own repo, authored by you.

    {:else}

    @@ -226,7 +226,7 @@ - +