@variance-authority/tribunal 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +100 -0
- package/LICENSE +21 -0
- package/README.md +506 -0
- package/dist/__fixtures__/review.d.ts +47 -0
- package/dist/__fixtures__/review.js +145 -0
- package/dist/__fixtures__/review.js.map +1 -0
- package/dist/bindings.d.ts +134 -0
- package/dist/bindings.js +88 -0
- package/dist/bindings.js.map +1 -0
- package/dist/changelog.d.ts +132 -0
- package/dist/changelog.js +129 -0
- package/dist/changelog.js.map +1 -0
- package/dist/history-queries.d.ts +82 -0
- package/dist/history-queries.js +200 -0
- package/dist/history-queries.js.map +1 -0
- package/dist/history-rows.d.ts +42 -0
- package/dist/history-rows.js +148 -0
- package/dist/history-rows.js.map +1 -0
- package/dist/history.d.ts +40 -0
- package/dist/history.js +186 -0
- package/dist/history.js.map +1 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +12 -0
- package/dist/index.js.map +1 -0
- package/dist/migration-steps.d.ts +32 -0
- package/dist/migration-steps.js +303 -0
- package/dist/migration-steps.js.map +1 -0
- package/dist/migrations.d.ts +58 -0
- package/dist/migrations.js +283 -0
- package/dist/migrations.js.map +1 -0
- package/dist/next.d.ts +94 -0
- package/dist/next.js +44 -0
- package/dist/next.js.map +1 -0
- package/dist/node/bin.d.ts +120 -0
- package/dist/node/bin.js +337 -0
- package/dist/node/bin.js.map +1 -0
- package/dist/node/bucket.d.ts +58 -0
- package/dist/node/bucket.js +142 -0
- package/dist/node/bucket.js.map +1 -0
- package/dist/node/database.d.ts +33 -0
- package/dist/node/database.js +157 -0
- package/dist/node/database.js.map +1 -0
- package/dist/node/index.d.ts +43 -0
- package/dist/node/index.js +43 -0
- package/dist/node/index.js.map +1 -0
- package/dist/node/serve.d.ts +81 -0
- package/dist/node/serve.js +193 -0
- package/dist/node/serve.js.map +1 -0
- package/dist/node/ui-assets.d.ts +47 -0
- package/dist/node/ui-assets.js +60 -0
- package/dist/node/ui-assets.js.map +1 -0
- package/dist/review-ingest.d.ts +24 -0
- package/dist/review-ingest.js +184 -0
- package/dist/review-ingest.js.map +1 -0
- package/dist/review-read.d.ts +87 -0
- package/dist/review-read.js +361 -0
- package/dist/review-read.js.map +1 -0
- package/dist/review-rows.d.ts +30 -0
- package/dist/review-rows.js +67 -0
- package/dist/review-rows.js.map +1 -0
- package/dist/review-types.d.ts +414 -0
- package/dist/review-types.js +2 -0
- package/dist/review-types.js.map +1 -0
- package/dist/review-write.d.ts +35 -0
- package/dist/review-write.js +86 -0
- package/dist/review-write.js.map +1 -0
- package/dist/review.d.ts +53 -0
- package/dist/review.fixtures.d.ts +47 -0
- package/dist/review.fixtures.js +145 -0
- package/dist/review.fixtures.js.map +1 -0
- package/dist/review.js +245 -0
- package/dist/review.js.map +1 -0
- package/dist/schema.d.ts +36 -0
- package/dist/schema.js +39 -0
- package/dist/schema.js.map +1 -0
- package/dist/store.d.ts +58 -0
- package/dist/store.js +278 -0
- package/dist/store.js.map +1 -0
- package/dist/testing.d.ts +38 -0
- package/dist/testing.js +92 -0
- package/dist/testing.js.map +1 -0
- package/dist/ui/__probe.d.ts +3 -0
- package/dist/ui/__probe.js +5 -0
- package/dist/ui/__probe.js.map +1 -0
- package/dist/ui/attribution.d.ts +46 -0
- package/dist/ui/attribution.js +79 -0
- package/dist/ui/attribution.js.map +1 -0
- package/dist/ui/because.d.ts +51 -0
- package/dist/ui/because.js +52 -0
- package/dist/ui/because.js.map +1 -0
- package/dist/ui/browser-entry.d.ts +2 -0
- package/dist/ui/browser-entry.js +34 -0
- package/dist/ui/browser-entry.js.map +1 -0
- package/dist/ui/builds.d.ts +43 -0
- package/dist/ui/builds.js +79 -0
- package/dist/ui/builds.js.map +1 -0
- package/dist/ui/carried.d.ts +73 -0
- package/dist/ui/carried.js +63 -0
- package/dist/ui/carried.js.map +1 -0
- package/dist/ui/cause.d.ts +73 -0
- package/dist/ui/cause.js +133 -0
- package/dist/ui/cause.js.map +1 -0
- package/dist/ui/change-story.d.ts +153 -0
- package/dist/ui/change-story.js +253 -0
- package/dist/ui/change-story.js.map +1 -0
- package/dist/ui/change.d.ts +51 -0
- package/dist/ui/change.js +125 -0
- package/dist/ui/change.js.map +1 -0
- package/dist/ui/client.d.ts +74 -0
- package/dist/ui/client.js +88 -0
- package/dist/ui/client.js.map +1 -0
- package/dist/ui/consumers.d.ts +80 -0
- package/dist/ui/consumers.js +125 -0
- package/dist/ui/consumers.js.map +1 -0
- package/dist/ui/control.d.ts +63 -0
- package/dist/ui/control.js +39 -0
- package/dist/ui/control.js.map +1 -0
- package/dist/ui/crossing.d.ts +55 -0
- package/dist/ui/crossing.js +74 -0
- package/dist/ui/crossing.js.map +1 -0
- package/dist/ui/declarations.d.ts +36 -0
- package/dist/ui/declarations.js +73 -0
- package/dist/ui/declarations.js.map +1 -0
- package/dist/ui/distance.d.ts +154 -0
- package/dist/ui/distance.js +180 -0
- package/dist/ui/distance.js.map +1 -0
- package/dist/ui/divergence.d.ts +40 -0
- package/dist/ui/divergence.js +176 -0
- package/dist/ui/divergence.js.map +1 -0
- package/dist/ui/docket.d.ts +39 -0
- package/dist/ui/docket.js +172 -0
- package/dist/ui/docket.js.map +1 -0
- package/dist/ui/findings.d.ts +42 -0
- package/dist/ui/findings.js +110 -0
- package/dist/ui/findings.js.map +1 -0
- package/dist/ui/foreseen.d.ts +55 -0
- package/dist/ui/foreseen.js +91 -0
- package/dist/ui/foreseen.js.map +1 -0
- package/dist/ui/glance.d.ts +101 -0
- package/dist/ui/glance.js +101 -0
- package/dist/ui/glance.js.map +1 -0
- package/dist/ui/grouping.d.ts +158 -0
- package/dist/ui/grouping.js +187 -0
- package/dist/ui/grouping.js.map +1 -0
- package/dist/ui/handed.d.ts +60 -0
- package/dist/ui/handed.js +122 -0
- package/dist/ui/handed.js.map +1 -0
- package/dist/ui/history.d.ts +88 -0
- package/dist/ui/history.js +170 -0
- package/dist/ui/history.js.map +1 -0
- package/dist/ui/holding.d.ts +84 -0
- package/dist/ui/holding.js +90 -0
- package/dist/ui/holding.js.map +1 -0
- package/dist/ui/impact.d.ts +31 -0
- package/dist/ui/impact.js +132 -0
- package/dist/ui/impact.js.map +1 -0
- package/dist/ui/index.d.ts +21 -0
- package/dist/ui/index.js +21 -0
- package/dist/ui/index.js.map +1 -0
- package/dist/ui/lead.d.ts +35 -0
- package/dist/ui/lead.js +34 -0
- package/dist/ui/lead.js.map +1 -0
- package/dist/ui/look.d.ts +109 -0
- package/dist/ui/look.js +248 -0
- package/dist/ui/look.js.map +1 -0
- package/dist/ui/mark.d.ts +11 -0
- package/dist/ui/mark.js +13 -0
- package/dist/ui/mark.js.map +1 -0
- package/dist/ui/moved.d.ts +121 -0
- package/dist/ui/moved.js +143 -0
- package/dist/ui/moved.js.map +1 -0
- package/dist/ui/navigation.d.ts +26 -0
- package/dist/ui/navigation.js +53 -0
- package/dist/ui/navigation.js.map +1 -0
- package/dist/ui/order.d.ts +113 -0
- package/dist/ui/order.js +226 -0
- package/dist/ui/order.js.map +1 -0
- package/dist/ui/origins.d.ts +42 -0
- package/dist/ui/origins.js +149 -0
- package/dist/ui/origins.js.map +1 -0
- package/dist/ui/outcome.d.ts +93 -0
- package/dist/ui/outcome.js +162 -0
- package/dist/ui/outcome.js.map +1 -0
- package/dist/ui/parted.d.ts +68 -0
- package/dist/ui/parted.js +101 -0
- package/dist/ui/parted.js.map +1 -0
- package/dist/ui/rail.d.ts +31 -0
- package/dist/ui/rail.js +21 -0
- package/dist/ui/rail.js.map +1 -0
- package/dist/ui/reach.d.ts +66 -0
- package/dist/ui/reach.js +185 -0
- package/dist/ui/reach.js.map +1 -0
- package/dist/ui/regions.d.ts +82 -0
- package/dist/ui/regions.js +67 -0
- package/dist/ui/regions.js.map +1 -0
- package/dist/ui/review.bundle.js +69 -0
- package/dist/ui/review.d.ts +97 -0
- package/dist/ui/review.js +39 -0
- package/dist/ui/review.js.map +1 -0
- package/dist/ui/root.d.ts +72 -0
- package/dist/ui/root.js +102 -0
- package/dist/ui/root.js.map +1 -0
- package/dist/ui/route.d.ts +95 -0
- package/dist/ui/route.js +114 -0
- package/dist/ui/route.js.map +1 -0
- package/dist/ui/run.d.ts +26 -0
- package/dist/ui/run.js +12 -0
- package/dist/ui/run.js.map +1 -0
- package/dist/ui/sense.d.ts +187 -0
- package/dist/ui/sense.js +222 -0
- package/dist/ui/sense.js.map +1 -0
- package/dist/ui/settled.d.ts +43 -0
- package/dist/ui/settled.js +59 -0
- package/dist/ui/settled.js.map +1 -0
- package/dist/ui/shell.d.ts +76 -0
- package/dist/ui/shell.js +65 -0
- package/dist/ui/shell.js.map +1 -0
- package/dist/ui/shift.d.ts +52 -0
- package/dist/ui/shift.js +98 -0
- package/dist/ui/shift.js.map +1 -0
- package/dist/ui/styles-docket.d.ts +18 -0
- package/dist/ui/styles-docket.js +281 -0
- package/dist/ui/styles-docket.js.map +1 -0
- package/dist/ui/styles-stage.d.ts +18 -0
- package/dist/ui/styles-stage.js +92 -0
- package/dist/ui/styles-stage.js.map +1 -0
- package/dist/ui/styles.d.ts +12 -0
- package/dist/ui/styles.js +482 -0
- package/dist/ui/styles.js.map +1 -0
- package/dist/ui/subject.d.ts +36 -0
- package/dist/ui/subject.js +138 -0
- package/dist/ui/subject.js.map +1 -0
- package/dist/ui/text.d.ts +129 -0
- package/dist/ui/text.js +164 -0
- package/dist/ui/text.js.map +1 -0
- package/dist/ui/variations.d.ts +37 -0
- package/dist/ui/variations.js +60 -0
- package/dist/ui/variations.js.map +1 -0
- package/dist/ui/viewer.d.ts +67 -0
- package/dist/ui/viewer.js +246 -0
- package/dist/ui/viewer.js.map +1 -0
- package/dist/worker-auth.d.ts +48 -0
- package/dist/worker-auth.js +74 -0
- package/dist/worker-auth.js.map +1 -0
- package/dist/worker-entry.d.ts +58 -0
- package/dist/worker-entry.js +36 -0
- package/dist/worker-entry.js.map +1 -0
- package/dist/worker-http.d.ts +39 -0
- package/dist/worker-http.js +111 -0
- package/dist/worker-http.js.map +1 -0
- package/dist/worker-input.d.ts +81 -0
- package/dist/worker-input.js +296 -0
- package/dist/worker-input.js.map +1 -0
- package/dist/worker.d.ts +91 -0
- package/dist/worker.js +287 -0
- package/dist/worker.js.map +1 -0
- package/mark.svg +30 -0
- package/migrations/0001_initial.sql +193 -0
- package/migrations/0002_changelog.sql +30 -0
- package/migrations/0003_accessibility.sql +8 -0
- package/migrations/0004_variations.sql +19 -0
- package/migrations/0005_reach.sql +26 -0
- package/migrations/0006_baseline-size.sql +7 -0
- package/migrations/0007_declarations.sql +7 -0
- package/migrations/0008_green-because.sql +7 -0
- package/migrations/0009_moved-bands.sql +6 -0
- package/migrations/0010_composition.sql +15 -0
- package/migrations/0011_movements.sql +21 -0
- package/migrations/0012_control-size.sql +6 -0
- package/package.json +110 -0
- package/wrangler.jsonc +45 -0
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A build: the docket on the left, one thing open beside it.
|
|
3
|
+
*
|
|
4
|
+
* Two panes, and neither of them is the document. The page itself does not
|
|
5
|
+
* scroll — the rail scrolls through the changes, the stage scrolls through the
|
|
6
|
+
* one that is open, and the address says which. That is the difference between a
|
|
7
|
+
* service and a report: a report is a column of everything, read top to bottom
|
|
8
|
+
* once; a service is a place a reviewer navigates, comes back to, sends a link
|
|
9
|
+
* into, and leaves a browser tab on.
|
|
10
|
+
*
|
|
11
|
+
* The four addresses this page answers are four stages behind one rail:
|
|
12
|
+
*
|
|
13
|
+
* - **`/builds/:id`** — the rail with nothing open, and a short reading of the
|
|
14
|
+
* run beside it. Not the encyclopedia: that is `/run`, and it is a link.
|
|
15
|
+
* - **`/builds/:id/changes/:component`** — one change, whole, with its own
|
|
16
|
+
* picture and its own two buttons.
|
|
17
|
+
* - **`/builds/:id/subjects/:subject`** — one render, whole, with the rail
|
|
18
|
+
* switched to subjects.
|
|
19
|
+
* - **`/builds/:id/run`** — everything the run read, full width.
|
|
20
|
+
*
|
|
21
|
+
* The crossing against the previous build is fetched **here**, once, and handed
|
|
22
|
+
* to all four. It is what lets a change card say *build 5 showed you this and
|
|
23
|
+
* nobody decided it* without a second request per card, and it is the reason the
|
|
24
|
+
* run page and a change can never disagree about what the last run said.
|
|
25
|
+
*/
|
|
26
|
+
import { type ReactElement } from 'react';
|
|
27
|
+
import type { ReviewClient } from './client.js';
|
|
28
|
+
import type { Route } from './route.js';
|
|
29
|
+
/** The four addresses that live inside one build. */
|
|
30
|
+
export type BuildRoute = Extract<Route, {
|
|
31
|
+
page: 'build' | 'change' | 'subject' | 'run';
|
|
32
|
+
}>;
|
|
33
|
+
export declare function BuildPage({ client, reviewer, route, go, }: {
|
|
34
|
+
readonly client: ReviewClient;
|
|
35
|
+
readonly reviewer: string;
|
|
36
|
+
readonly route: BuildRoute;
|
|
37
|
+
readonly go: (route: Route) => void;
|
|
38
|
+
}): ReactElement;
|
|
39
|
+
//# sourceMappingURL=docket.d.ts.map
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* A build: the docket on the left, one thing open beside it.
|
|
4
|
+
*
|
|
5
|
+
* Two panes, and neither of them is the document. The page itself does not
|
|
6
|
+
* scroll — the rail scrolls through the changes, the stage scrolls through the
|
|
7
|
+
* one that is open, and the address says which. That is the difference between a
|
|
8
|
+
* service and a report: a report is a column of everything, read top to bottom
|
|
9
|
+
* once; a service is a place a reviewer navigates, comes back to, sends a link
|
|
10
|
+
* into, and leaves a browser tab on.
|
|
11
|
+
*
|
|
12
|
+
* The four addresses this page answers are four stages behind one rail:
|
|
13
|
+
*
|
|
14
|
+
* - **`/builds/:id`** — the rail with nothing open, and a short reading of the
|
|
15
|
+
* run beside it. Not the encyclopedia: that is `/run`, and it is a link.
|
|
16
|
+
* - **`/builds/:id/changes/:component`** — one change, whole, with its own
|
|
17
|
+
* picture and its own two buttons.
|
|
18
|
+
* - **`/builds/:id/subjects/:subject`** — one render, whole, with the rail
|
|
19
|
+
* switched to subjects.
|
|
20
|
+
* - **`/builds/:id/run`** — everything the run read, full width.
|
|
21
|
+
*
|
|
22
|
+
* The crossing against the previous build is fetched **here**, once, and handed
|
|
23
|
+
* to all four. It is what lets a change card say *build 5 showed you this and
|
|
24
|
+
* nobody decided it* without a second request per card, and it is the reason the
|
|
25
|
+
* run page and a change can never disagree about what the last run said.
|
|
26
|
+
*/
|
|
27
|
+
import { useCallback, useEffect, useState } from 'react';
|
|
28
|
+
import { ChangePanel } from './change.js';
|
|
29
|
+
import { useCrossing } from './crossing.js';
|
|
30
|
+
import { distanceFrom } from './distance.js';
|
|
31
|
+
import { Impact } from './impact.js';
|
|
32
|
+
import { originsOf } from './grouping.js';
|
|
33
|
+
import { causeOf } from './lead.js';
|
|
34
|
+
import { OriginsPanel } from './origins.js';
|
|
35
|
+
import { SubjectRail } from './rail.js';
|
|
36
|
+
import { RunPage } from './run.js';
|
|
37
|
+
import { needsReview } from './settled.js';
|
|
38
|
+
import { Failure, Go, Topbar, messageOf } from './shell.js';
|
|
39
|
+
import { SubjectPanel } from './subject.js';
|
|
40
|
+
import { count, number } from './text.js';
|
|
41
|
+
export function BuildPage({ client, reviewer, route, go, }) {
|
|
42
|
+
const [detail, setDetail] = useState({ state: 'loading' });
|
|
43
|
+
const load = useCallback(async () => {
|
|
44
|
+
try {
|
|
45
|
+
setDetail({ state: 'ready', value: await client.build(route.build) });
|
|
46
|
+
}
|
|
47
|
+
catch (error) {
|
|
48
|
+
setDetail({ state: 'failed', why: messageOf(error) });
|
|
49
|
+
}
|
|
50
|
+
}, [client, route.build]);
|
|
51
|
+
useEffect(() => {
|
|
52
|
+
void load();
|
|
53
|
+
}, [load]);
|
|
54
|
+
if (detail.state === 'loading') {
|
|
55
|
+
return (_jsx("div", { className: "va-app", children: _jsxs("p", { className: "va-note", children: ["Loading ", route.build, "\u2026"] }) }));
|
|
56
|
+
}
|
|
57
|
+
if (detail.state === 'failed') {
|
|
58
|
+
return (_jsx("div", { className: "va-app", children: _jsx(Failure, { why: detail.why, retry: load }) }));
|
|
59
|
+
}
|
|
60
|
+
return (_jsx(Build, { client: client, reviewer: reviewer, build: detail.value, route: route, go: go, reload: load }));
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* The build, once it is loaded — and the level the crossing is asked at.
|
|
64
|
+
*
|
|
65
|
+
* Split from {@link BuildPage} because {@link useCrossing} needs a build to cross
|
|
66
|
+
* and hooks cannot be called behind a loading branch.
|
|
67
|
+
*/
|
|
68
|
+
function Build({ client, reviewer, build, route, go, reload, }) {
|
|
69
|
+
const crossing = useCrossing(client, build);
|
|
70
|
+
const order = (route.page === 'build' ? route.order : undefined) ?? 'story';
|
|
71
|
+
const { origins } = originsOf(build);
|
|
72
|
+
const reviewable = build.subjects.filter((subject) => needsReview(subject.verdict));
|
|
73
|
+
const open = route.page === 'change' ? origins.find((each) => each.component === route.change) : undefined;
|
|
74
|
+
const subject = route.page === 'subject'
|
|
75
|
+
? build.subjects.find((each) => each.subject === route.subject)
|
|
76
|
+
: undefined;
|
|
77
|
+
// Anchored on the change this render is filed under: on a page with no single
|
|
78
|
+
// change, distance has nothing to be measured from.
|
|
79
|
+
const anchor = subject === undefined ? undefined : causeOf(subject);
|
|
80
|
+
return (_jsxs("div", { className: "va-app", children: [_jsxs(Topbar, { crumbs: [{ at: { page: 'builds' }, say: 'Builds' }], go: go, title: `Build ${build.build}`, subtitle: build.project, children: [_jsx("span", { className: build.pending > 0 ? 'va-pill va-warn' : 'va-pill va-good', children: build.pending > 0 ? `${number(build.pending)} awaiting review` : 'settled' }), _jsxs("span", { className: "va-topbar-meta", children: [_jsx("code", { className: "va-commit", children: build.commit.slice(0, 8) }), build.branch === undefined ? null : _jsx("span", { children: build.branch }), _jsx("span", { children: build.identity.engine }), _jsx(Go, { to: { page: 'run', build: build.build }, go: go, className: "va-mode", children: "The run" })] })] }), route.page === 'run' ? (_jsx("div", { className: "va-body", children: _jsx(RunPage, { client: client, build: build, crossing: crossing }) })) : (_jsxs("div", { className: "va-body", children: [_jsxs("nav", { className: "va-rail", children: [_jsx(Switch, { build: build.build, order: order, subjects: reviewable, route: route, go: go }), route.page === 'subject' ? (_jsx(SubjectRail, { subjects: reviewable, causes: build.causes.length, variations: build.variations.length, selected: route.subject, onSelect: (next) => go(next === null
|
|
81
|
+
? { page: 'build', build: build.build }
|
|
82
|
+
: { page: 'subject', build: build.build, subject: next }) })) : (_jsx(OriginsPanel, { build: build, crossing: crossing, order: order, selected: open?.component, go: go }))] }), subject !== undefined ? (_jsx(SubjectPanel, { client: client, reviewer: reviewer, build: build.build, subject: subject, ...(anchor === undefined ? {} : { anchor, far: distanceFrom(build, anchor) }), sourced: build.causes.some((cause) => cause.file !== undefined), onDecided: reload })) : open !== undefined ? (_jsx(ChangePanel, { client: client, reviewer: reviewer, build: build, origin: open, crossing: crossing, changes: new Set(origins.map((each) => each.component)), go: go, onDecided: reload })) : (_jsx(Opening, { build: build, crossing: crossing, route: route, go: go }))] }))] }));
|
|
83
|
+
}
|
|
84
|
+
/** Changes or subjects: the two ways to work a build, as two addresses. */
|
|
85
|
+
function Switch({ build, order, subjects, route, go, }) {
|
|
86
|
+
const first = subjects[0]?.subject;
|
|
87
|
+
return (_jsxs("div", { className: "va-switch", role: "group", "aria-label": "What to work through", children: [_jsx(Go, { to: { page: 'build', build, order }, go: go, className: route.page === 'subject' ? 'va-mode' : 'va-mode va-on', title: "One row per change: a component and everywhere it moved", children: "Changes" }), first === undefined ? (_jsx("span", { className: "va-mode va-off", title: "Nothing in this build is awaiting a decision", children: "Subjects" })) : (_jsx(Go, { to: { page: 'subject', build, subject: route.page === 'subject' ? route.subject : first }, go: go, className: route.page === 'subject' ? 'va-mode va-on' : 'va-mode', title: "One row per render, which is what the run measured", children: "Subjects" }))] }));
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* The stage with nothing open: a short reading of the build, and where to go.
|
|
91
|
+
*
|
|
92
|
+
* Everything a reader could want past this has an address, and putting it here
|
|
93
|
+
* instead is how the previous version of this page came to be read top to bottom
|
|
94
|
+
* by nobody.
|
|
95
|
+
*
|
|
96
|
+
* Under the heading there used to be a definition — *a change is a component and
|
|
97
|
+
* every render it moved in, one decision covers all of them* — printed on every
|
|
98
|
+
* build of every project, in the position a reader gives their second glance to.
|
|
99
|
+
* It is true and it is the same sentence every time, which makes it the most
|
|
100
|
+
* expensive line on the page. The heading already counts the changes; the tally,
|
|
101
|
+
* the commits and the diff below are about this build.
|
|
102
|
+
*/
|
|
103
|
+
function Opening({ build, crossing, route, go, }) {
|
|
104
|
+
const { origins, unattributed } = originsOf(build);
|
|
105
|
+
const missing = route.page === 'change' ? route.change : undefined;
|
|
106
|
+
return (_jsx("div", { className: "va-stage va-scroll", children: _jsxs("div", { className: "va-page va-opening", children: [missing === undefined ? null : (_jsxs("p", { className: "va-failure", children: ["This build has no change under ", _jsx("strong", { children: missing }), ". It may have been decided and the baseline promoted, or the link may be from another build."] })), _jsx("h1", { children: origins.length === 0
|
|
107
|
+
? 'Nothing in this build changed'
|
|
108
|
+
: `${count(origins.length, 'change')} to work through` }), origins.length === 0 ? _jsx(Still, { verdicts: build.verdicts }) : null, unattributed.length === 0 ? null : (_jsxs("p", { className: "va-note", children: [count(unattributed.length, 'render'), " moved with no component named as the cause by a region."] })), _jsx(Since, { crossing: crossing }), _jsx(Impact, { build: build, crossing: crossing }), _jsxs("p", { className: "va-opening-links", children: [_jsx(Go, { to: { page: 'run', build: build.build }, go: go, className: "va-mode", children: "What this run read \u2192" }), _jsx("span", { className: "va-note", children: "Reach, the crossing in full, the variation lattice, and what nothing looked at." })] })] }) }));
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* What a build with no changes did instead, counted.
|
|
112
|
+
*
|
|
113
|
+
* *Every subject matched its baseline, or was settled by a rule you wrote* was
|
|
114
|
+
* the line here, and the `or` is the whole problem: the two halves are different
|
|
115
|
+
* builds — one where the suite is green and one where a ledger is absorbing
|
|
116
|
+
* differences — and the store knows which. A reader who wrote an ignore rule last
|
|
117
|
+
* week and wants to know whether it is still swallowing something was being told
|
|
118
|
+
* the two possibilities they already knew about.
|
|
119
|
+
*
|
|
120
|
+
* Zeroes are dropped rather than printed, because a clean build reads as *41
|
|
121
|
+
* unchanged* and not as a row of noughts to scan past.
|
|
122
|
+
*/
|
|
123
|
+
function Still({ verdicts }) {
|
|
124
|
+
const said = [
|
|
125
|
+
verdicts.unchanged === 0 ? undefined : `${number(verdicts.unchanged)} unchanged`,
|
|
126
|
+
verdicts.ignored === 0 ? undefined : `${number(verdicts.ignored)} settled by a rule`,
|
|
127
|
+
verdicts.new === 0 ? undefined : `${count(verdicts.new, 'new subject')}`,
|
|
128
|
+
verdicts.incomparable === 0 ? undefined : `${count(verdicts.incomparable, 'incomparable')}`,
|
|
129
|
+
].filter((clause) => clause !== undefined);
|
|
130
|
+
if (said.length === 0)
|
|
131
|
+
return null;
|
|
132
|
+
return _jsx("p", { className: "va-subtitle", children: said.join(' · ') });
|
|
133
|
+
}
|
|
134
|
+
/** The build against the one before it, in one line, on the page it opens on. */
|
|
135
|
+
function Since({ crossing }) {
|
|
136
|
+
if (crossing.state === 'loading')
|
|
137
|
+
return _jsx("p", { className: "va-note", children: "Reading the previous run\u2026" });
|
|
138
|
+
if (crossing.state === 'none') {
|
|
139
|
+
return _jsx("p", { className: "va-note", children: "There is no earlier run to place this one against." });
|
|
140
|
+
}
|
|
141
|
+
if (crossing.state === 'failed') {
|
|
142
|
+
return (_jsxs("p", { className: "va-failure", children: ["The previous run could not be read, so nothing here says what has changed since it.", ' ', crossing.why] }));
|
|
143
|
+
}
|
|
144
|
+
const { shifts, held } = crossing.divergence;
|
|
145
|
+
const again = shifts.filter((each) => each.shift === 'again').length;
|
|
146
|
+
const fresh = shifts.filter((each) => each.shift === 'new' || each.shift === 'first').length;
|
|
147
|
+
const clauses = [
|
|
148
|
+
again === 0
|
|
149
|
+
? undefined
|
|
150
|
+
: `${count(again, 'subject')} ${again === 1 ? 'carries a difference it' : 'carry a difference they'} already had`,
|
|
151
|
+
fresh === 0 ? undefined : `${count(fresh, 'subject')} moved for the first time`,
|
|
152
|
+
held === 0 ? undefined : `${count(held, 'subject')} held still in both`,
|
|
153
|
+
].filter((each) => each !== undefined);
|
|
154
|
+
if (clauses.length === 0) {
|
|
155
|
+
return (_jsxs("p", { className: "va-since", children: ["Against build ", crossing.earlier.build, ": nothing here is a difference it also carried, and nothing held still in both. Every subject reads some other way than it did."] }));
|
|
156
|
+
}
|
|
157
|
+
return (_jsxs("p", { className: "va-since", children: ["Against build ", crossing.earlier.build, ": ", joined(clauses), ".", again === 0 ? '' : ' You have seen those.'] }));
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* The clauses as one sentence, in the order they were built.
|
|
161
|
+
*
|
|
162
|
+
* Only the ones with something in them. A count of zero read as *0 moved for the
|
|
163
|
+
* first time* on a run where nothing was new, and a reader has to work out that a
|
|
164
|
+
* number they were shown means the thing did not happen — absent is not zero, and
|
|
165
|
+
* a clause about nothing is a clause worth dropping.
|
|
166
|
+
*/
|
|
167
|
+
function joined(clauses) {
|
|
168
|
+
if (clauses.length === 1)
|
|
169
|
+
return clauses[0] ?? '';
|
|
170
|
+
return `${clauses.slice(0, -1).join(', ')} and ${clauses[clauses.length - 1] ?? ''}`;
|
|
171
|
+
}
|
|
172
|
+
//# sourceMappingURL=docket.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"docket.js","sourceRoot":"","sources":["../../src/ui/docket.tsx"],"names":[],"mappings":"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","sourcesContent":["/**\n * A build: the docket on the left, one thing open beside it.\n *\n * Two panes, and neither of them is the document. The page itself does not\n * scroll — the rail scrolls through the changes, the stage scrolls through the\n * one that is open, and the address says which. That is the difference between a\n * service and a report: a report is a column of everything, read top to bottom\n * once; a service is a place a reviewer navigates, comes back to, sends a link\n * into, and leaves a browser tab on.\n *\n * The four addresses this page answers are four stages behind one rail:\n *\n * - **`/builds/:id`** — the rail with nothing open, and a short reading of the\n * run beside it. Not the encyclopedia: that is `/run`, and it is a link.\n * - **`/builds/:id/changes/:component`** — one change, whole, with its own\n * picture and its own two buttons.\n * - **`/builds/:id/subjects/:subject`** — one render, whole, with the rail\n * switched to subjects.\n * - **`/builds/:id/run`** — everything the run read, full width.\n *\n * The crossing against the previous build is fetched **here**, once, and handed\n * to all four. It is what lets a change card say *build 5 showed you this and\n * nobody decided it* without a second request per card, and it is the reason the\n * run page and a change can never disagree about what the last run said.\n */\n\nimport { useCallback, useEffect, useState, type ReactElement } from 'react';\nimport type { BuildDetail } from '../review-types.js';\nimport { ChangePanel } from './change.js';\nimport type { ReviewClient } from './client.js';\nimport { useCrossing, type Crossing } from './crossing.js';\nimport { distanceFrom } from './distance.js';\nimport { Impact } from './impact.js';\nimport { originsOf } from './grouping.js';\nimport { causeOf } from './lead.js';\nimport { OriginsPanel } from './origins.js';\nimport { SubjectRail } from './rail.js';\nimport type { Order, Route } from './route.js';\nimport { RunPage } from './run.js';\nimport { needsReview } from './settled.js';\nimport { Failure, Go, Topbar, messageOf, type Loaded } from './shell.js';\nimport { SubjectPanel } from './subject.js';\nimport { count, number } from './text.js';\n\n/** The four addresses that live inside one build. */\nexport type BuildRoute = Extract<Route, { page: 'build' | 'change' | 'subject' | 'run' }>;\n\nexport function BuildPage({\n client,\n reviewer,\n route,\n go,\n}: {\n readonly client: ReviewClient;\n readonly reviewer: string;\n readonly route: BuildRoute;\n readonly go: (route: Route) => void;\n}): ReactElement {\n const [detail, setDetail] = useState<Loaded<BuildDetail>>({ state: 'loading' });\n\n const load = useCallback(async (): Promise<void> => {\n try {\n setDetail({ state: 'ready', value: await client.build(route.build) });\n } catch (error) {\n setDetail({ state: 'failed', why: messageOf(error) });\n }\n }, [client, route.build]);\n\n useEffect(() => {\n void load();\n }, [load]);\n\n if (detail.state === 'loading') {\n return (\n <div className=\"va-app\">\n <p className=\"va-note\">Loading {route.build}…</p>\n </div>\n );\n }\n if (detail.state === 'failed') {\n return (\n <div className=\"va-app\">\n <Failure why={detail.why} retry={load} />\n </div>\n );\n }\n\n return (\n <Build client={client} reviewer={reviewer} build={detail.value} route={route} go={go} reload={load} />\n );\n}\n\n/**\n * The build, once it is loaded — and the level the crossing is asked at.\n *\n * Split from {@link BuildPage} because {@link useCrossing} needs a build to cross\n * and hooks cannot be called behind a loading branch.\n */\nfunction Build({\n client,\n reviewer,\n build,\n route,\n go,\n reload,\n}: {\n readonly client: ReviewClient;\n readonly reviewer: string;\n readonly build: BuildDetail;\n readonly route: BuildRoute;\n readonly go: (route: Route) => void;\n readonly reload: () => void;\n}): ReactElement {\n const crossing = useCrossing(client, build);\n const order: Order = (route.page === 'build' ? route.order : undefined) ?? 'story';\n\n const { origins } = originsOf(build);\n const reviewable = build.subjects.filter((subject) => needsReview(subject.verdict));\n const open = route.page === 'change' ? origins.find((each) => each.component === route.change) : undefined;\n const subject =\n route.page === 'subject'\n ? build.subjects.find((each) => each.subject === route.subject)\n : undefined;\n // Anchored on the change this render is filed under: on a page with no single\n // change, distance has nothing to be measured from.\n const anchor = subject === undefined ? undefined : causeOf(subject);\n\n return (\n <div className=\"va-app\">\n <Topbar\n crumbs={[{ at: { page: 'builds' }, say: 'Builds' }]}\n go={go}\n title={`Build ${build.build}`}\n subtitle={build.project}\n >\n <span className={build.pending > 0 ? 'va-pill va-warn' : 'va-pill va-good'}>\n {build.pending > 0 ? `${number(build.pending)} awaiting review` : 'settled'}\n </span>\n <span className=\"va-topbar-meta\">\n <code className=\"va-commit\">{build.commit.slice(0, 8)}</code>\n {build.branch === undefined ? null : <span>{build.branch}</span>}\n <span>{build.identity.engine}</span>\n <Go to={{ page: 'run', build: build.build }} go={go} className=\"va-mode\">\n The run\n </Go>\n </span>\n </Topbar>\n\n {route.page === 'run' ? (\n <div className=\"va-body\">\n <RunPage client={client} build={build} crossing={crossing} />\n </div>\n ) : (\n <div className=\"va-body\">\n <nav className=\"va-rail\">\n <Switch build={build.build} order={order} subjects={reviewable} route={route} go={go} />\n {route.page === 'subject' ? (\n <SubjectRail\n subjects={reviewable}\n causes={build.causes.length}\n variations={build.variations.length}\n selected={route.subject}\n onSelect={(next) =>\n go(\n next === null\n ? { page: 'build', build: build.build }\n : { page: 'subject', build: build.build, subject: next },\n )\n }\n />\n ) : (\n <OriginsPanel\n build={build}\n crossing={crossing}\n order={order}\n selected={open?.component}\n go={go}\n />\n )}\n </nav>\n\n {subject !== undefined ? (\n <SubjectPanel\n client={client}\n reviewer={reviewer}\n build={build.build}\n subject={subject}\n {...(anchor === undefined ? {} : { anchor, far: distanceFrom(build, anchor) })}\n sourced={build.causes.some((cause) => cause.file !== undefined)}\n onDecided={reload}\n />\n ) : open !== undefined ? (\n <ChangePanel\n client={client}\n reviewer={reviewer}\n build={build}\n origin={open}\n crossing={crossing}\n changes={new Set(origins.map((each) => each.component))}\n go={go}\n onDecided={reload}\n />\n ) : (\n <Opening build={build} crossing={crossing} route={route} go={go} />\n )}\n </div>\n )}\n </div>\n );\n}\n\n/** Changes or subjects: the two ways to work a build, as two addresses. */\nfunction Switch({\n build,\n order,\n subjects,\n route,\n go,\n}: {\n readonly build: string;\n readonly order: Order;\n readonly subjects: readonly { readonly subject: string }[];\n readonly route: BuildRoute;\n readonly go: (route: Route) => void;\n}): ReactElement {\n const first = subjects[0]?.subject;\n\n return (\n <div className=\"va-switch\" role=\"group\" aria-label=\"What to work through\">\n <Go\n to={{ page: 'build', build, order }}\n go={go}\n className={route.page === 'subject' ? 'va-mode' : 'va-mode va-on'}\n title=\"One row per change: a component and everywhere it moved\"\n >\n Changes\n </Go>\n {first === undefined ? (\n <span className=\"va-mode va-off\" title=\"Nothing in this build is awaiting a decision\">\n Subjects\n </span>\n ) : (\n <Go\n to={{ page: 'subject', build, subject: route.page === 'subject' ? route.subject : first }}\n go={go}\n className={route.page === 'subject' ? 'va-mode va-on' : 'va-mode'}\n title=\"One row per render, which is what the run measured\"\n >\n Subjects\n </Go>\n )}\n </div>\n );\n}\n\n/**\n * The stage with nothing open: a short reading of the build, and where to go.\n *\n * Everything a reader could want past this has an address, and putting it here\n * instead is how the previous version of this page came to be read top to bottom\n * by nobody.\n *\n * Under the heading there used to be a definition — *a change is a component and\n * every render it moved in, one decision covers all of them* — printed on every\n * build of every project, in the position a reader gives their second glance to.\n * It is true and it is the same sentence every time, which makes it the most\n * expensive line on the page. The heading already counts the changes; the tally,\n * the commits and the diff below are about this build.\n */\nfunction Opening({\n build,\n crossing,\n route,\n go,\n}: {\n readonly build: BuildDetail;\n readonly crossing: Crossing;\n readonly route: BuildRoute;\n readonly go: (route: Route) => void;\n}): ReactElement {\n const { origins, unattributed } = originsOf(build);\n const missing = route.page === 'change' ? route.change : undefined;\n\n return (\n <div className=\"va-stage va-scroll\">\n <div className=\"va-page va-opening\">\n {missing === undefined ? null : (\n <p className=\"va-failure\">\n This build has no change under <strong>{missing}</strong>. It may have been decided and\n the baseline promoted, or the link may be from another build.\n </p>\n )}\n\n <h1>\n {origins.length === 0\n ? 'Nothing in this build changed'\n : `${count(origins.length, 'change')} to work through`}\n </h1>\n {origins.length === 0 ? <Still verdicts={build.verdicts} /> : null}\n\n {unattributed.length === 0 ? null : (\n <p className=\"va-note\">\n {count(unattributed.length, 'render')} moved with no component named as the cause by a\n region.\n </p>\n )}\n\n <Since crossing={crossing} />\n <Impact build={build} crossing={crossing} />\n\n <p className=\"va-opening-links\">\n <Go to={{ page: 'run', build: build.build }} go={go} className=\"va-mode\">\n What this run read →\n </Go>\n <span className=\"va-note\">\n Reach, the crossing in full, the variation lattice, and what nothing looked at.\n </span>\n </p>\n </div>\n </div>\n );\n}\n\n/**\n * What a build with no changes did instead, counted.\n *\n * *Every subject matched its baseline, or was settled by a rule you wrote* was\n * the line here, and the `or` is the whole problem: the two halves are different\n * builds — one where the suite is green and one where a ledger is absorbing\n * differences — and the store knows which. A reader who wrote an ignore rule last\n * week and wants to know whether it is still swallowing something was being told\n * the two possibilities they already knew about.\n *\n * Zeroes are dropped rather than printed, because a clean build reads as *41\n * unchanged* and not as a row of noughts to scan past.\n */\nfunction Still({ verdicts }: { readonly verdicts: BuildDetail['verdicts'] }): ReactElement | null {\n const said = [\n verdicts.unchanged === 0 ? undefined : `${number(verdicts.unchanged)} unchanged`,\n verdicts.ignored === 0 ? undefined : `${number(verdicts.ignored)} settled by a rule`,\n verdicts.new === 0 ? undefined : `${count(verdicts.new, 'new subject')}`,\n verdicts.incomparable === 0 ? undefined : `${count(verdicts.incomparable, 'incomparable')}`,\n ].filter((clause): clause is string => clause !== undefined);\n\n if (said.length === 0) return null;\n return <p className=\"va-subtitle\">{said.join(' · ')}</p>;\n}\n\n/** The build against the one before it, in one line, on the page it opens on. */\nfunction Since({ crossing }: { readonly crossing: Crossing }): ReactElement | null {\n if (crossing.state === 'loading') return <p className=\"va-note\">Reading the previous run…</p>;\n if (crossing.state === 'none') {\n return <p className=\"va-note\">There is no earlier run to place this one against.</p>;\n }\n if (crossing.state === 'failed') {\n return (\n <p className=\"va-failure\">\n The previous run could not be read, so nothing here says what has changed since it.{' '}\n {crossing.why}\n </p>\n );\n }\n\n const { shifts, held } = crossing.divergence;\n const again = shifts.filter((each) => each.shift === 'again').length;\n const fresh = shifts.filter((each) => each.shift === 'new' || each.shift === 'first').length;\n\n const clauses = [\n again === 0\n ? undefined\n : `${count(again, 'subject')} ${again === 1 ? 'carries a difference it' : 'carry a difference they'} already had`,\n fresh === 0 ? undefined : `${count(fresh, 'subject')} moved for the first time`,\n held === 0 ? undefined : `${count(held, 'subject')} held still in both`,\n ].filter((each): each is string => each !== undefined);\n\n if (clauses.length === 0) {\n return (\n <p className=\"va-since\">\n Against build {crossing.earlier.build}: nothing here is a difference it also carried, and\n nothing held still in both. Every subject reads some other way than it did.\n </p>\n );\n }\n\n return (\n <p className=\"va-since\">\n Against build {crossing.earlier.build}: {joined(clauses)}.\n {again === 0 ? '' : ' You have seen those.'}\n </p>\n );\n}\n\n/**\n * The clauses as one sentence, in the order they were built.\n *\n * Only the ones with something in them. A count of zero read as *0 moved for the\n * first time* on a run where nothing was new, and a reader has to work out that a\n * number they were shown means the thing did not happen — absent is not zero, and\n * a clause about nothing is a clause worth dropping.\n */\nfunction joined(clauses: readonly string[]): string {\n if (clauses.length === 1) return clauses[0] ?? '';\n return `${clauses.slice(0, -1).join(', ')} and ${clauses[clauses.length - 1] ?? ''}`;\n}\n"]}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { ReactElement } from 'react';
|
|
2
|
+
import type { SubjectView } from '../review.js';
|
|
3
|
+
/**
|
|
4
|
+
* Findings, and the three things a reader has to be told before the list is one.
|
|
5
|
+
*
|
|
6
|
+
* **Whether anything looked.** `[]` means this render was inspected and no defect
|
|
7
|
+
* was found. `undefined` means nothing inspected it. Printing the second as the
|
|
8
|
+
* first tells a reviewer the component is fine on the authority of something that
|
|
9
|
+
* never looked.
|
|
10
|
+
*
|
|
11
|
+
* **What kind of defect these are.** The rows used to arrive under a bare
|
|
12
|
+
* *Findings* as rule ids and clauses, and a reader who did not already know what
|
|
13
|
+
* the inspector checks had to infer the subject of the report from the names of
|
|
14
|
+
* its rules. The band is on the record, so the list is grouped by it and each
|
|
15
|
+
* group is headed with the word — `Accessibility` over the nine rules that are,
|
|
16
|
+
* and not over the two that are not.
|
|
17
|
+
*
|
|
18
|
+
* **Whether it is theirs, and therefore what comes first.** A finding is read
|
|
19
|
+
* from one render with no baseline consulted, so the same list prints on the run
|
|
20
|
+
* that introduced a defect and on every run after it. The panel was silent about
|
|
21
|
+
* which, then it said both in one breath — *42 defects read from this render ·
|
|
22
|
+
* 20 arrived with this change · 22 already in the baseline* — which is a sentence
|
|
23
|
+
* that opens on a number nobody can act on and gives a reviewer twenty-two
|
|
24
|
+
* afternoons of somebody else's work to scroll past on the way to their own. Now
|
|
25
|
+
* the defects this change brought lead the panel at full size, and everything the
|
|
26
|
+
* record does not put on this change is folded behind its count. *Not dated* is
|
|
27
|
+
* its own answer and is never drawn as *arrived with this change*: the reading is
|
|
28
|
+
* [`byArrival`](../../../report/src/findings.ts), shared with the HTML report so
|
|
29
|
+
* the two surfaces say it in the same words and in the same order.
|
|
30
|
+
*
|
|
31
|
+
* Drawn in three registers rather than one line. The report writes a rule id and
|
|
32
|
+
* a clause — and the clause is written to *follow a noun the report never
|
|
33
|
+
* prints*, which is how `label-mismatch reads "SNKR. shop" and is named …` used
|
|
34
|
+
* to reach this page. So the headline is the defect in a person's words, the
|
|
35
|
+
* element the clause was written for comes from `where`, and the id goes last,
|
|
36
|
+
* beside the file, where it belongs: it is the least of the three to a reviewer
|
|
37
|
+
* and the whole of it to an ignore list.
|
|
38
|
+
*/
|
|
39
|
+
export declare function Findings({ subject }: {
|
|
40
|
+
readonly subject: SubjectView;
|
|
41
|
+
}): ReactElement;
|
|
42
|
+
//# sourceMappingURL=findings.d.ts.map
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { AGE_WHY, AGE_WORDS, ageOf, arrivalLine, byArrival, byBand, carriedLine, methodLine, mixedAges, } from '@variance-authority/report';
|
|
3
|
+
import { element, headline, number, sentence } from './text.js';
|
|
4
|
+
/**
|
|
5
|
+
* Findings, and the three things a reader has to be told before the list is one.
|
|
6
|
+
*
|
|
7
|
+
* **Whether anything looked.** `[]` means this render was inspected and no defect
|
|
8
|
+
* was found. `undefined` means nothing inspected it. Printing the second as the
|
|
9
|
+
* first tells a reviewer the component is fine on the authority of something that
|
|
10
|
+
* never looked.
|
|
11
|
+
*
|
|
12
|
+
* **What kind of defect these are.** The rows used to arrive under a bare
|
|
13
|
+
* *Findings* as rule ids and clauses, and a reader who did not already know what
|
|
14
|
+
* the inspector checks had to infer the subject of the report from the names of
|
|
15
|
+
* its rules. The band is on the record, so the list is grouped by it and each
|
|
16
|
+
* group is headed with the word — `Accessibility` over the nine rules that are,
|
|
17
|
+
* and not over the two that are not.
|
|
18
|
+
*
|
|
19
|
+
* **Whether it is theirs, and therefore what comes first.** A finding is read
|
|
20
|
+
* from one render with no baseline consulted, so the same list prints on the run
|
|
21
|
+
* that introduced a defect and on every run after it. The panel was silent about
|
|
22
|
+
* which, then it said both in one breath — *42 defects read from this render ·
|
|
23
|
+
* 20 arrived with this change · 22 already in the baseline* — which is a sentence
|
|
24
|
+
* that opens on a number nobody can act on and gives a reviewer twenty-two
|
|
25
|
+
* afternoons of somebody else's work to scroll past on the way to their own. Now
|
|
26
|
+
* the defects this change brought lead the panel at full size, and everything the
|
|
27
|
+
* record does not put on this change is folded behind its count. *Not dated* is
|
|
28
|
+
* its own answer and is never drawn as *arrived with this change*: the reading is
|
|
29
|
+
* [`byArrival`](../../../report/src/findings.ts), shared with the HTML report so
|
|
30
|
+
* the two surfaces say it in the same words and in the same order.
|
|
31
|
+
*
|
|
32
|
+
* Drawn in three registers rather than one line. The report writes a rule id and
|
|
33
|
+
* a clause — and the clause is written to *follow a noun the report never
|
|
34
|
+
* prints*, which is how `label-mismatch reads "SNKR. shop" and is named …` used
|
|
35
|
+
* to reach this page. So the headline is the defect in a person's words, the
|
|
36
|
+
* element the clause was written for comes from `where`, and the id goes last,
|
|
37
|
+
* beside the file, where it belongs: it is the least of the three to a reviewer
|
|
38
|
+
* and the whole of it to an ignore list.
|
|
39
|
+
*/
|
|
40
|
+
export function Findings({ subject }) {
|
|
41
|
+
if (subject.findings === undefined) {
|
|
42
|
+
return _jsx("p", { className: "va-note", children: "This render was not inspected, so no defect list applies." });
|
|
43
|
+
}
|
|
44
|
+
if (subject.findings.length === 0) {
|
|
45
|
+
return _jsx("p", { className: "va-note", children: "Inspected, and nothing to report." });
|
|
46
|
+
}
|
|
47
|
+
const { arrived, rest, dated } = byArrival(subject.findings);
|
|
48
|
+
const carried = carriedLine(subject.findings);
|
|
49
|
+
return (_jsxs(_Fragment, { children: [_jsx("p", { className: arrived.length > 0 ? 'va-findings-lead va-findings-mine' : 'va-findings-lead', children: arrivalLine(subject.findings) }), arrived.length === 0 ? null : _jsx(Bands, { findings: arrived }), dated ? null : _jsx(Bands, { findings: rest }), carried === undefined ? null : (_jsxs("details", { className: "va-findings-rest", children: [_jsx("summary", { children: carried }), _jsx("p", { className: "va-note", children: AGE_WHY[rest.some((each) => ageOf(each) === 'standing') ? 'standing' : 'undated'] }), _jsx(Bands, { findings: rest })] })), _jsx("p", { className: "va-note va-findings-why", children: methodLine(subject.findings) })] }));
|
|
50
|
+
}
|
|
51
|
+
/** One list, banded. The same markup whichever side of the fold it is drawn on. */
|
|
52
|
+
function Bands({ findings }) {
|
|
53
|
+
const dated = mixedAges(findings);
|
|
54
|
+
return (_jsx(_Fragment, { children: byBand(findings).map((group) => (_jsxs("section", { className: "va-findings-band", children: [_jsx("h3", { className: "va-band-head", children: group.title }), _jsx("ul", { className: "va-findings", children: occurrences(group.findings).map(({ key, finding, times }) => (_jsx(Row, { finding: finding, times: times, dated: dated }, key))) })] }, group.title))) }));
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* One defect, ruled by its date, and worded only when its neighbours differ.
|
|
58
|
+
*
|
|
59
|
+
* The colour is unconditional: a reviewer scanning a panel of fourteen reads the
|
|
60
|
+
* edge before they read anything, and what they are scanning for is the one that
|
|
61
|
+
* arrived with their change — so *arrived* is the only state that gets the loud
|
|
62
|
+
* edge, and the two that are not a claim about this change share the quiet one.
|
|
63
|
+
*
|
|
64
|
+
* The phrase is not. The list a row sits in is now split by date, so the heading
|
|
65
|
+
* above it already said *arrived with this change* once, and repeating it down
|
|
66
|
+
* fourteen rows is a word a reader stops seeing. `dated` is the one case where
|
|
67
|
+
* the row is carrying the finding instead of echoing the heading: a fold holding
|
|
68
|
+
* both a defect the baseline had and one nothing dated.
|
|
69
|
+
*/
|
|
70
|
+
function Row({ finding, times, dated, }) {
|
|
71
|
+
const age = ageOf(finding);
|
|
72
|
+
return (_jsxs("li", { className: `va-finding va-finding-${age}`, children: [_jsxs("p", { className: "va-finding-title", children: [_jsx("span", { className: "va-finding-said", children: headline(finding.rule) }), _jsxs("span", { className: "va-finding-marks", children: [times > 1 ? _jsxs("span", { className: "va-times", children: [number(times), " places"] }) : null, dated ? (_jsx("span", { className: `va-age va-age-${age}`, title: AGE_WHY[age], children: AGE_WORDS[age] })) : null] })] }), _jsx("p", { className: "va-finding-where", children: element(finding.where) ?? finding.path }), _jsx("p", { className: "va-finding-what", children: sentence(finding.what) }), _jsxs("p", { className: "va-finding-owner", children: [finding.component === undefined ? null : (_jsx("span", { className: "va-tag", children: finding.component })), finding.file === undefined ? null : _jsx("code", { className: "va-tag", children: finding.file }), _jsx("span", { className: "va-tag va-rule", children: finding.rule })] })] }));
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* One defect, however many times the render draws the component carrying it.
|
|
76
|
+
*
|
|
77
|
+
* A listing page draws the same card twenty times, so a rule that fires on that
|
|
78
|
+
* card fires twenty times, and this panel printed twenty identical blocks: same
|
|
79
|
+
* rule, same element, same line of the same file, differing only in a DOM path
|
|
80
|
+
* no reviewer reads. That is not twenty decisions. It is one defect in `Button`,
|
|
81
|
+
* and the number worth printing is how far it reached.
|
|
82
|
+
*
|
|
83
|
+
* Grouped on everything the entry actually shows, which is all of a finding
|
|
84
|
+
* except its path. Two rows a reader cannot tell apart are one finding; `where`
|
|
85
|
+
* stays in the key, so two genuinely different elements stay two rows, and so
|
|
86
|
+
* does `standing` — a rule that fired on an element the baseline had and on one
|
|
87
|
+
* it did not is the case this panel exists to separate, and folding the two would
|
|
88
|
+
* report the new element under the old one's date. Insertion order is kept,
|
|
89
|
+
* because the report's order is the order of the render.
|
|
90
|
+
*/
|
|
91
|
+
function occurrences(findings) {
|
|
92
|
+
const seen = new Map();
|
|
93
|
+
for (const finding of findings) {
|
|
94
|
+
const key = [
|
|
95
|
+
finding.rule,
|
|
96
|
+
finding.component ?? '',
|
|
97
|
+
finding.file ?? '',
|
|
98
|
+
finding.where ?? '',
|
|
99
|
+
finding.what,
|
|
100
|
+
ageOf(finding),
|
|
101
|
+
].join(' · ');
|
|
102
|
+
const already = seen.get(key);
|
|
103
|
+
if (already === undefined)
|
|
104
|
+
seen.set(key, { key, finding, times: 1 });
|
|
105
|
+
else
|
|
106
|
+
already.times += 1;
|
|
107
|
+
}
|
|
108
|
+
return [...seen.values()];
|
|
109
|
+
}
|
|
110
|
+
//# sourceMappingURL=findings.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"findings.js","sourceRoot":"","sources":["../../src/ui/findings.tsx"],"names":[],"mappings":"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","sourcesContent":["import {\n AGE_WHY,\n AGE_WORDS,\n ageOf,\n arrivalLine,\n byArrival,\n byBand,\n carriedLine,\n methodLine,\n mixedAges,\n} from '@variance-authority/report';\nimport type { FindingRecord } from '@variance-authority/report';\nimport type { ReactElement } from 'react';\nimport type { SubjectView } from '../review.js';\nimport { element, headline, number, sentence } from './text.js';\n\n/**\n * Findings, and the three things a reader has to be told before the list is one.\n *\n * **Whether anything looked.** `[]` means this render was inspected and no defect\n * was found. `undefined` means nothing inspected it. Printing the second as the\n * first tells a reviewer the component is fine on the authority of something that\n * never looked.\n *\n * **What kind of defect these are.** The rows used to arrive under a bare\n * *Findings* as rule ids and clauses, and a reader who did not already know what\n * the inspector checks had to infer the subject of the report from the names of\n * its rules. The band is on the record, so the list is grouped by it and each\n * group is headed with the word — `Accessibility` over the nine rules that are,\n * and not over the two that are not.\n *\n * **Whether it is theirs, and therefore what comes first.** A finding is read\n * from one render with no baseline consulted, so the same list prints on the run\n * that introduced a defect and on every run after it. The panel was silent about\n * which, then it said both in one breath — *42 defects read from this render ·\n * 20 arrived with this change · 22 already in the baseline* — which is a sentence\n * that opens on a number nobody can act on and gives a reviewer twenty-two\n * afternoons of somebody else's work to scroll past on the way to their own. Now\n * the defects this change brought lead the panel at full size, and everything the\n * record does not put on this change is folded behind its count. *Not dated* is\n * its own answer and is never drawn as *arrived with this change*: the reading is\n * [`byArrival`](../../../report/src/findings.ts), shared with the HTML report so\n * the two surfaces say it in the same words and in the same order.\n *\n * Drawn in three registers rather than one line. The report writes a rule id and\n * a clause — and the clause is written to *follow a noun the report never\n * prints*, which is how `label-mismatch reads \"SNKR. shop\" and is named …` used\n * to reach this page. So the headline is the defect in a person's words, the\n * element the clause was written for comes from `where`, and the id goes last,\n * beside the file, where it belongs: it is the least of the three to a reviewer\n * and the whole of it to an ignore list.\n */\nexport function Findings({ subject }: { readonly subject: SubjectView }): ReactElement {\n if (subject.findings === undefined) {\n return <p className=\"va-note\">This render was not inspected, so no defect list applies.</p>;\n }\n if (subject.findings.length === 0) {\n return <p className=\"va-note\">Inspected, and nothing to report.</p>;\n }\n\n const { arrived, rest, dated } = byArrival(subject.findings);\n const carried = carriedLine(subject.findings);\n\n return (\n <>\n <p className={arrived.length > 0 ? 'va-findings-lead va-findings-mine' : 'va-findings-lead'}>\n {arrivalLine(subject.findings)}\n </p>\n\n {arrived.length === 0 ? null : <Bands findings={arrived} />}\n {dated ? null : <Bands findings={rest} />}\n\n {carried === undefined ? null : (\n <details className=\"va-findings-rest\">\n <summary>{carried}</summary>\n <p className=\"va-note\">\n {AGE_WHY[rest.some((each) => ageOf(each) === 'standing') ? 'standing' : 'undated']}\n </p>\n <Bands findings={rest} />\n </details>\n )}\n\n <p className=\"va-note va-findings-why\">{methodLine(subject.findings)}</p>\n </>\n );\n}\n\n/** One list, banded. The same markup whichever side of the fold it is drawn on. */\nfunction Bands({ findings }: { readonly findings: readonly FindingRecord[] }): ReactElement {\n const dated = mixedAges(findings);\n\n return (\n <>\n {byBand(findings).map((group) => (\n <section key={group.title} className=\"va-findings-band\">\n <h3 className=\"va-band-head\">{group.title}</h3>\n <ul className=\"va-findings\">\n {occurrences(group.findings).map(({ key, finding, times }) => (\n <Row key={key} finding={finding} times={times} dated={dated} />\n ))}\n </ul>\n </section>\n ))}\n </>\n );\n}\n\n/**\n * One defect, ruled by its date, and worded only when its neighbours differ.\n *\n * The colour is unconditional: a reviewer scanning a panel of fourteen reads the\n * edge before they read anything, and what they are scanning for is the one that\n * arrived with their change — so *arrived* is the only state that gets the loud\n * edge, and the two that are not a claim about this change share the quiet one.\n *\n * The phrase is not. The list a row sits in is now split by date, so the heading\n * above it already said *arrived with this change* once, and repeating it down\n * fourteen rows is a word a reader stops seeing. `dated` is the one case where\n * the row is carrying the finding instead of echoing the heading: a fold holding\n * both a defect the baseline had and one nothing dated.\n */\nfunction Row({\n finding,\n times,\n dated,\n}: {\n readonly finding: FindingRecord;\n readonly times: number;\n /** Whether this list mixes dates, and so whether the row has to name its own. */\n readonly dated: boolean;\n}): ReactElement {\n const age = ageOf(finding);\n\n return (\n <li className={`va-finding va-finding-${age}`}>\n <p className=\"va-finding-title\">\n <span className=\"va-finding-said\">{headline(finding.rule)}</span>\n <span className=\"va-finding-marks\">\n {times > 1 ? <span className=\"va-times\">{number(times)} places</span> : null}\n {dated ? (\n <span className={`va-age va-age-${age}`} title={AGE_WHY[age]}>\n {AGE_WORDS[age]}\n </span>\n ) : null}\n </span>\n </p>\n <p className=\"va-finding-where\">{element(finding.where) ?? finding.path}</p>\n <p className=\"va-finding-what\">{sentence(finding.what)}</p>\n <p className=\"va-finding-owner\">\n {finding.component === undefined ? null : (\n <span className=\"va-tag\">{finding.component}</span>\n )}\n {finding.file === undefined ? null : <code className=\"va-tag\">{finding.file}</code>}\n <span className=\"va-tag va-rule\">{finding.rule}</span>\n </p>\n </li>\n );\n}\n\n/** One defect, and the number of times a single render repeats it. */\ninterface Occurrence {\n readonly key: string;\n readonly finding: FindingRecord;\n times: number;\n}\n\n/**\n * One defect, however many times the render draws the component carrying it.\n *\n * A listing page draws the same card twenty times, so a rule that fires on that\n * card fires twenty times, and this panel printed twenty identical blocks: same\n * rule, same element, same line of the same file, differing only in a DOM path\n * no reviewer reads. That is not twenty decisions. It is one defect in `Button`,\n * and the number worth printing is how far it reached.\n *\n * Grouped on everything the entry actually shows, which is all of a finding\n * except its path. Two rows a reader cannot tell apart are one finding; `where`\n * stays in the key, so two genuinely different elements stay two rows, and so\n * does `standing` — a rule that fired on an element the baseline had and on one\n * it did not is the case this panel exists to separate, and folding the two would\n * report the new element under the old one's date. Insertion order is kept,\n * because the report's order is the order of the render.\n */\nfunction occurrences(findings: readonly FindingRecord[]): readonly Occurrence[] {\n const seen = new Map<string, Occurrence>();\n for (const finding of findings) {\n const key = [\n finding.rule,\n finding.component ?? '',\n finding.file ?? '',\n finding.where ?? '',\n finding.what,\n ageOf(finding),\n ].join(' · ');\n const already = seen.get(key);\n if (already === undefined) seen.set(key, { key, finding, times: 1 });\n else already.times += 1;\n }\n return [...seen.values()];\n}\n"]}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What the commit implied, set against what the run found — in both directions.
|
|
3
|
+
*
|
|
4
|
+
* A diff reaches subjects. That is a prediction, made before a single pixel was
|
|
5
|
+
* compared: *editing `ui/button.tsx` can arrive at these sixteen*. The run then
|
|
6
|
+
* compares them and finds fourteen moved. Nothing on the page has ever put those
|
|
7
|
+
* two numbers next to each other, and the gap between them is the only part of a
|
|
8
|
+
* build a reviewer cannot work out for themselves.
|
|
9
|
+
*
|
|
10
|
+
* ## The two directions fail differently
|
|
11
|
+
*
|
|
12
|
+
* **Reached and still** is the edit that did not land. Two `Button` stories are
|
|
13
|
+
* reached by the file the commit changed, were compared against a baseline, and
|
|
14
|
+
* nothing moved. Either the change does not do what its author thinks, or those
|
|
15
|
+
* two variants do not exercise it — and both are worth a reviewer's minute
|
|
16
|
+
* before they approve the fourteen that did move.
|
|
17
|
+
*
|
|
18
|
+
* **Changed and unreached** is the other way round, and it is the more serious
|
|
19
|
+
* one: something moved that the commit cannot explain. It is not attributed to a
|
|
20
|
+
* root, because attributing it would be the invention this module exists to
|
|
21
|
+
* catch, so it is reported against the build.
|
|
22
|
+
*
|
|
23
|
+
* ## Excluded is not still, and uncompared is not either
|
|
24
|
+
*
|
|
25
|
+
* The `MainNav` stories moved by three hundred pixels and every one of them fell
|
|
26
|
+
* inside a declared ignore. Filing them under *still* would tell a reviewer the
|
|
27
|
+
* edit missed the component it was written for, when in fact it hit it and a
|
|
28
|
+
* rule forgave it. And a subject with no baseline was never asked the question at
|
|
29
|
+
* all. Three outcomes, three lists, because a single count of *did not move*
|
|
30
|
+
* would be three findings averaged into one wrong one.
|
|
31
|
+
*/
|
|
32
|
+
import type { BuildDetail } from '../review-types.js';
|
|
33
|
+
/** One changed file's prediction, and what became of it. */
|
|
34
|
+
export interface Foreseen {
|
|
35
|
+
/** Subjects the diff reaches through the components declared in this file. */
|
|
36
|
+
readonly reached: number;
|
|
37
|
+
/** Of those, the ones the run found changed. */
|
|
38
|
+
readonly moved: number;
|
|
39
|
+
/** Reached, compared, and nothing moved. The prediction that failed. */
|
|
40
|
+
readonly still: readonly string[];
|
|
41
|
+
/** Reached and moved, with every differing pixel inside a rule that forgives it. */
|
|
42
|
+
readonly excluded: readonly string[];
|
|
43
|
+
/** Reached and never compared — no baseline, or nothing to compare it against. */
|
|
44
|
+
readonly uncompared: readonly string[];
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* The prediction this file made, or `null` when the run made none.
|
|
48
|
+
*
|
|
49
|
+
* `null` covers both a build with no diff and a diff the run could not attribute
|
|
50
|
+
* to subjects. Zeroes would say the file reaches nothing, which is a finding, and
|
|
51
|
+
* this module must not manufacture one out of a missing input.
|
|
52
|
+
*/
|
|
53
|
+
export declare function foreseenBy(build: BuildDetail, file: string): Foreseen | null;
|
|
54
|
+
export declare function unforeseen(build: BuildDetail): readonly string[] | null;
|
|
55
|
+
//# sourceMappingURL=foreseen.d.ts.map
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What the commit implied, set against what the run found — in both directions.
|
|
3
|
+
*
|
|
4
|
+
* A diff reaches subjects. That is a prediction, made before a single pixel was
|
|
5
|
+
* compared: *editing `ui/button.tsx` can arrive at these sixteen*. The run then
|
|
6
|
+
* compares them and finds fourteen moved. Nothing on the page has ever put those
|
|
7
|
+
* two numbers next to each other, and the gap between them is the only part of a
|
|
8
|
+
* build a reviewer cannot work out for themselves.
|
|
9
|
+
*
|
|
10
|
+
* ## The two directions fail differently
|
|
11
|
+
*
|
|
12
|
+
* **Reached and still** is the edit that did not land. Two `Button` stories are
|
|
13
|
+
* reached by the file the commit changed, were compared against a baseline, and
|
|
14
|
+
* nothing moved. Either the change does not do what its author thinks, or those
|
|
15
|
+
* two variants do not exercise it — and both are worth a reviewer's minute
|
|
16
|
+
* before they approve the fourteen that did move.
|
|
17
|
+
*
|
|
18
|
+
* **Changed and unreached** is the other way round, and it is the more serious
|
|
19
|
+
* one: something moved that the commit cannot explain. It is not attributed to a
|
|
20
|
+
* root, because attributing it would be the invention this module exists to
|
|
21
|
+
* catch, so it is reported against the build.
|
|
22
|
+
*
|
|
23
|
+
* ## Excluded is not still, and uncompared is not either
|
|
24
|
+
*
|
|
25
|
+
* The `MainNav` stories moved by three hundred pixels and every one of them fell
|
|
26
|
+
* inside a declared ignore. Filing them under *still* would tell a reviewer the
|
|
27
|
+
* edit missed the component it was written for, when in fact it hit it and a
|
|
28
|
+
* rule forgave it. And a subject with no baseline was never asked the question at
|
|
29
|
+
* all. Three outcomes, three lists, because a single count of *did not move*
|
|
30
|
+
* would be three findings averaged into one wrong one.
|
|
31
|
+
*/
|
|
32
|
+
/**
|
|
33
|
+
* The prediction this file made, or `null` when the run made none.
|
|
34
|
+
*
|
|
35
|
+
* `null` covers both a build with no diff and a diff the run could not attribute
|
|
36
|
+
* to subjects. Zeroes would say the file reaches nothing, which is a finding, and
|
|
37
|
+
* this module must not manufacture one out of a missing input.
|
|
38
|
+
*/
|
|
39
|
+
export function foreseenBy(build, file) {
|
|
40
|
+
const per = build.reach?.subjects;
|
|
41
|
+
if (per === undefined)
|
|
42
|
+
return null;
|
|
43
|
+
const mine = new Set((build.reach?.components ?? [])
|
|
44
|
+
.filter((each) => each.trail[0] === file)
|
|
45
|
+
.map((each) => each.component));
|
|
46
|
+
if (mine.size === 0)
|
|
47
|
+
return null;
|
|
48
|
+
const verdicts = new Map(build.subjects.map((each) => [each.subject, each.verdict]));
|
|
49
|
+
const still = [];
|
|
50
|
+
const excluded = [];
|
|
51
|
+
const uncompared = [];
|
|
52
|
+
let reached = 0;
|
|
53
|
+
let moved = 0;
|
|
54
|
+
for (const [subject, how] of Object.entries(per)) {
|
|
55
|
+
if (!how.reached || !(how.through ?? []).some((each) => mine.has(each)))
|
|
56
|
+
continue;
|
|
57
|
+
reached += 1;
|
|
58
|
+
switch (verdicts.get(subject)) {
|
|
59
|
+
case 'changed':
|
|
60
|
+
moved += 1;
|
|
61
|
+
break;
|
|
62
|
+
case 'unchanged':
|
|
63
|
+
still.push(subject);
|
|
64
|
+
break;
|
|
65
|
+
case 'ignored':
|
|
66
|
+
excluded.push(subject);
|
|
67
|
+
break;
|
|
68
|
+
default:
|
|
69
|
+
// `new`, `incomparable`, and a subject the reach names that this build
|
|
70
|
+
// has no row for. None of them answered the question.
|
|
71
|
+
uncompared.push(subject);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return {
|
|
75
|
+
reached,
|
|
76
|
+
moved,
|
|
77
|
+
still: still.sort(),
|
|
78
|
+
excluded: excluded.sort(),
|
|
79
|
+
uncompared: uncompared.sort(),
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
export function unforeseen(build) {
|
|
83
|
+
const per = build.reach?.subjects;
|
|
84
|
+
if (per === undefined)
|
|
85
|
+
return null;
|
|
86
|
+
return build.subjects
|
|
87
|
+
.filter((each) => each.verdict === 'changed' && per[each.subject]?.reached !== true)
|
|
88
|
+
.map((each) => each.subject)
|
|
89
|
+
.sort();
|
|
90
|
+
}
|
|
91
|
+
//# sourceMappingURL=foreseen.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"foreseen.js","sourceRoot":"","sources":["../../src/ui/foreseen.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAkBH;;;;;;GAMG;AACH,MAAM,UAAU,UAAU,CAAC,KAAkB,EAAE,IAAY;IACzD,MAAM,GAAG,GAAG,KAAK,CAAC,KAAK,EAAE,QAAQ,CAAC;IAClC,IAAI,GAAG,KAAK,SAAS;QAAE,OAAO,IAAI,CAAC;IAEnC,MAAM,IAAI,GAAG,IAAI,GAAG,CAClB,CAAC,KAAK,CAAC,KAAK,EAAE,UAAU,IAAI,EAAE,CAAC;SAC5B,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC;SACxC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,CACjC,CAAC;IACF,IAAI,IAAI,CAAC,IAAI,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IAEjC,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;IACrF,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,MAAM,QAAQ,GAAa,EAAE,CAAC;IAC9B,MAAM,UAAU,GAAa,EAAE,CAAC;IAChC,IAAI,OAAO,GAAG,CAAC,CAAC;IAChB,IAAI,KAAK,GAAG,CAAC,CAAC;IAEd,KAAK,MAAM,CAAC,OAAO,EAAE,GAAG,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;QACjD,IAAI,CAAC,GAAG,CAAC,OAAO,IAAI,CAAC,CAAC,GAAG,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;YAAE,SAAS;QAClF,OAAO,IAAI,CAAC,CAAC;QAEb,QAAQ,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC;YAC9B,KAAK,SAAS;gBACZ,KAAK,IAAI,CAAC,CAAC;gBACX,MAAM;YACR,KAAK,WAAW;gBACd,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;gBACpB,MAAM;YACR,KAAK,SAAS;gBACZ,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;gBACvB,MAAM;YACR;gBACE,uEAAuE;gBACvE,sDAAsD;gBACtD,UAAU,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAC7B,CAAC;IACH,CAAC;IAED,OAAO;QACL,OAAO;QACP,KAAK;QACL,KAAK,EAAE,KAAK,CAAC,IAAI,EAAE;QACnB,QAAQ,EAAE,QAAQ,CAAC,IAAI,EAAE;QACzB,UAAU,EAAE,UAAU,CAAC,IAAI,EAAE;KAC9B,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,KAAkB;IAC3C,MAAM,GAAG,GAAG,KAAK,CAAC,KAAK,EAAE,QAAQ,CAAC;IAClC,IAAI,GAAG,KAAK,SAAS;QAAE,OAAO,IAAI,CAAC;IAEnC,OAAO,KAAK,CAAC,QAAQ;SAClB,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,OAAO,KAAK,SAAS,IAAI,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,OAAO,KAAK,IAAI,CAAC;SACnF,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC;SAC3B,IAAI,EAAE,CAAC;AACZ,CAAC","sourcesContent":["/**\n * What the commit implied, set against what the run found — in both directions.\n *\n * A diff reaches subjects. That is a prediction, made before a single pixel was\n * compared: *editing `ui/button.tsx` can arrive at these sixteen*. The run then\n * compares them and finds fourteen moved. Nothing on the page has ever put those\n * two numbers next to each other, and the gap between them is the only part of a\n * build a reviewer cannot work out for themselves.\n *\n * ## The two directions fail differently\n *\n * **Reached and still** is the edit that did not land. Two `Button` stories are\n * reached by the file the commit changed, were compared against a baseline, and\n * nothing moved. Either the change does not do what its author thinks, or those\n * two variants do not exercise it — and both are worth a reviewer's minute\n * before they approve the fourteen that did move.\n *\n * **Changed and unreached** is the other way round, and it is the more serious\n * one: something moved that the commit cannot explain. It is not attributed to a\n * root, because attributing it would be the invention this module exists to\n * catch, so it is reported against the build.\n *\n * ## Excluded is not still, and uncompared is not either\n *\n * The `MainNav` stories moved by three hundred pixels and every one of them fell\n * inside a declared ignore. Filing them under *still* would tell a reviewer the\n * edit missed the component it was written for, when in fact it hit it and a\n * rule forgave it. And a subject with no baseline was never asked the question at\n * all. Three outcomes, three lists, because a single count of *did not move*\n * would be three findings averaged into one wrong one.\n */\n\nimport type { BuildDetail } from '../review-types.js';\n\n/** One changed file's prediction, and what became of it. */\nexport interface Foreseen {\n /** Subjects the diff reaches through the components declared in this file. */\n readonly reached: number;\n /** Of those, the ones the run found changed. */\n readonly moved: number;\n /** Reached, compared, and nothing moved. The prediction that failed. */\n readonly still: readonly string[];\n /** Reached and moved, with every differing pixel inside a rule that forgives it. */\n readonly excluded: readonly string[];\n /** Reached and never compared — no baseline, or nothing to compare it against. */\n readonly uncompared: readonly string[];\n}\n\n/**\n * The prediction this file made, or `null` when the run made none.\n *\n * `null` covers both a build with no diff and a diff the run could not attribute\n * to subjects. Zeroes would say the file reaches nothing, which is a finding, and\n * this module must not manufacture one out of a missing input.\n */\nexport function foreseenBy(build: BuildDetail, file: string): Foreseen | null {\n const per = build.reach?.subjects;\n if (per === undefined) return null;\n\n const mine = new Set(\n (build.reach?.components ?? [])\n .filter((each) => each.trail[0] === file)\n .map((each) => each.component),\n );\n if (mine.size === 0) return null;\n\n const verdicts = new Map(build.subjects.map((each) => [each.subject, each.verdict]));\n const still: string[] = [];\n const excluded: string[] = [];\n const uncompared: string[] = [];\n let reached = 0;\n let moved = 0;\n\n for (const [subject, how] of Object.entries(per)) {\n if (!how.reached || !(how.through ?? []).some((each) => mine.has(each))) continue;\n reached += 1;\n\n switch (verdicts.get(subject)) {\n case 'changed':\n moved += 1;\n break;\n case 'unchanged':\n still.push(subject);\n break;\n case 'ignored':\n excluded.push(subject);\n break;\n default:\n // `new`, `incomparable`, and a subject the reach names that this build\n // has no row for. None of them answered the question.\n uncompared.push(subject);\n }\n }\n\n return {\n reached,\n moved,\n still: still.sort(),\n excluded: excluded.sort(),\n uncompared: uncompared.sort(),\n };\n}\n\nexport function unforeseen(build: BuildDetail): readonly string[] | null {\n const per = build.reach?.subjects;\n if (per === undefined) return null;\n\n return build.subjects\n .filter((each) => each.verdict === 'changed' && per[each.subject]?.reached !== true)\n .map((each) => each.subject)\n .sort();\n}\n"]}
|