@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,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The chrome every page wears, and the three things every page needs.
|
|
3
|
+
*
|
|
4
|
+
* Apart from the pages because it is the part that must not differ between them:
|
|
5
|
+
* a topbar that moves by four pixels between the docket and a subject reads as a
|
|
6
|
+
* page that reloaded, and a failure rendered two ways is a failure a reader has
|
|
7
|
+
* to learn twice.
|
|
8
|
+
*/
|
|
9
|
+
import { type ReactElement, type ReactNode } from 'react';
|
|
10
|
+
import { type Route } from './route.js';
|
|
11
|
+
/** Something fetched, in the three states it can be in. Never two. */
|
|
12
|
+
export type Loaded<T> = {
|
|
13
|
+
readonly state: 'loading';
|
|
14
|
+
} | {
|
|
15
|
+
readonly state: 'failed';
|
|
16
|
+
readonly why: string;
|
|
17
|
+
} | {
|
|
18
|
+
readonly state: 'ready';
|
|
19
|
+
readonly value: T;
|
|
20
|
+
};
|
|
21
|
+
/** An error as a sentence a reader can act on, never an empty list. */
|
|
22
|
+
export declare function Failure({ why, retry, }: {
|
|
23
|
+
readonly why: string;
|
|
24
|
+
readonly retry: () => void;
|
|
25
|
+
}): ReactElement;
|
|
26
|
+
/**
|
|
27
|
+
* Report prose, with its identifiers set as identifiers.
|
|
28
|
+
*
|
|
29
|
+
* The sentences on this page were written by the observer, not by this surface,
|
|
30
|
+
* and they are shown as written — this is the record, and a review page that
|
|
31
|
+
* paraphrases it is a review page a reviewer cannot check. The only thing done
|
|
32
|
+
* to them is typographic.
|
|
33
|
+
*/
|
|
34
|
+
export declare function Prose({ text }: {
|
|
35
|
+
readonly text: string;
|
|
36
|
+
}): ReactElement;
|
|
37
|
+
/**
|
|
38
|
+
* A link that is a link.
|
|
39
|
+
*
|
|
40
|
+
* `<a href>` rather than a button, because the address is real: a reviewer who
|
|
41
|
+
* middle-clicks a change gets it in a tab, and one who copies the link gets a
|
|
42
|
+
* link that opens on the same change. The click is intercepted so an ordinary
|
|
43
|
+
* navigation stays a repaint — but only the ordinary one. A modified click, or
|
|
44
|
+
* anything but the primary button, is the browser's.
|
|
45
|
+
*/
|
|
46
|
+
export declare function Go({ to, go, className, title, children, }: {
|
|
47
|
+
readonly to: Route;
|
|
48
|
+
readonly go: (route: Route) => void;
|
|
49
|
+
readonly className?: string | undefined;
|
|
50
|
+
readonly title?: string | undefined;
|
|
51
|
+
readonly children: ReactNode;
|
|
52
|
+
}): ReactElement;
|
|
53
|
+
/**
|
|
54
|
+
* The bar at the top of every page: who you are looking at, and the way back.
|
|
55
|
+
*
|
|
56
|
+
* The crumbs are the whole navigation. There is no menu, because there are seven
|
|
57
|
+
* pages and five of them are *about* the page above them — a build is about a
|
|
58
|
+
* project, a change is about a build. A trail says that; a menu asserts they are
|
|
59
|
+
* peers.
|
|
60
|
+
*/
|
|
61
|
+
export declare function Topbar({ crumbs, go, title, subtitle, children, }: {
|
|
62
|
+
readonly crumbs: readonly {
|
|
63
|
+
readonly at: Route;
|
|
64
|
+
readonly say: string;
|
|
65
|
+
}[];
|
|
66
|
+
readonly go: (route: Route) => void;
|
|
67
|
+
readonly title: string;
|
|
68
|
+
readonly subtitle?: string | undefined;
|
|
69
|
+
readonly children?: ReactNode;
|
|
70
|
+
}): ReactElement;
|
|
71
|
+
/** The one page with no data on it: an address this surface does not answer. */
|
|
72
|
+
export declare function Nowhere({ go }: {
|
|
73
|
+
readonly go: (route: Route) => void;
|
|
74
|
+
}): ReactElement;
|
|
75
|
+
export declare function messageOf(error: unknown): string;
|
|
76
|
+
//# sourceMappingURL=shell.d.ts.map
|
package/dist/ui/shell.js
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* The chrome every page wears, and the three things every page needs.
|
|
4
|
+
*
|
|
5
|
+
* Apart from the pages because it is the part that must not differ between them:
|
|
6
|
+
* a topbar that moves by four pixels between the docket and a subject reads as a
|
|
7
|
+
* page that reloaded, and a failure rendered two ways is a failure a reader has
|
|
8
|
+
* to learn twice.
|
|
9
|
+
*/
|
|
10
|
+
import { Fragment } from 'react';
|
|
11
|
+
import { Mark } from './mark.js';
|
|
12
|
+
import { hrefOf } from './route.js';
|
|
13
|
+
import { segments } from './text.js';
|
|
14
|
+
/** An error as a sentence a reader can act on, never an empty list. */
|
|
15
|
+
export function Failure({ why, retry, }) {
|
|
16
|
+
return (_jsxs("p", { className: "va-failure", children: [why, ' ', _jsx("button", { type: "button", onClick: retry, children: "retry" })] }));
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Report prose, with its identifiers set as identifiers.
|
|
20
|
+
*
|
|
21
|
+
* The sentences on this page were written by the observer, not by this surface,
|
|
22
|
+
* and they are shown as written — this is the record, and a review page that
|
|
23
|
+
* paraphrases it is a review page a reviewer cannot check. The only thing done
|
|
24
|
+
* to them is typographic.
|
|
25
|
+
*/
|
|
26
|
+
export function Prose({ text }) {
|
|
27
|
+
return (_jsx(_Fragment, { children: segments(text).map((part, index) => part.code ? (_jsx("code", { children: part.text }, `${String(index)}-${part.text}`)) : (_jsx(Fragment, { children: part.text }, `${String(index)}-${part.text}`))) }));
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* A link that is a link.
|
|
31
|
+
*
|
|
32
|
+
* `<a href>` rather than a button, because the address is real: a reviewer who
|
|
33
|
+
* middle-clicks a change gets it in a tab, and one who copies the link gets a
|
|
34
|
+
* link that opens on the same change. The click is intercepted so an ordinary
|
|
35
|
+
* navigation stays a repaint — but only the ordinary one. A modified click, or
|
|
36
|
+
* anything but the primary button, is the browser's.
|
|
37
|
+
*/
|
|
38
|
+
export function Go({ to, go, className, title, children, }) {
|
|
39
|
+
return (_jsx("a", { href: hrefOf(to), className: className, ...(title === undefined ? {} : { title }), onClick: (event) => {
|
|
40
|
+
if (event.metaKey || event.ctrlKey || event.shiftKey || event.altKey || event.button !== 0) {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
event.preventDefault();
|
|
44
|
+
go(to);
|
|
45
|
+
}, children: children }));
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* The bar at the top of every page: who you are looking at, and the way back.
|
|
49
|
+
*
|
|
50
|
+
* The crumbs are the whole navigation. There is no menu, because there are seven
|
|
51
|
+
* pages and five of them are *about* the page above them — a build is about a
|
|
52
|
+
* project, a change is about a build. A trail says that; a menu asserts they are
|
|
53
|
+
* peers.
|
|
54
|
+
*/
|
|
55
|
+
export function Topbar({ crumbs, go, title, subtitle, children, }) {
|
|
56
|
+
return (_jsxs("header", { className: "va-topbar", children: [_jsx(Mark, {}), _jsx("nav", { className: "va-crumbs", children: crumbs.map((crumb) => (_jsxs(Fragment, { children: [_jsx(Go, { to: crumb.at, go: go, className: "va-crumb", children: crumb.say }), _jsx("span", { className: "va-crumb-sep", children: "/" })] }, hrefOf(crumb.at)))) }), _jsxs("span", { className: "va-topbar-title", children: [_jsx("strong", { children: title }), subtitle === undefined ? null : _jsx("span", { className: "va-topbar-sub", children: subtitle })] }), children] }));
|
|
57
|
+
}
|
|
58
|
+
/** The one page with no data on it: an address this surface does not answer. */
|
|
59
|
+
export function Nowhere({ go }) {
|
|
60
|
+
return (_jsxs("div", { className: "va-app", children: [_jsx(Topbar, { crumbs: [], go: go, title: "No page at this address" }), _jsx("div", { className: "va-body va-scroll", children: _jsxs("div", { className: "va-page", children: [_jsx("p", { className: "va-note", children: "This service answers the build list, a build, a change, a subject, a run and the changelog. Whatever this link pointed at, it is not one of them \u2014 a link from a later version of the surface, or one that has been edited." }), _jsx("p", { children: _jsx(Go, { to: { page: 'builds' }, go: go, className: "va-mode", children: "Go to the builds" }) })] }) })] }));
|
|
61
|
+
}
|
|
62
|
+
export function messageOf(error) {
|
|
63
|
+
return error instanceof Error ? error.message : String(error);
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=shell.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shell.js","sourceRoot":"","sources":["../../src/ui/shell.tsx"],"names":[],"mappings":"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","sourcesContent":["/**\n * The chrome every page wears, and the three things every page needs.\n *\n * Apart from the pages because it is the part that must not differ between them:\n * a topbar that moves by four pixels between the docket and a subject reads as a\n * page that reloaded, and a failure rendered two ways is a failure a reader has\n * to learn twice.\n */\n\nimport { Fragment, type ReactElement, type ReactNode } from 'react';\nimport { Mark } from './mark.js';\nimport { hrefOf, type Route } from './route.js';\nimport { segments } from './text.js';\n\n/** Something fetched, in the three states it can be in. Never two. */\nexport type Loaded<T> =\n | { readonly state: 'loading' }\n | { readonly state: 'failed'; readonly why: string }\n | { readonly state: 'ready'; readonly value: T };\n\n/** An error as a sentence a reader can act on, never an empty list. */\nexport function Failure({\n why,\n retry,\n}: {\n readonly why: string;\n readonly retry: () => void;\n}): ReactElement {\n return (\n <p className=\"va-failure\">\n {why}{' '}\n <button type=\"button\" onClick={retry}>\n retry\n </button>\n </p>\n );\n}\n\n/**\n * Report prose, with its identifiers set as identifiers.\n *\n * The sentences on this page were written by the observer, not by this surface,\n * and they are shown as written — this is the record, and a review page that\n * paraphrases it is a review page a reviewer cannot check. The only thing done\n * to them is typographic.\n */\nexport function Prose({ text }: { readonly text: string }): ReactElement {\n return (\n <>\n {segments(text).map((part, index) =>\n part.code ? (\n <code key={`${String(index)}-${part.text}`}>{part.text}</code>\n ) : (\n <Fragment key={`${String(index)}-${part.text}`}>{part.text}</Fragment>\n ),\n )}\n </>\n );\n}\n\n/**\n * A link that is a link.\n *\n * `<a href>` rather than a button, because the address is real: a reviewer who\n * middle-clicks a change gets it in a tab, and one who copies the link gets a\n * link that opens on the same change. The click is intercepted so an ordinary\n * navigation stays a repaint — but only the ordinary one. A modified click, or\n * anything but the primary button, is the browser's.\n */\nexport function Go({\n to,\n go,\n className,\n title,\n children,\n}: {\n readonly to: Route;\n readonly go: (route: Route) => void;\n readonly className?: string | undefined;\n readonly title?: string | undefined;\n readonly children: ReactNode;\n}): ReactElement {\n return (\n <a\n href={hrefOf(to)}\n className={className}\n {...(title === undefined ? {} : { title })}\n onClick={(event) => {\n if (event.metaKey || event.ctrlKey || event.shiftKey || event.altKey || event.button !== 0) {\n return;\n }\n event.preventDefault();\n go(to);\n }}\n >\n {children}\n </a>\n );\n}\n\n/**\n * The bar at the top of every page: who you are looking at, and the way back.\n *\n * The crumbs are the whole navigation. There is no menu, because there are seven\n * pages and five of them are *about* the page above them — a build is about a\n * project, a change is about a build. A trail says that; a menu asserts they are\n * peers.\n */\nexport function Topbar({\n crumbs,\n go,\n title,\n subtitle,\n children,\n}: {\n readonly crumbs: readonly { readonly at: Route; readonly say: string }[];\n readonly go: (route: Route) => void;\n readonly title: string;\n readonly subtitle?: string | undefined;\n readonly children?: ReactNode;\n}): ReactElement {\n return (\n <header className=\"va-topbar\">\n <Mark />\n <nav className=\"va-crumbs\">\n {crumbs.map((crumb) => (\n <Fragment key={hrefOf(crumb.at)}>\n <Go to={crumb.at} go={go} className=\"va-crumb\">\n {crumb.say}\n </Go>\n <span className=\"va-crumb-sep\">/</span>\n </Fragment>\n ))}\n </nav>\n <span className=\"va-topbar-title\">\n <strong>{title}</strong>\n {subtitle === undefined ? null : <span className=\"va-topbar-sub\">{subtitle}</span>}\n </span>\n {children}\n </header>\n );\n}\n\n/** The one page with no data on it: an address this surface does not answer. */\nexport function Nowhere({ go }: { readonly go: (route: Route) => void }): ReactElement {\n return (\n <div className=\"va-app\">\n <Topbar crumbs={[]} go={go} title=\"No page at this address\" />\n <div className=\"va-body va-scroll\">\n <div className=\"va-page\">\n <p className=\"va-note\">\n This service answers the build list, a build, a change, a subject, a run and the\n changelog. Whatever this link pointed at, it is not one of them — a link from a later\n version of the surface, or one that has been edited.\n </p>\n <p>\n <Go to={{ page: 'builds' }} go={go} className=\"va-mode\">\n Go to the builds\n </Go>\n </p>\n </div>\n </div>\n </div>\n );\n}\n\nexport function messageOf(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n"]}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What became of each subject between two runs — the fold, with nothing drawn.
|
|
3
|
+
*
|
|
4
|
+
* A build page compares a candidate to a baseline and stops. So a difference that
|
|
5
|
+
* arrived on Tuesday, went undecided, and arrived again on Wednesday is drawn on
|
|
6
|
+
* Wednesday exactly as it was on Tuesday: eighteen thumbnails, no memory, and a
|
|
7
|
+
* reviewer re-reading a docket they already read. The second reading costs the
|
|
8
|
+
* same as the first, which is why the fourth one does not happen.
|
|
9
|
+
*
|
|
10
|
+
* The shape digest is what makes the answer exact rather than approximate. Two
|
|
11
|
+
* runs whose leading regions carry the same fingerprint are carrying *the same
|
|
12
|
+
* difference*, not a similar one, and that licenses the only sentence worth
|
|
13
|
+
* printing anywhere off this: **you have seen this, and it has not moved since.**
|
|
14
|
+
*
|
|
15
|
+
* ## Absent is not equal
|
|
16
|
+
*
|
|
17
|
+
* A run that recorded no shape for its leading region said nothing, and two runs
|
|
18
|
+
* that both said nothing did not agree. Those get their own state, because
|
|
19
|
+
* folding them into *the same difference again* would be a confident claim about
|
|
20
|
+
* a pair nothing measured — on the one screen where the claim tells somebody they
|
|
21
|
+
* can skip looking.
|
|
22
|
+
*
|
|
23
|
+
* Separate from the panels that read it because there are now three of them: the
|
|
24
|
+
* build reads the whole crossing, a change reads the rows under one component,
|
|
25
|
+
* and a subject reads its own single row. One fold, and none of them may reach a
|
|
26
|
+
* different answer than the others about the same pair of runs.
|
|
27
|
+
*/
|
|
28
|
+
import type { BuildDetail, SubjectView } from '../review-types.js';
|
|
29
|
+
/** What became of one subject between the two runs. */
|
|
30
|
+
export type Shift = 'differently' | 'unsaid' | 'first' | 'new' | 'again' | 'settled' | 'declared' | 'absorbed' | 'unmasked' | 'unplaced' | 'uncompared' | 'dropped' | 'held';
|
|
31
|
+
export interface Shifted {
|
|
32
|
+
readonly subject: string;
|
|
33
|
+
readonly shift: Shift;
|
|
34
|
+
/** This run's reading. Absent only when the subject was dropped. */
|
|
35
|
+
readonly now?: SubjectView;
|
|
36
|
+
/** The earlier run's reading, including whatever was decided about it. */
|
|
37
|
+
readonly earlier?: SubjectView;
|
|
38
|
+
}
|
|
39
|
+
export interface Divergence {
|
|
40
|
+
readonly shifts: readonly Shifted[];
|
|
41
|
+
/** Subjects both runs compared and neither found a difference in. */
|
|
42
|
+
readonly held: number;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* The two runs crossed, one row per subject either of them named.
|
|
46
|
+
*
|
|
47
|
+
* Union rather than intersection: a subject the earlier run had and this one does
|
|
48
|
+
* not is a suite that shrank, and an intersection would report that as nothing
|
|
49
|
+
* having happened.
|
|
50
|
+
*/
|
|
51
|
+
export declare function divergeFrom(now: BuildDetail, prior: BuildDetail): Divergence;
|
|
52
|
+
//# sourceMappingURL=shift.d.ts.map
|
package/dist/ui/shift.js
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What became of each subject between two runs — the fold, with nothing drawn.
|
|
3
|
+
*
|
|
4
|
+
* A build page compares a candidate to a baseline and stops. So a difference that
|
|
5
|
+
* arrived on Tuesday, went undecided, and arrived again on Wednesday is drawn on
|
|
6
|
+
* Wednesday exactly as it was on Tuesday: eighteen thumbnails, no memory, and a
|
|
7
|
+
* reviewer re-reading a docket they already read. The second reading costs the
|
|
8
|
+
* same as the first, which is why the fourth one does not happen.
|
|
9
|
+
*
|
|
10
|
+
* The shape digest is what makes the answer exact rather than approximate. Two
|
|
11
|
+
* runs whose leading regions carry the same fingerprint are carrying *the same
|
|
12
|
+
* difference*, not a similar one, and that licenses the only sentence worth
|
|
13
|
+
* printing anywhere off this: **you have seen this, and it has not moved since.**
|
|
14
|
+
*
|
|
15
|
+
* ## Absent is not equal
|
|
16
|
+
*
|
|
17
|
+
* A run that recorded no shape for its leading region said nothing, and two runs
|
|
18
|
+
* that both said nothing did not agree. Those get their own state, because
|
|
19
|
+
* folding them into *the same difference again* would be a confident claim about
|
|
20
|
+
* a pair nothing measured — on the one screen where the claim tells somebody they
|
|
21
|
+
* can skip looking.
|
|
22
|
+
*
|
|
23
|
+
* Separate from the panels that read it because there are now three of them: the
|
|
24
|
+
* build reads the whole crossing, a change reads the rows under one component,
|
|
25
|
+
* and a subject reads its own single row. One fold, and none of them may reach a
|
|
26
|
+
* different answer than the others about the same pair of runs.
|
|
27
|
+
*/
|
|
28
|
+
import { shapeOf } from './lead.js';
|
|
29
|
+
/**
|
|
30
|
+
* The two runs crossed, one row per subject either of them named.
|
|
31
|
+
*
|
|
32
|
+
* Union rather than intersection: a subject the earlier run had and this one does
|
|
33
|
+
* not is a suite that shrank, and an intersection would report that as nothing
|
|
34
|
+
* having happened.
|
|
35
|
+
*/
|
|
36
|
+
export function divergeFrom(now, prior) {
|
|
37
|
+
const before = new Map(prior.subjects.map((subject) => [subject.subject, subject]));
|
|
38
|
+
const shifts = [];
|
|
39
|
+
let held = 0;
|
|
40
|
+
for (const subject of now.subjects) {
|
|
41
|
+
const earlier = before.get(subject.subject);
|
|
42
|
+
before.delete(subject.subject);
|
|
43
|
+
const shift = shiftOf(subject, earlier);
|
|
44
|
+
if (shift === 'held')
|
|
45
|
+
held += 1;
|
|
46
|
+
else
|
|
47
|
+
shifts.push({ subject: subject.subject, shift, now: subject, ...(earlier === undefined ? {} : { earlier }) });
|
|
48
|
+
}
|
|
49
|
+
for (const earlier of before.values()) {
|
|
50
|
+
shifts.push({ subject: earlier.subject, shift: 'dropped', earlier });
|
|
51
|
+
}
|
|
52
|
+
return { shifts, held };
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Whether a verdict describes a pair this run actually put side by side.
|
|
56
|
+
*
|
|
57
|
+
* `ignored` belongs here and reads as though it does not. A declaration decided
|
|
58
|
+
* it, but the comparison it decided happened: there was a baseline, there were
|
|
59
|
+
* differing pixels, and every one of them landed somewhere the operator wrote
|
|
60
|
+
* down. Filing it with `incomparable` told a reviewer *no baseline was put beside
|
|
61
|
+
* them here* about subjects that were compared — an absence invented on top of a
|
|
62
|
+
* measurement, which is the one thing this panel exists not to do.
|
|
63
|
+
*/
|
|
64
|
+
function compared(subject) {
|
|
65
|
+
return (subject.verdict === 'changed' ||
|
|
66
|
+
subject.verdict === 'unchanged' ||
|
|
67
|
+
subject.verdict === 'ignored');
|
|
68
|
+
}
|
|
69
|
+
function shiftOf(now, earlier) {
|
|
70
|
+
if (earlier === undefined)
|
|
71
|
+
return 'new';
|
|
72
|
+
if (!compared(now))
|
|
73
|
+
return 'uncompared';
|
|
74
|
+
if (!compared(earlier))
|
|
75
|
+
return 'unplaced';
|
|
76
|
+
// A declaration standing over both runs is bookkeeping; one that arrived
|
|
77
|
+
// between them is a rule that has just taken a subject out of review, and the
|
|
78
|
+
// subject it took is named. That is the moment a mask starts covering
|
|
79
|
+
// something, and it is visible exactly once — here.
|
|
80
|
+
if (now.verdict === 'ignored')
|
|
81
|
+
return earlier.verdict === 'ignored' ? 'declared' : 'absorbed';
|
|
82
|
+
// The other direction. A subject a rule absorbed there and nothing absorbs
|
|
83
|
+
// here either stopped differing — which is `settled`, and true whether or not
|
|
84
|
+
// a rule was watching — or is being reported, which is the rule failing to
|
|
85
|
+
// cover what it was written for.
|
|
86
|
+
if (earlier.verdict === 'ignored')
|
|
87
|
+
return now.verdict === 'unchanged' ? 'settled' : 'unmasked';
|
|
88
|
+
if (now.verdict === 'unchanged')
|
|
89
|
+
return earlier.verdict === 'unchanged' ? 'held' : 'settled';
|
|
90
|
+
if (earlier.verdict === 'unchanged')
|
|
91
|
+
return 'first';
|
|
92
|
+
const here = shapeOf(now);
|
|
93
|
+
const there = shapeOf(earlier);
|
|
94
|
+
if (here === undefined || there === undefined)
|
|
95
|
+
return 'unsaid';
|
|
96
|
+
return here === there ? 'again' : 'differently';
|
|
97
|
+
}
|
|
98
|
+
//# sourceMappingURL=shift.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shift.js","sourceRoot":"","sources":["../../src/ui/shift.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * What became of each subject between two runs — the fold, with nothing drawn.\n *\n * A build page compares a candidate to a baseline and stops. So a difference that\n * arrived on Tuesday, went undecided, and arrived again on Wednesday is drawn on\n * Wednesday exactly as it was on Tuesday: eighteen thumbnails, no memory, and a\n * reviewer re-reading a docket they already read. The second reading costs the\n * same as the first, which is why the fourth one does not happen.\n *\n * The shape digest is what makes the answer exact rather than approximate. Two\n * runs whose leading regions carry the same fingerprint are carrying *the same\n * difference*, not a similar one, and that licenses the only sentence worth\n * printing anywhere off this: **you have seen this, and it has not moved since.**\n *\n * ## Absent is not equal\n *\n * A run that recorded no shape for its leading region said nothing, and two runs\n * that both said nothing did not agree. Those get their own state, because\n * folding them into *the same difference again* would be a confident claim about\n * a pair nothing measured — on the one screen where the claim tells somebody they\n * can skip looking.\n *\n * Separate from the panels that read it because there are now three of them: the\n * build reads the whole crossing, a change reads the rows under one component,\n * and a subject reads its own single row. One fold, and none of them may reach a\n * different answer than the others about the same pair of runs.\n */\n\nimport type { BuildDetail, SubjectView } from '../review-types.js';\nimport { shapeOf } from './lead.js';\n\n/** What became of one subject between the two runs. */\nexport type Shift =\n | 'differently'\n | 'unsaid'\n | 'first'\n | 'new'\n | 'again'\n | 'settled'\n | 'declared'\n | 'absorbed'\n | 'unmasked'\n | 'unplaced'\n | 'uncompared'\n | 'dropped'\n | 'held';\n\nexport interface Shifted {\n readonly subject: string;\n readonly shift: Shift;\n /** This run's reading. Absent only when the subject was dropped. */\n readonly now?: SubjectView;\n /** The earlier run's reading, including whatever was decided about it. */\n readonly earlier?: SubjectView;\n}\n\nexport interface Divergence {\n readonly shifts: readonly Shifted[];\n /** Subjects both runs compared and neither found a difference in. */\n readonly held: number;\n}\n\n/**\n * The two runs crossed, one row per subject either of them named.\n *\n * Union rather than intersection: a subject the earlier run had and this one does\n * not is a suite that shrank, and an intersection would report that as nothing\n * having happened.\n */\nexport function divergeFrom(now: BuildDetail, prior: BuildDetail): Divergence {\n const before = new Map(prior.subjects.map((subject) => [subject.subject, subject]));\n const shifts: Shifted[] = [];\n let held = 0;\n\n for (const subject of now.subjects) {\n const earlier = before.get(subject.subject);\n before.delete(subject.subject);\n const shift = shiftOf(subject, earlier);\n if (shift === 'held') held += 1;\n else shifts.push({ subject: subject.subject, shift, now: subject, ...(earlier === undefined ? {} : { earlier }) });\n }\n\n for (const earlier of before.values()) {\n shifts.push({ subject: earlier.subject, shift: 'dropped', earlier });\n }\n\n return { shifts, held };\n}\n\n/**\n * Whether a verdict describes a pair this run actually put side by side.\n *\n * `ignored` belongs here and reads as though it does not. A declaration decided\n * it, but the comparison it decided happened: there was a baseline, there were\n * differing pixels, and every one of them landed somewhere the operator wrote\n * down. Filing it with `incomparable` told a reviewer *no baseline was put beside\n * them here* about subjects that were compared — an absence invented on top of a\n * measurement, which is the one thing this panel exists not to do.\n */\nfunction compared(subject: SubjectView): boolean {\n return (\n subject.verdict === 'changed' ||\n subject.verdict === 'unchanged' ||\n subject.verdict === 'ignored'\n );\n}\n\nfunction shiftOf(now: SubjectView, earlier: SubjectView | undefined): Shift {\n if (earlier === undefined) return 'new';\n if (!compared(now)) return 'uncompared';\n if (!compared(earlier)) return 'unplaced';\n\n // A declaration standing over both runs is bookkeeping; one that arrived\n // between them is a rule that has just taken a subject out of review, and the\n // subject it took is named. That is the moment a mask starts covering\n // something, and it is visible exactly once — here.\n if (now.verdict === 'ignored') return earlier.verdict === 'ignored' ? 'declared' : 'absorbed';\n\n // The other direction. A subject a rule absorbed there and nothing absorbs\n // here either stopped differing — which is `settled`, and true whether or not\n // a rule was watching — or is being reported, which is the rule failing to\n // cover what it was written for.\n if (earlier.verdict === 'ignored') return now.verdict === 'unchanged' ? 'settled' : 'unmasked';\n\n if (now.verdict === 'unchanged') return earlier.verdict === 'unchanged' ? 'held' : 'settled';\n if (earlier.verdict === 'unchanged') return 'first';\n\n const here = shapeOf(now);\n const there = shapeOf(earlier);\n if (here === undefined || there === undefined) return 'unsaid';\n return here === there ? 'again' : 'differently';\n}\n"]}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The two panes a build is worked in: the docket rail, and the change beside it.
|
|
3
|
+
*
|
|
4
|
+
* Its own module for the same reason [`styles-stage.ts`](./styles-stage.ts) is:
|
|
5
|
+
* the sheet is one string handed to an operator, and the number of rules a person
|
|
6
|
+
* has to hold at once to change one of them is a real cost. What is decided here
|
|
7
|
+
* is *where a reviewer's eye goes first*, which is a layout question with a
|
|
8
|
+
* correct answer — the band headings and the row a change occupies, above the
|
|
9
|
+
* fold, in a column that does not grow with the build.
|
|
10
|
+
*
|
|
11
|
+
* The rule that matters most is the least visible one: nothing in this block
|
|
12
|
+
* scrolls the document. The rail scrolls through the changes, the stage scrolls
|
|
13
|
+
* through the one that is open, and the page itself is fixed to the viewport. A
|
|
14
|
+
* surface where the document scrolls is a report; a surface where the panes
|
|
15
|
+
* scroll is a place a reviewer navigates.
|
|
16
|
+
*/
|
|
17
|
+
export declare const DOCKET_STYLES = "\n/* The trail, which is the whole navigation. Five of the seven pages are *about*\n the page above them, and a trail says that where a menu would assert they are\n peers. */\n.va-crumbs { align-items: center; display: flex; gap: 0.3rem; min-width: 0; }\n.va-crumb { color: var(--va-ink-3); font-size: 0.8rem; text-decoration: none; white-space: nowrap; }\n.va-crumb:hover { color: var(--va-accent); }\n.va-crumb-sep { color: var(--va-line-firm); font-size: 0.8rem; }\n\n/* Changes or subjects: the two ways to work a build, as two addresses rather\n than as a piece of component state. */\n.va-switch { background: var(--va-sunken); border-radius: 9px; display: flex; flex: none; gap: 0.15rem; margin-bottom: 0.5rem; padding: 0.2rem; }\n.va-switch .va-mode { flex: 1; text-align: center; text-decoration: none; }\n.va-switch .va-mode.va-on { background: var(--va-accent-soft); color: var(--va-accent); font-weight: 650; }\n.va-switch .va-mode.va-off { color: var(--va-ink-3); cursor: not-allowed; }\n\n.va-rail-head { align-items: center; display: flex; flex: none; flex-wrap: wrap; gap: 0.4rem; padding: 0 0.15rem 0.5rem; }\n.va-rail-tally { color: var(--va-ink-3); font-size: 0.76rem; margin-right: auto; }\n.va-rail-empty { padding: 0.6rem 0.15rem; }\n/* The scrolling half. `min-height: 0` is what keeps it scrolling rather than\n pushing the rail past the bottom of the window \u2014 a flex child's default\n minimum is its content, and a build of forty changes would take the viewport\n with it. */\n.va-rail-list { flex: 1 1 auto; min-height: 0; overflow-y: auto; }\n\n/* The orders. Small, and deliberately not a select: four addresses a reader can\n middle-click, which a select cannot be. */\n.va-sorting { display: inline-flex; gap: 0.1rem; }\n.va-sort { border-radius: 6px; color: var(--va-ink-3); font-family: var(--va-mono); font-size: 0.66rem; letter-spacing: 0.06em; padding: 0.15rem 0.4rem; text-decoration: none; text-transform: uppercase; }\n.va-sort:hover { background: var(--va-sunken); color: var(--va-ink); }\n.va-sort.va-on { background: var(--va-accent-soft); color: var(--va-accent); }\n\n/* A band, and the sentence that says why its rows are together. The left edge\n carries the band's meaning: an alarm is red, a decision is green, and the\n ordinary work you asked for is the accent. */\n.va-band { border-left: 2px solid var(--va-line-firm); margin: 0 0 0.9rem 0.15rem; padding-left: 0.7rem; }\n.va-band h3 { align-items: center; display: flex; font-size: 0.76rem; font-weight: 700; gap: 0.4rem; letter-spacing: 0.05em; margin-bottom: 0.15rem; text-transform: uppercase; }\n.va-band h3 .va-num { background: var(--va-sunken); border-radius: 999px; color: var(--va-ink-2); font-size: 0.7rem; letter-spacing: 0; padding: 0 0.4rem; }\n.va-band > .va-note { font-size: 0.74rem; margin-bottom: 0.35rem; }\n.va-band-stranded { border-left-color: var(--va-bad); }\n.va-band-stranded h3 { color: var(--va-bad-ink); }\n.va-band-reached { border-left-color: var(--va-accent); }\n.va-band-contradicted { border-left-color: var(--va-warn); }\n.va-band-contradicted h3 { color: var(--va-warn-ink); }\n.va-band-unexplained { border-left-color: var(--va-warn); }\n.va-band-unexplained h3 { color: var(--va-warn-ink); }\n.va-band-upstream { border-left-color: var(--va-collateral); }\n.va-band-token { border-left-color: var(--va-collateral); }\n.va-band-unnamed { border-left-color: var(--va-line-firm); }\n.va-band-unread { border-left-color: var(--va-warn); }\n.va-band-decided { border-left-color: var(--va-good); }\n.va-band-orphan { border-left-color: var(--va-warn); }\n\n/* A heading that is a path or a property, not a claim, so it overrides the\n band's uppercase: a name a reviewer uppercases is a name they cannot grep. */\n.va-band h3.va-root, .va-root { flex-wrap: wrap; font-family: var(--va-mono); font-size: 0.78rem; font-weight: 600; gap: 0.15rem; letter-spacing: 0; text-transform: none; }\n.va-root .va-root-in { color: var(--va-ink-3); font-weight: 400; }\n.va-root .va-root-name { color: var(--va-ink); }\n.va-root-component .va-root-name { font-family: var(--va-sans); font-weight: 700; }\n\n/* The same cause on the change page: one line under the name, and the other\n changes it made. */\n.va-from { align-items: baseline; display: flex; flex-wrap: wrap; font-size: 0.8rem; gap: 0.3rem; margin-top: 0.2rem; }\n.va-from-mark { color: var(--va-ink-3); font-size: 0.72rem; letter-spacing: 0.05em; text-transform: uppercase; }\n.va-from .va-root { font-size: 0.8rem; }\n.va-from-go { color: var(--va-accent); text-decoration: none; }\n.va-from-go:hover { text-decoration: underline; }\n\n/* The prediction under a root, and the same claim about the whole build. It sits\n between a heading and the rows it explains, so it is quieter than both. */\n.va-foreseen { color: var(--va-ink-3); font-size: 0.72rem; line-height: 1.5; margin: 0.1rem 0 0.4rem; }\n.va-foreseen .va-num { color: var(--va-ink-2); font-family: var(--va-mono); }\n.va-foreseen .va-from-go { color: var(--va-ink-2); font-family: var(--va-mono); text-decoration: none; }\n.va-foreseen .va-from-go:hover { color: var(--va-accent); text-decoration: underline; }\n.va-foreseen-off { color: var(--va-warn-ink); }\n.va-rail-head + .va-foreseen { margin: -0.2rem 0 0.5rem; }\n\n/* One change, in a row a reviewer scans a column of. The name is the only thing\n at full contrast; everything else on the row is there to be skipped. */\n.va-row { margin-bottom: 0.1rem; }\n.va-row-link { align-items: baseline; border: 1px solid transparent; border-radius: 8px; column-gap: 0.5rem; display: grid; grid-template-columns: minmax(0, 1fr) auto; padding: 0.35rem 0.5rem; row-gap: 0.05rem; text-decoration: none; }\n.va-row-link:hover { background: var(--va-sunken); }\n.va-row.va-here .va-row-link { background: var(--va-accent-soft); border-color: var(--va-accent); }\n.va-row-name { font-size: 0.88rem; font-weight: 550; grid-area: 1 / 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n.va-row-spread { font-size: 0.74rem; grid-area: 2 / 1; }\n.va-row-size { color: var(--va-ink-3); font-size: 0.74rem; grid-area: 2 / 2; text-align: right; }\n.va-row .va-mark { font-size: 0.68rem; grid-area: 1 / 2; justify-self: end; }\n.va-row-from { color: var(--va-ink-3); font-family: var(--va-mono); font-size: 0.7rem; grid-area: 3 / 1 / 4 / 3; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n\n/* The page with nothing open: four sentences and two links, and no encyclopedia.\n Everything a reader could want past this has an address of its own. */\n.va-opening { max-width: 44rem; }\n.va-opening h1 { margin-bottom: 0.3rem; }\n.va-opening-links { align-items: baseline; display: flex; flex-wrap: wrap; gap: 0.6rem; margin-top: 1.4rem; }\n\n/* The change itself. The two buttons are in the head, level with the component\n name: every line below them is a reason to press one or to refuse, and a\n reviewer who has read them should not scroll back past the reasons to act. */\n.va-decide { max-width: 62rem; }\n.va-decide-head { align-items: flex-start; border-bottom: 1px solid var(--va-line); display: flex; flex-wrap: wrap; gap: 0.8rem; justify-content: space-between; padding-bottom: 0.8rem; }\n.va-decide-head h1 { margin: 0; }\n.va-decide-lead { font-size: 1rem; margin-top: 0.8rem; }\n.va-decide-act { align-items: center; display: flex; flex-wrap: wrap; gap: 0.55rem; }\n.va-decide-act button { font-weight: 600; }\n.va-decide-act .va-approve { background: var(--va-good); border-color: var(--va-good); color: #ffffff; }\n.va-decide-act .va-approve:disabled { background: var(--va-sunken); border-color: var(--va-line-firm); color: var(--va-ink-3); }\n.va-decide h2 { font-size: 0.78rem; letter-spacing: 0.08em; margin-top: 1.6rem; text-transform: uppercase; }\n\n/* Whether the commit arrives, in the three colours the three answers deserve.\n The alarm is spent on one of them and must stay that way: a page that shouted\n over every component out of a dependency trained the alarm out of its readers\n inside a build. */\n.va-reaches { border-radius: 7px; font-size: 0.9rem; margin-top: 0.9rem; padding: 0.5rem 0.7rem; }\n.va-reaches.va-reached { background: var(--va-info-bg); color: var(--va-info-ink); }\n.va-reaches.va-alarm { background: var(--va-bad-bg); color: var(--va-bad-ink); }\n.va-reaches.va-unnamed { background: var(--va-info-bg); color: var(--va-info-ink); }\n\n/* The run's own conclusion, above the file graph's. Left-marked rather than\n filled: it sits directly over the reach block, and two filled panels in a row\n read as one panel with a seam in it. The colour is the rung \u2014 the two that\n say *the run found nothing* borrow the alarm, and the three that explain\n something do not, because an explanation is not a finding. */\n.va-attributed { border-left: 3px solid var(--va-line-firm); font-size: 0.92rem; margin-top: 0.9rem; padding-left: 0.7rem; }\n.va-attributed code { font-size: 0.86em; }\n.va-attributed-edited { border-left-color: var(--va-accent); }\n.va-attributed-upstream { border-left-color: var(--va-collateral); }\n.va-attributed-token { border-left-color: var(--va-collateral); }\n.va-attributed-contradicted { border-left-color: var(--va-bad); }\n.va-attributed-unexplained { border-left-color: var(--va-bad); }\n.va-attributed-go { color: var(--va-accent); font-weight: 600; white-space: nowrap; }\n\n/* A band this change did not move on its own. Directly under the lead and\n marked in the collateral colour, which is what it is: the sense moved here,\n the decision is one file up, and the two belong on one screen without the\n second looking like a second change. */\n.va-handed { color: var(--va-ink-2); font-size: 0.95rem; margin-top: 0.35rem; }\n.va-handed em { color: var(--va-ink); font-style: normal; font-weight: 600; }\n.va-handed-go { color: var(--va-accent); font-weight: 600; white-space: nowrap; }\n\n/* The three counts, side by side rather than in a sentence. A sentence has to\n pick an order and an emphasis; these are three answers of equal standing to\n three questions a reviewer asks in the same breath. */\n.va-tally { align-items: baseline; color: var(--va-ink-2); display: flex; flex-wrap: wrap; font-size: 0.9rem; gap: 0.15rem 1.1rem; margin: 0.2rem 0 0.9rem; }\n.va-tally strong { color: var(--va-ink); font-size: 1rem; }\n.va-tally > .va-note { margin-left: auto; }\n\n/* What happened to the things that draw this one \u2014 the question an author of a\n leaf actually has. The list is ordered worst first, so the row that needs a\n look is the one under the summary. */\n.va-consumers { margin: 1.1rem 0; }\n.va-consumers-tally { color: var(--va-ink-2); font-size: 0.92rem; margin: 0 0 0.5rem; }\n.va-consumers-own { color: var(--va-bad-ink); font-weight: 600; }\n.va-consumers-list { display: grid; gap: 0.3rem; list-style: none; margin: 0; padding: 0; }\n.va-consumers-list li { align-items: baseline; border-left: 3px solid var(--va-collateral); display: flex; flex-wrap: wrap; gap: 0.5rem; padding: 0.15rem 0 0.15rem 0.6rem; }\n.va-consumer-name { color: var(--va-ink); font-weight: 600; }\n.va-consumer-moved { border-left-color: var(--va-bad); }\n.va-consumers-rest { color: var(--va-ink-3); font-size: 0.85rem; margin: 0.5rem 0 0; }\n.va-consumers-rest span[title] { color: var(--va-ink-2); }\n.va-alarm-text { color: var(--va-bad-ink); }\n\n/* What a press of Approve settles that is not the change it is under. Directly\n under the counts, because it is a correction to one of them: the seven renders\n to accept are seven whole pictures, and four of them hold somebody else's\n undecided difference. */\n.va-carries { background: var(--va-bad-bg); border-radius: 6px; color: var(--va-ink-2); font-size: 0.88rem; margin: 0 0 0.9rem; padding: 0.4rem 0.6rem; }\n.va-carries-go { color: var(--va-accent); font-weight: 600; white-space: nowrap; }\n.va-carries-mark { background: var(--va-bad-bg); border-radius: 999px; color: var(--va-bad-ink); font-size: 0.68rem; padding: 0.05rem 0.4rem; }\n\n/* What a band claim is made of. At the foot, because it is the same sentence for\n all of them and a reviewer needs it once \u2014 and not behind a disclosure,\n because the complaint that produced it was a page hiding its sources. */\n.va-held { border-left: 2px solid var(--va-good); color: var(--va-ink-2); font-size: 0.86rem; margin: 0.6rem 0 0; padding-left: 0.7rem; }\n.va-held-mark { color: var(--va-good); font-weight: 600; }\n.va-held-none { border-left-color: var(--va-ink-3); }\n.va-held-none .va-held-mark { color: var(--va-ink-2); }\n.va-held-go { color: inherit; text-decoration: underline; text-decoration-style: dotted; }\n.va-evidence { border-top: 1px solid var(--va-line); color: var(--va-ink-3); font-size: 0.8rem; margin-top: 1.6rem; padding-top: 0.6rem; }\n.va-evidence em { color: var(--va-ink-2); font-style: normal; }\n\n/* What the last run said, beside the change rather than nine thousand pixels\n below it. Warm rather than red: *you have seen this* is not an alarm, it is\n the sentence a reviewer skips work on. */\n.va-since { border-radius: 7px; font-size: 0.9rem; margin-top: 0.7rem; padding: 0.5rem 0.7rem; }\n.va-since.va-known { background: var(--va-warn-bg); color: var(--va-warn-ink); }\n.va-collateral-note { margin-top: 1.6rem; }\n\n/* Where the renders stopped agreeing. Directly above the list they divide, so a\n reviewer reads the groups and then finds them in order \u2014 and the sizes are the\n left column in both, which is what makes the two readable as one list. */\n.va-parted { margin: 1.4rem 0 0.6rem; }\n.va-parted h2 { margin-bottom: 0.4rem; }\n.va-parted ul { display: grid; gap: 0.5rem; list-style: none; margin: 0; padding: 0; }\n.va-parted li { border-left: 3px solid var(--va-line); padding: 0.1rem 0 0.1rem 0.7rem; }\n.va-parted li:first-child { border-left-color: var(--va-collateral); }\n.va-parted-head { align-items: baseline; display: flex; flex-wrap: wrap; gap: 0.6rem; margin: 0; }\n.va-parted-head .va-num { color: var(--va-ink); font-family: var(--va-mono); font-size: 0.95rem; }\n.va-parted-only { color: var(--va-ink-2); font-size: 0.85rem; font-weight: 600; }\n.va-parted-take { background: var(--va-sunken); border-radius: 5px; color: var(--va-ink-3); font-size: 0.74rem; margin-left: auto; padding: 0.1rem 0.4rem; }\n.va-parted-in { font-size: 0.8rem; margin: 0.15rem 0 0; overflow-wrap: anywhere; }\n\n/* Where it showed up: one row per render, each decidable on its own, because a\n batch that could only be taken whole would be a batch nobody could refuse\n part of. */\n.va-where { display: grid; gap: 0.15rem; margin-top: 0.5rem; }\n.va-where-row { align-items: baseline; border-radius: 8px; column-gap: 0.6rem; display: grid; grid-template-columns: minmax(6rem, 15rem) auto auto minmax(0, 1fr) auto; padding: 0.35rem 0.5rem; }\n.va-where-row:hover { background: var(--va-sunken); }\n.va-where-subject { font-size: 0.88rem; font-weight: 550; grid-column: 1; overflow: hidden; text-overflow: ellipsis; text-decoration: none; white-space: nowrap; }\n.va-where-size { grid-column: 2; text-align: right; }\n.va-where-row .va-mark { grid-column: 3; }\n.va-where-with { grid-column: 4; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n.va-where-row .va-note { font-size: 0.76rem; }\n.va-where-act { display: flex; gap: 0.25rem; grid-column: 5; justify-self: end; }\n.va-where-row > .va-mark:last-child { grid-column: 5; justify-self: end; }\n.va-where-act button { border-radius: 6px; font-size: 0.8rem; line-height: 1; padding: 0.2rem 0.5rem; }\n.va-where-act .va-approve:not(:disabled) { border-color: var(--va-good); color: var(--va-good-ink); }\n.va-mark.va-known { background: var(--va-warn-bg); border-radius: 999px; color: var(--va-warn-ink); font-size: 0.68rem; padding: 0.05rem 0.4rem; }\n.va-mark.va-alarm { background: var(--va-bad-bg); border-radius: 999px; color: var(--va-bad-ink); font-size: 0.68rem; padding: 0.05rem 0.4rem; }\n\n/* What moved, in the sense the hashes recorded. The lead sentence is emphasis\n inside the existing lead paragraph and gets no box of its own \u2014 it is the\n subject of the page, not a note beside it. What follows is detail, and the\n only part that raises its voice is a component the picture lost: the hashes\n name it and no region does, which is the one state a reviewer cannot see. */\n.va-decide-lead em { font-style: normal; font-weight: 600; }\n.va-moved-lost { background: var(--va-bad-bg); border-radius: 7px; color: var(--va-bad-ink); font-size: 0.9rem; margin-top: 0.7rem; padding: 0.5rem 0.7rem; }\n.va-moved h3 { font-size: 0.72rem; letter-spacing: 0.06em; margin-top: 1rem; text-transform: uppercase; }\n.va-moved-with { display: grid; gap: 0.15rem; margin-top: 0.35rem; }\n.va-moved-with li { align-items: baseline; column-gap: 0.5rem; display: flex; flex-wrap: wrap; font-size: 0.88rem; }\n.va-moved-list { display: grid; gap: 0.3rem; }\n.va-moved-row { align-items: baseline; column-gap: 0.5rem; display: flex; flex-wrap: wrap; font-size: 0.85rem; }\n/* Pushed rather than edited, and dimmed to say so. The list is ordered by name\n and not by consequence, so without this a passenger reads level with a cause. */\n.va-moved-passenger .va-moved-name { color: var(--va-ink-3); font-weight: 400; }\n.va-moved-name { font-weight: 600; }\n/* How far a passenger is from the change, in the import graph. A chip and not a\n sentence, and only on the rows the records place: a column that says the same\n thing on every row is read once and skipped after. */\n.va-moved-far { background: var(--va-sunken); border-radius: 5px; color: var(--va-ink-3); font-size: 0.74rem; padding: 0.05rem 0.35rem; }\n\n/* The commit, its files, and how far out it landed \u2014 the build page's own\n section, above the docket rather than beside it, because it is true of every\n change on the page and nothing on it is decided. */\n.va-impact { border-top: 1px solid var(--va-line); display: grid; gap: 1rem; margin-top: 1.4rem; padding-top: 1.1rem; }\n.va-impact-commit { align-items: baseline; display: flex; flex-wrap: wrap; font-size: 0.9rem; }\n.va-impact-files ul { display: grid; gap: 0.1rem; margin-top: 0.35rem; }\n.va-impact-files li { font-size: 0.82rem; }\n.va-impact-depth h2 { font-size: 0.72rem; letter-spacing: 0.06em; text-transform: uppercase; }\n.va-rungs { display: grid; gap: 0.3rem; margin-top: 0.5rem; max-width: 34rem; }\n.va-rung { align-items: center; display: grid; gap: 0.6rem; grid-template-columns: 7.5rem 1fr auto; }\n.va-rung-at { font-size: 0.8rem; text-align: right; }\n/* The bar is the reached count and the fill is the moved count inside it, so the\n two are read as a share rather than as two lengths a reader has to divide. */\n.va-rung-bar { background: var(--va-collateral); border-radius: 3px; display: block; height: 0.6rem; min-width: 2px; }\n.va-rung-moved { background: var(--va-cause); border-radius: 3px; display: block; height: 100%; }\n.va-rung-num { font-size: 0.78rem; }\n/* The movement the bars cannot hold, which is the one part of the spread they\n are silent about. A list and not a sentence, because each row carries its own\n reason and a paragraph of them is a paragraph nobody finishes. */\n.va-unheld { background: var(--va-bad-bg); border-radius: 7px; color: var(--va-bad-ink); font-size: 0.85rem; margin-top: 0.7rem; max-width: 34rem; padding: 0.5rem 0.7rem; }\n.va-unheld ul { display: grid; gap: 0.25rem; margin-top: 0.4rem; }\n.va-unheld li { align-items: baseline; column-gap: 0.45rem; display: flex; flex-wrap: wrap; }\n.va-unheld-name { font-weight: 600; }\n.va-unheld-why { opacity: 0.78; }\n";
|
|
18
|
+
//# sourceMappingURL=styles-docket.d.ts.map
|