@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
package/dist/ui/run.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { DeclarationsPanel } from './declarations.js';
|
|
3
|
+
import { DivergenceOf } from './divergence.js';
|
|
4
|
+
import { ReachPanel } from './reach.js';
|
|
5
|
+
import { Settled } from './settled.js';
|
|
6
|
+
import { Prose } from './shell.js';
|
|
7
|
+
import { CoverageLine } from './builds.js';
|
|
8
|
+
import { Variations } from './variations.js';
|
|
9
|
+
export function RunPage({ client, build, crossing, }) {
|
|
10
|
+
return (_jsx("div", { className: "va-stage va-scroll", children: _jsxs("div", { className: "va-page", children: [_jsx("h1", { children: "What this run read" }), _jsx("p", { className: "va-subtitle", children: "The whole reading, in the order it was made: what the commit reaches, what has changed since the last run, what was compared without a baseline, and what nothing looked at. The decisions are on the changes." }), build.intent === undefined ? null : (_jsxs("p", { className: "va-intent", children: ["Declared intent: ", _jsx("em", { children: build.intent })] })), _jsx(CoverageLine, { build: build }), _jsx("section", { className: "va-card", children: _jsx(ReachPanel, { client: client, build: build }) }), _jsx("section", { className: "va-card", children: _jsx(DivergenceOf, { crossing: crossing }) }), build.variations.length === 0 ? null : (_jsx("section", { className: "va-card", children: _jsx(Variations, { variations: build.variations }) })), _jsxs("section", { className: "va-card", children: [_jsx("h2", { children: "Settled" }), _jsx(Settled, { subjects: build.subjects, ignores: build.declarations.ignores })] }), _jsxs("section", { className: "va-card", children: [_jsx("h2", { children: "Declarations" }), _jsx(DeclarationsPanel, { declarations: build.declarations })] }), build.notObserved.length === 0 ? null : (_jsxs("section", { className: "va-card", children: [_jsx("h2", { children: "Not observed" }), _jsx("ul", { className: "va-not-observed", children: build.notObserved.map((entry) => (_jsxs("li", { className: entry.kind === 'failed' ? 'va-failed' : '', children: [_jsx("strong", { children: entry.subject }), " \u2014 ", _jsx(Prose, { text: entry.because })] }, entry.subject))) })] }))] }) }));
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=run.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"run.js","sourceRoot":"","sources":["../../src/ui/run.tsx"],"names":[],"mappings":";AAqBA,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AACtD,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC/C,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AACxC,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAE7C,MAAM,UAAU,OAAO,CAAC,EACtB,MAAM,EACN,KAAK,EACL,QAAQ,GAKT;IACC,OAAO,CACL,cAAK,SAAS,EAAC,oBAAoB,YACjC,eAAK,SAAS,EAAC,SAAS,aACtB,8CAA2B,EAC3B,YAAG,SAAS,EAAC,aAAa,+NAItB,EAEH,KAAK,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CACnC,aAAG,SAAS,EAAC,WAAW,kCACL,uBAAK,KAAK,CAAC,MAAM,GAAM,IACtC,CACL,EACD,KAAC,YAAY,IAAC,KAAK,EAAE,KAAK,GAAI,EAE9B,kBAAS,SAAS,EAAC,SAAS,YAC1B,KAAC,UAAU,IAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,GAAI,GACpC,EAEV,kBAAS,SAAS,EAAC,SAAS,YAC1B,KAAC,YAAY,IAAC,QAAQ,EAAE,QAAQ,GAAI,GAC5B,EAET,KAAK,CAAC,UAAU,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CACtC,kBAAS,SAAS,EAAC,SAAS,YAC1B,KAAC,UAAU,IAAC,UAAU,EAAE,KAAK,CAAC,UAAU,GAAI,GACpC,CACX,EAED,mBAAS,SAAS,EAAC,SAAS,aAC1B,mCAAgB,EAChB,KAAC,OAAO,IAAC,QAAQ,EAAE,KAAK,CAAC,QAAQ,EAAE,OAAO,EAAE,KAAK,CAAC,YAAY,CAAC,OAAO,GAAI,IAClE,EAEV,mBAAS,SAAS,EAAC,SAAS,aAC1B,wCAAqB,EACrB,KAAC,iBAAiB,IAAC,YAAY,EAAE,KAAK,CAAC,YAAY,GAAI,IAC/C,EAET,KAAK,CAAC,WAAW,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CACvC,mBAAS,SAAS,EAAC,SAAS,aAC1B,wCAAqB,EACrB,aAAI,SAAS,EAAC,iBAAiB,YAC5B,KAAK,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAChC,cAAwB,SAAS,EAAE,KAAK,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,aAC3E,2BAAS,KAAK,CAAC,OAAO,GAAU,cAAG,KAAC,KAAK,IAAC,IAAI,EAAE,KAAK,CAAC,OAAO,GAAI,KAD1D,KAAK,CAAC,OAAO,CAEjB,CACN,CAAC,GACC,IACG,CACX,IACG,GACF,CACP,CAAC;AACJ,CAAC","sourcesContent":["/**\n * The run, in full — everything the report says that is not one change.\n *\n * This is the material that used to sit under the docket on the build page, and\n * moving it is the whole of the layout argument. Reach, the crossing against the\n * last run, the variation lattice, what a declaration settled, what was declared,\n * what was never observed: every one of those is worth reading, and none of them\n * is worth reading *while deciding whether to approve a restyle of `Button`*. On\n * one page they were four thousand pixels of scroll between a reviewer and the\n * second change in the build.\n *\n * So they have an address. A reviewer working a docket never opens it; a reviewer\n * asking *what did this run actually do* opens nothing else. The crossing is\n * handed down rather than fetched again — the build page has already asked, and\n * the two readings of one comparison must not be able to disagree.\n */\n\nimport type { ReactElement } from 'react';\nimport type { BuildDetail } from '../review-types.js';\nimport type { ReviewClient } from './client.js';\nimport type { Crossing } from './crossing.js';\nimport { DeclarationsPanel } from './declarations.js';\nimport { DivergenceOf } from './divergence.js';\nimport { ReachPanel } from './reach.js';\nimport { Settled } from './settled.js';\nimport { Prose } from './shell.js';\nimport { CoverageLine } from './builds.js';\nimport { Variations } from './variations.js';\n\nexport function RunPage({\n client,\n build,\n crossing,\n}: {\n readonly client: ReviewClient;\n readonly build: BuildDetail;\n readonly crossing: Crossing;\n}): ReactElement {\n return (\n <div className=\"va-stage va-scroll\">\n <div className=\"va-page\">\n <h1>What this run read</h1>\n <p className=\"va-subtitle\">\n The whole reading, in the order it was made: what the commit reaches, what has changed\n since the last run, what was compared without a baseline, and what nothing looked at. The\n decisions are on the changes.\n </p>\n\n {build.intent === undefined ? null : (\n <p className=\"va-intent\">\n Declared intent: <em>{build.intent}</em>\n </p>\n )}\n <CoverageLine build={build} />\n\n <section className=\"va-card\">\n <ReachPanel client={client} build={build} />\n </section>\n\n <section className=\"va-card\">\n <DivergenceOf crossing={crossing} />\n </section>\n\n {build.variations.length === 0 ? null : (\n <section className=\"va-card\">\n <Variations variations={build.variations} />\n </section>\n )}\n\n <section className=\"va-card\">\n <h2>Settled</h2>\n <Settled subjects={build.subjects} ignores={build.declarations.ignores} />\n </section>\n\n <section className=\"va-card\">\n <h2>Declarations</h2>\n <DeclarationsPanel declarations={build.declarations} />\n </section>\n\n {build.notObserved.length === 0 ? null : (\n <section className=\"va-card\">\n <h2>Not observed</h2>\n <ul className=\"va-not-observed\">\n {build.notObserved.map((entry) => (\n <li key={entry.subject} className={entry.kind === 'failed' ? 'va-failed' : ''}>\n <strong>{entry.subject}</strong> — <Prose text={entry.because} />\n </li>\n ))}\n </ul>\n </section>\n )}\n </div>\n </div>\n );\n}\n"]}
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What moved, in the sense the digests recorded rather than the area it covered.
|
|
3
|
+
*
|
|
4
|
+
* The page this exists for used to read *2 distinct differences, in 7 renders.
|
|
5
|
+
* 30,373 px in total* — three numbers, none of which is the change. A reviewer
|
|
6
|
+
* cannot approve what they have not been told, and every one of those figures
|
|
7
|
+
* describes the measurement instead of the edit.
|
|
8
|
+
*
|
|
9
|
+
* The run always knew better. A baseline carries a component's hashes split five
|
|
10
|
+
* ways, so a comparison of two sidecars can say *this component's declared style
|
|
11
|
+
* moved and its layout moved and its text did not* — without decoding a pixel,
|
|
12
|
+
* and without depending on the difference having landed somewhere a box could be
|
|
13
|
+
* drawn around. The report carries that list per subject; this turns it into the
|
|
14
|
+
* two things a surface needs from it.
|
|
15
|
+
*
|
|
16
|
+
* ## The two things
|
|
17
|
+
*
|
|
18
|
+
* **What each component moved in**, which is the sentence the page was missing.
|
|
19
|
+
*
|
|
20
|
+
* **What the picture lost**, which is the finding nobody was being shown. A
|
|
21
|
+
* region is named by resolving the box the pixels drew back to a component, so
|
|
22
|
+
* it fails precisely when a change matters most: an edit that reflows its
|
|
23
|
+
* neighbours merges into one blob, the blob fits no component, and the name that
|
|
24
|
+
* comes back is the document root. Held against the hashes, that subject stops
|
|
25
|
+
* being a mystery — `Button` moved in its layout and its declared style, and no
|
|
26
|
+
* region carries the name. The gap is reportable, and until it is reported the
|
|
27
|
+
* page is quietly confident about a component it never mentioned.
|
|
28
|
+
*
|
|
29
|
+
* The reverse pile is kept too, and it is not the same finding. A component a
|
|
30
|
+
* region names while its own hashes held still was *pushed* — it is collateral,
|
|
31
|
+
* and a page that called it a change would be doing the ranking this project
|
|
32
|
+
* exists to correct.
|
|
33
|
+
*/
|
|
34
|
+
import type { SubjectView } from '../review-types.js';
|
|
35
|
+
import type { Appearance } from './grouping.js';
|
|
36
|
+
/** One component the hashes say moved, held against what the picture found. */
|
|
37
|
+
export interface Sensed {
|
|
38
|
+
readonly component: string;
|
|
39
|
+
readonly bands: readonly string[];
|
|
40
|
+
/** Its own content moved, rather than only its rect being pushed. */
|
|
41
|
+
readonly cause: boolean;
|
|
42
|
+
/** Present on one side only, which `bands` alone cannot say. */
|
|
43
|
+
readonly presence?: 'added' | 'removed';
|
|
44
|
+
/** A region in this subject resolved to this name. */
|
|
45
|
+
readonly drawn: boolean;
|
|
46
|
+
}
|
|
47
|
+
/** A subject read at the semantic tier, and the two tiers held against each other. */
|
|
48
|
+
export interface Sense {
|
|
49
|
+
/**
|
|
50
|
+
* `false` when the baseline carried no hashes, so none of this was measured.
|
|
51
|
+
*
|
|
52
|
+
* The whole record collapses to *unknown* rather than to *nothing moved*, and
|
|
53
|
+
* a surface must print the difference. An empty `moved` on a measured subject
|
|
54
|
+
* with pixels in it is a real finding; the same emptiness unmeasured is a
|
|
55
|
+
* baseline written before the hashes existed.
|
|
56
|
+
*/
|
|
57
|
+
readonly measured: boolean;
|
|
58
|
+
readonly moved: readonly Sensed[];
|
|
59
|
+
/** Caused the difference, and no region names it: what the picture lost. */
|
|
60
|
+
readonly missed: readonly Sensed[];
|
|
61
|
+
/** Named by a region while its own hashes held still — pushed, not edited. */
|
|
62
|
+
readonly pushed: readonly string[];
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* The band, in the words a reviewer thinks in.
|
|
66
|
+
*
|
|
67
|
+
* Not the band name. `token` is a word this project uses precisely and a reader
|
|
68
|
+
* arriving from a pull request does not, and a page that leads with it has spent
|
|
69
|
+
* its most important line on vocabulary. The mapping is deliberately flat — one
|
|
70
|
+
* phrase each, no qualifiers — because these are joined into a clause and a
|
|
71
|
+
* hedge repeated four times down a list is a hedge nobody reads.
|
|
72
|
+
*
|
|
73
|
+
* An unknown band is returned unchanged. A newer report carrying a band this
|
|
74
|
+
* deployment has never heard of should print the word it was given, not vanish.
|
|
75
|
+
*/
|
|
76
|
+
export declare function senseOf(band: string): string;
|
|
77
|
+
/** `its layout and its style values` — the bands as one clause, loudest first. */
|
|
78
|
+
export declare function senses(bands: readonly string[]): string;
|
|
79
|
+
/**
|
|
80
|
+
* One subject, with the hashes and the regions read against each other.
|
|
81
|
+
*
|
|
82
|
+
* The join is on the component name and nothing else, which is the only key the
|
|
83
|
+
* two tiers share: a region carries the name its box resolved to, and a hash
|
|
84
|
+
* entry carries the name the boundary walk assigned. Both come from the same
|
|
85
|
+
* walk in the same run, so a name that appears in one and not the other is a
|
|
86
|
+
* real disagreement rather than a spelling.
|
|
87
|
+
*/
|
|
88
|
+
export declare function senseOfSubject(subject: SubjectView): Sense;
|
|
89
|
+
/** One component's movement across the renders a change showed up in. */
|
|
90
|
+
export interface Across {
|
|
91
|
+
/**
|
|
92
|
+
* Renders whose baseline carried hashes to compare — never their band count.
|
|
93
|
+
*
|
|
94
|
+
* Zero is the state every other field here is unreadable in: `bands: []` then
|
|
95
|
+
* means *nothing was measured*, and printed as *nothing moved* it is a claim
|
|
96
|
+
* made on the authority of something that never looked.
|
|
97
|
+
*/
|
|
98
|
+
readonly measured: number;
|
|
99
|
+
/** Every band this component moved in, anywhere, loudest first. */
|
|
100
|
+
readonly bands: readonly string[];
|
|
101
|
+
/** Renders it moved in where no region carried its name. */
|
|
102
|
+
readonly missedIn: readonly string[];
|
|
103
|
+
/** Renders whose baseline had no hashes, so nothing was measured there. */
|
|
104
|
+
readonly unmeasured: readonly string[];
|
|
105
|
+
/**
|
|
106
|
+
* What else moved in the same renders, and in what sense.
|
|
107
|
+
*
|
|
108
|
+
* The answer to the complaint this page kept earning: a card whose price string
|
|
109
|
+
* changed reports the button, because the button is what the largest box
|
|
110
|
+
* resolved to. Naming the other movers with their own bands is what lets a
|
|
111
|
+
* reviewer see that the text moved in the parent and the button moved in its
|
|
112
|
+
* style — two edits, correctly separated, on one screen.
|
|
113
|
+
*/
|
|
114
|
+
readonly alongside: readonly {
|
|
115
|
+
readonly component: string;
|
|
116
|
+
readonly bands: readonly string[];
|
|
117
|
+
}[];
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* The same reading, folded over every render one change appeared in.
|
|
121
|
+
*
|
|
122
|
+
* Union rather than intersection, and the difference is not cosmetic. A restyled
|
|
123
|
+
* button in a card that also grew moves in `token` everywhere and in `geometry`
|
|
124
|
+
* only where the row reflowed; an intersection would drop the second and report
|
|
125
|
+
* a size change as a repaint.
|
|
126
|
+
*/
|
|
127
|
+
export declare function senseAcross(component: string, appearances: readonly Appearance[]): Across;
|
|
128
|
+
/**
|
|
129
|
+
* Loudest first, and held here rather than imported.
|
|
130
|
+
*
|
|
131
|
+
* `BANDS` is `core`'s, and this package reads reports rather than core's compare
|
|
132
|
+
* tier — a surface that pulled the ordering across would tie the words on a page
|
|
133
|
+
* to a library it does not otherwise need.
|
|
134
|
+
*/
|
|
135
|
+
export declare const ORDER: readonly string[];
|
|
136
|
+
/**
|
|
137
|
+
* The known bands in their own order, then anything this build has not heard of.
|
|
138
|
+
*
|
|
139
|
+
* A newer report may carry a band added after this deployment shipped, and a
|
|
140
|
+
* filter over {@link ORDER} would drop it — the surface would report four bands
|
|
141
|
+
* where the run recorded five, and nothing on the page would say so. Unknown
|
|
142
|
+
* words go last because the order is a loudness claim this build cannot make
|
|
143
|
+
* about them, and alphabetically because that is an order rather than an
|
|
144
|
+
* accident of iteration.
|
|
145
|
+
*/
|
|
146
|
+
export declare function loudestFirst(bands: Iterable<string>): readonly string[];
|
|
147
|
+
/** A render this component moved in that some other change owns. */
|
|
148
|
+
export interface Elsewhere {
|
|
149
|
+
readonly subject: string;
|
|
150
|
+
/** The change the docket filed this render under, when a region named one. */
|
|
151
|
+
readonly filedUnder?: string;
|
|
152
|
+
/** A region here carries this component, so its picture is a picture of it. */
|
|
153
|
+
readonly drawn: boolean;
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* The renders a change moved in and does not appear on.
|
|
157
|
+
*
|
|
158
|
+
* A subject is filed under the component its **leading region** resolved to, and
|
|
159
|
+
* a render has exactly one of those — so a commit that edits a button and a
|
|
160
|
+
* price string files every card under whichever of the two happened to own the
|
|
161
|
+
* larger differing box. The button's page then lists seven renders of the twelve
|
|
162
|
+
* it moved in, and says nothing at all about the other five.
|
|
163
|
+
*
|
|
164
|
+
* That is the reviewer's problem and not a presentation detail: pressing approve
|
|
165
|
+
* on this page decides the renders on this page. The five it never mentioned
|
|
166
|
+
* stay open, under a name the person looking for this change would not think to
|
|
167
|
+
* open. Naming them is the difference between a docket and a docket that can be
|
|
168
|
+
* finished.
|
|
169
|
+
*
|
|
170
|
+
* `here` is the set already listed, passed in rather than derived, because the
|
|
171
|
+
* caller's list is the definition of *already shown* and re-deriving it here
|
|
172
|
+
* would be a second opinion about the same question.
|
|
173
|
+
*/
|
|
174
|
+
export declare function movedElsewhere(component: string, subjects: readonly SubjectView[], here: ReadonlySet<string>): readonly Elsewhere[];
|
|
175
|
+
/**
|
|
176
|
+
* What each band is read from, for the reviewer who asks how the page knows.
|
|
177
|
+
*
|
|
178
|
+
* *What it announces moved* is a claim, and until this line existed the page made
|
|
179
|
+
* it with nothing behind it. It is a digest comparison: the run hashes the role,
|
|
180
|
+
* name and state of every node inside a component boundary, the baseline sidecar
|
|
181
|
+
* keeps that hash, and a later run's hash disagrees. Which is a real answer, and
|
|
182
|
+
* also a short one — the values are not stored, so the band says *these differ*
|
|
183
|
+
* and cannot say what the name became. Printing the first half without the second
|
|
184
|
+
* lets a reviewer read a precision into it that is not there.
|
|
185
|
+
*/
|
|
186
|
+
export declare const READS: Readonly<Record<string, string>>;
|
|
187
|
+
//# sourceMappingURL=sense.d.ts.map
|
package/dist/ui/sense.js
ADDED
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What moved, in the sense the digests recorded rather than the area it covered.
|
|
3
|
+
*
|
|
4
|
+
* The page this exists for used to read *2 distinct differences, in 7 renders.
|
|
5
|
+
* 30,373 px in total* — three numbers, none of which is the change. A reviewer
|
|
6
|
+
* cannot approve what they have not been told, and every one of those figures
|
|
7
|
+
* describes the measurement instead of the edit.
|
|
8
|
+
*
|
|
9
|
+
* The run always knew better. A baseline carries a component's hashes split five
|
|
10
|
+
* ways, so a comparison of two sidecars can say *this component's declared style
|
|
11
|
+
* moved and its layout moved and its text did not* — without decoding a pixel,
|
|
12
|
+
* and without depending on the difference having landed somewhere a box could be
|
|
13
|
+
* drawn around. The report carries that list per subject; this turns it into the
|
|
14
|
+
* two things a surface needs from it.
|
|
15
|
+
*
|
|
16
|
+
* ## The two things
|
|
17
|
+
*
|
|
18
|
+
* **What each component moved in**, which is the sentence the page was missing.
|
|
19
|
+
*
|
|
20
|
+
* **What the picture lost**, which is the finding nobody was being shown. A
|
|
21
|
+
* region is named by resolving the box the pixels drew back to a component, so
|
|
22
|
+
* it fails precisely when a change matters most: an edit that reflows its
|
|
23
|
+
* neighbours merges into one blob, the blob fits no component, and the name that
|
|
24
|
+
* comes back is the document root. Held against the hashes, that subject stops
|
|
25
|
+
* being a mystery — `Button` moved in its layout and its declared style, and no
|
|
26
|
+
* region carries the name. The gap is reportable, and until it is reported the
|
|
27
|
+
* page is quietly confident about a component it never mentioned.
|
|
28
|
+
*
|
|
29
|
+
* The reverse pile is kept too, and it is not the same finding. A component a
|
|
30
|
+
* region names while its own hashes held still was *pushed* — it is collateral,
|
|
31
|
+
* and a page that called it a change would be doing the ranking this project
|
|
32
|
+
* exists to correct.
|
|
33
|
+
*/
|
|
34
|
+
import { causeOf } from './lead.js';
|
|
35
|
+
/**
|
|
36
|
+
* The band, in the words a reviewer thinks in.
|
|
37
|
+
*
|
|
38
|
+
* Not the band name. `token` is a word this project uses precisely and a reader
|
|
39
|
+
* arriving from a pull request does not, and a page that leads with it has spent
|
|
40
|
+
* its most important line on vocabulary. The mapping is deliberately flat — one
|
|
41
|
+
* phrase each, no qualifiers — because these are joined into a clause and a
|
|
42
|
+
* hedge repeated four times down a list is a hedge nobody reads.
|
|
43
|
+
*
|
|
44
|
+
* An unknown band is returned unchanged. A newer report carrying a band this
|
|
45
|
+
* deployment has never heard of should print the word it was given, not vanish.
|
|
46
|
+
*/
|
|
47
|
+
export function senseOf(band) {
|
|
48
|
+
return SENSES[band] ?? band;
|
|
49
|
+
}
|
|
50
|
+
const SENSES = {
|
|
51
|
+
// Role, accessible name, ARIA state: what a screen reader says out loud.
|
|
52
|
+
a11y: 'what it announces',
|
|
53
|
+
// Both the structure digest and the rect: tags, attributes, child boundaries,
|
|
54
|
+
// and where the boxes ended up. One phrase, because a reviewer asking "did the
|
|
55
|
+
// layout move" is not distinguishing the two and the digests cannot either.
|
|
56
|
+
geometry: 'its layout',
|
|
57
|
+
token: 'its style values',
|
|
58
|
+
content: 'its text',
|
|
59
|
+
// Never produced by a hash comparison — a component hash is built from a
|
|
60
|
+
// document. Here so a report that grows one is not printed as a bare slug.
|
|
61
|
+
texture: 'its texture',
|
|
62
|
+
};
|
|
63
|
+
/** `its layout and its style values` — the bands as one clause, loudest first. */
|
|
64
|
+
export function senses(bands) {
|
|
65
|
+
const words = bands.map(senseOf);
|
|
66
|
+
if (words.length <= 1)
|
|
67
|
+
return words[0] ?? '';
|
|
68
|
+
return `${words.slice(0, -1).join(', ')} and ${words[words.length - 1] ?? ''}`;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* One subject, with the hashes and the regions read against each other.
|
|
72
|
+
*
|
|
73
|
+
* The join is on the component name and nothing else, which is the only key the
|
|
74
|
+
* two tiers share: a region carries the name its box resolved to, and a hash
|
|
75
|
+
* entry carries the name the boundary walk assigned. Both come from the same
|
|
76
|
+
* walk in the same run, so a name that appears in one and not the other is a
|
|
77
|
+
* real disagreement rather than a spelling.
|
|
78
|
+
*/
|
|
79
|
+
export function senseOfSubject(subject) {
|
|
80
|
+
const drawn = new Set(subject.regions
|
|
81
|
+
.map((region) => region.component)
|
|
82
|
+
.filter((name) => name !== undefined));
|
|
83
|
+
const moved = (subject.moved ?? []).map((entry) => ({ ...entry, drawn: drawn.has(entry.component) }));
|
|
84
|
+
const stirred = new Set(moved.map((entry) => entry.component));
|
|
85
|
+
return {
|
|
86
|
+
measured: subject.moved !== undefined,
|
|
87
|
+
moved,
|
|
88
|
+
missed: moved.filter((entry) => entry.cause && !entry.drawn),
|
|
89
|
+
// Only when the hashes were read. Without them every named region would land
|
|
90
|
+
// here, and the page would report the whole build as collateral.
|
|
91
|
+
pushed: subject.moved === undefined
|
|
92
|
+
? []
|
|
93
|
+
: [...drawn].filter((name) => !stirred.has(name)).sort(),
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* The same reading, folded over every render one change appeared in.
|
|
98
|
+
*
|
|
99
|
+
* Union rather than intersection, and the difference is not cosmetic. A restyled
|
|
100
|
+
* button in a card that also grew moves in `token` everywhere and in `geometry`
|
|
101
|
+
* only where the row reflowed; an intersection would drop the second and report
|
|
102
|
+
* a size change as a repaint.
|
|
103
|
+
*/
|
|
104
|
+
export function senseAcross(component, appearances) {
|
|
105
|
+
const bands = new Set();
|
|
106
|
+
const missedIn = [];
|
|
107
|
+
const unmeasured = [];
|
|
108
|
+
const others = new Map();
|
|
109
|
+
for (const { subject } of appearances) {
|
|
110
|
+
const sense = senseOfSubject(subject);
|
|
111
|
+
if (!sense.measured) {
|
|
112
|
+
unmeasured.push(subject.subject);
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
for (const entry of sense.moved) {
|
|
116
|
+
if (entry.component === component) {
|
|
117
|
+
for (const band of entry.bands)
|
|
118
|
+
bands.add(band);
|
|
119
|
+
if (!entry.drawn)
|
|
120
|
+
missedIn.push(subject.subject);
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
const seen = others.get(entry.component) ?? new Set();
|
|
124
|
+
for (const band of entry.bands)
|
|
125
|
+
seen.add(band);
|
|
126
|
+
others.set(entry.component, seen);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return {
|
|
130
|
+
measured: appearances.length - unmeasured.length,
|
|
131
|
+
bands: loudestFirst(bands),
|
|
132
|
+
missedIn,
|
|
133
|
+
unmeasured,
|
|
134
|
+
alongside: [...others.entries()]
|
|
135
|
+
.map(([name, seen]) => ({ component: name, bands: loudestFirst(seen) }))
|
|
136
|
+
.sort((left, right) => left.component.localeCompare(right.component)),
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Loudest first, and held here rather than imported.
|
|
141
|
+
*
|
|
142
|
+
* `BANDS` is `core`'s, and this package reads reports rather than core's compare
|
|
143
|
+
* tier — a surface that pulled the ordering across would tie the words on a page
|
|
144
|
+
* to a library it does not otherwise need.
|
|
145
|
+
*/
|
|
146
|
+
export const ORDER = ['a11y', 'geometry', 'token', 'content', 'texture'];
|
|
147
|
+
/**
|
|
148
|
+
* The known bands in their own order, then anything this build has not heard of.
|
|
149
|
+
*
|
|
150
|
+
* A newer report may carry a band added after this deployment shipped, and a
|
|
151
|
+
* filter over {@link ORDER} would drop it — the surface would report four bands
|
|
152
|
+
* where the run recorded five, and nothing on the page would say so. Unknown
|
|
153
|
+
* words go last because the order is a loudness claim this build cannot make
|
|
154
|
+
* about them, and alphabetically because that is an order rather than an
|
|
155
|
+
* accident of iteration.
|
|
156
|
+
*/
|
|
157
|
+
export function loudestFirst(bands) {
|
|
158
|
+
const present = new Set(bands);
|
|
159
|
+
const known = ORDER.filter((band) => present.has(band));
|
|
160
|
+
const rest = [...present].filter((band) => !ORDER.includes(band)).sort();
|
|
161
|
+
return [...known, ...rest];
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* The renders a change moved in and does not appear on.
|
|
165
|
+
*
|
|
166
|
+
* A subject is filed under the component its **leading region** resolved to, and
|
|
167
|
+
* a render has exactly one of those — so a commit that edits a button and a
|
|
168
|
+
* price string files every card under whichever of the two happened to own the
|
|
169
|
+
* larger differing box. The button's page then lists seven renders of the twelve
|
|
170
|
+
* it moved in, and says nothing at all about the other five.
|
|
171
|
+
*
|
|
172
|
+
* That is the reviewer's problem and not a presentation detail: pressing approve
|
|
173
|
+
* on this page decides the renders on this page. The five it never mentioned
|
|
174
|
+
* stay open, under a name the person looking for this change would not think to
|
|
175
|
+
* open. Naming them is the difference between a docket and a docket that can be
|
|
176
|
+
* finished.
|
|
177
|
+
*
|
|
178
|
+
* `here` is the set already listed, passed in rather than derived, because the
|
|
179
|
+
* caller's list is the definition of *already shown* and re-deriving it here
|
|
180
|
+
* would be a second opinion about the same question.
|
|
181
|
+
*/
|
|
182
|
+
export function movedElsewhere(component, subjects, here) {
|
|
183
|
+
const found = [];
|
|
184
|
+
for (const subject of subjects) {
|
|
185
|
+
if (subject.verdict !== 'changed')
|
|
186
|
+
continue;
|
|
187
|
+
if (here.has(subject.subject))
|
|
188
|
+
continue;
|
|
189
|
+
// Caused, not merely moved. A render where this component's box was pushed
|
|
190
|
+
// by somebody else's edit is not a render of this change, and listing it
|
|
191
|
+
// would put the collateral the ranking exists to demote back on the page.
|
|
192
|
+
const moved = (subject.moved ?? []).some((entry) => entry.component === component && entry.cause);
|
|
193
|
+
if (!moved)
|
|
194
|
+
continue;
|
|
195
|
+
const under = causeOf(subject);
|
|
196
|
+
found.push({
|
|
197
|
+
subject: subject.subject,
|
|
198
|
+
...(under === undefined ? {} : { filedUnder: under }),
|
|
199
|
+
drawn: subject.regions.some((region) => region.component === component),
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
return found;
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* What each band is read from, for the reviewer who asks how the page knows.
|
|
206
|
+
*
|
|
207
|
+
* *What it announces moved* is a claim, and until this line existed the page made
|
|
208
|
+
* it with nothing behind it. It is a digest comparison: the run hashes the role,
|
|
209
|
+
* name and state of every node inside a component boundary, the baseline sidecar
|
|
210
|
+
* keeps that hash, and a later run's hash disagrees. Which is a real answer, and
|
|
211
|
+
* also a short one — the values are not stored, so the band says *these differ*
|
|
212
|
+
* and cannot say what the name became. Printing the first half without the second
|
|
213
|
+
* lets a reviewer read a precision into it that is not there.
|
|
214
|
+
*/
|
|
215
|
+
export const READS = {
|
|
216
|
+
a11y: 'the role, name and state of every node in it',
|
|
217
|
+
geometry: 'its nodes’ boxes, and the layout properties they compute to',
|
|
218
|
+
token: 'the style properties it declares, and the tokens behind them',
|
|
219
|
+
content: 'the text its nodes carry',
|
|
220
|
+
texture: 'the pixels themselves, after the other four agreed',
|
|
221
|
+
};
|
|
222
|
+
//# sourceMappingURL=sense.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sense.js","sourceRoot":"","sources":["../../src/ui/sense.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * What moved, in the sense the digests recorded rather than the area it covered.\n *\n * The page this exists for used to read *2 distinct differences, in 7 renders.\n * 30,373 px in total* — three numbers, none of which is the change. A reviewer\n * cannot approve what they have not been told, and every one of those figures\n * describes the measurement instead of the edit.\n *\n * The run always knew better. A baseline carries a component's hashes split five\n * ways, so a comparison of two sidecars can say *this component's declared style\n * moved and its layout moved and its text did not* — without decoding a pixel,\n * and without depending on the difference having landed somewhere a box could be\n * drawn around. The report carries that list per subject; this turns it into the\n * two things a surface needs from it.\n *\n * ## The two things\n *\n * **What each component moved in**, which is the sentence the page was missing.\n *\n * **What the picture lost**, which is the finding nobody was being shown. A\n * region is named by resolving the box the pixels drew back to a component, so\n * it fails precisely when a change matters most: an edit that reflows its\n * neighbours merges into one blob, the blob fits no component, and the name that\n * comes back is the document root. Held against the hashes, that subject stops\n * being a mystery — `Button` moved in its layout and its declared style, and no\n * region carries the name. The gap is reportable, and until it is reported the\n * page is quietly confident about a component it never mentioned.\n *\n * The reverse pile is kept too, and it is not the same finding. A component a\n * region names while its own hashes held still was *pushed* — it is collateral,\n * and a page that called it a change would be doing the ranking this project\n * exists to correct.\n */\n\nimport type { SubjectView } from '../review-types.js';\nimport type { Appearance } from './grouping.js';\nimport { causeOf } from './lead.js';\n\n/** One component the hashes say moved, held against what the picture found. */\nexport interface Sensed {\n readonly component: string;\n readonly bands: readonly string[];\n /** Its own content moved, rather than only its rect being pushed. */\n readonly cause: boolean;\n /** Present on one side only, which `bands` alone cannot say. */\n readonly presence?: 'added' | 'removed';\n /** A region in this subject resolved to this name. */\n readonly drawn: boolean;\n}\n\n/** A subject read at the semantic tier, and the two tiers held against each other. */\nexport interface Sense {\n /**\n * `false` when the baseline carried no hashes, so none of this was measured.\n *\n * The whole record collapses to *unknown* rather than to *nothing moved*, and\n * a surface must print the difference. An empty `moved` on a measured subject\n * with pixels in it is a real finding; the same emptiness unmeasured is a\n * baseline written before the hashes existed.\n */\n readonly measured: boolean;\n readonly moved: readonly Sensed[];\n /** Caused the difference, and no region names it: what the picture lost. */\n readonly missed: readonly Sensed[];\n /** Named by a region while its own hashes held still — pushed, not edited. */\n readonly pushed: readonly string[];\n}\n\n/**\n * The band, in the words a reviewer thinks in.\n *\n * Not the band name. `token` is a word this project uses precisely and a reader\n * arriving from a pull request does not, and a page that leads with it has spent\n * its most important line on vocabulary. The mapping is deliberately flat — one\n * phrase each, no qualifiers — because these are joined into a clause and a\n * hedge repeated four times down a list is a hedge nobody reads.\n *\n * An unknown band is returned unchanged. A newer report carrying a band this\n * deployment has never heard of should print the word it was given, not vanish.\n */\nexport function senseOf(band: string): string {\n return SENSES[band] ?? band;\n}\n\nconst SENSES: Readonly<Record<string, string>> = {\n // Role, accessible name, ARIA state: what a screen reader says out loud.\n a11y: 'what it announces',\n // Both the structure digest and the rect: tags, attributes, child boundaries,\n // and where the boxes ended up. One phrase, because a reviewer asking \"did the\n // layout move\" is not distinguishing the two and the digests cannot either.\n geometry: 'its layout',\n token: 'its style values',\n content: 'its text',\n // Never produced by a hash comparison — a component hash is built from a\n // document. Here so a report that grows one is not printed as a bare slug.\n texture: 'its texture',\n};\n\n/** `its layout and its style values` — the bands as one clause, loudest first. */\nexport function senses(bands: readonly string[]): string {\n const words = bands.map(senseOf);\n if (words.length <= 1) return words[0] ?? '';\n return `${words.slice(0, -1).join(', ')} and ${words[words.length - 1] ?? ''}`;\n}\n\n/**\n * One subject, with the hashes and the regions read against each other.\n *\n * The join is on the component name and nothing else, which is the only key the\n * two tiers share: a region carries the name its box resolved to, and a hash\n * entry carries the name the boundary walk assigned. Both come from the same\n * walk in the same run, so a name that appears in one and not the other is a\n * real disagreement rather than a spelling.\n */\nexport function senseOfSubject(subject: SubjectView): Sense {\n const drawn = new Set(\n subject.regions\n .map((region) => region.component)\n .filter((name): name is string => name !== undefined),\n );\n\n const moved = (subject.moved ?? []).map(\n (entry): Sensed => ({ ...entry, drawn: drawn.has(entry.component) }),\n );\n const stirred = new Set(moved.map((entry) => entry.component));\n\n return {\n measured: subject.moved !== undefined,\n moved,\n missed: moved.filter((entry) => entry.cause && !entry.drawn),\n // Only when the hashes were read. Without them every named region would land\n // here, and the page would report the whole build as collateral.\n pushed:\n subject.moved === undefined\n ? []\n : [...drawn].filter((name) => !stirred.has(name)).sort(),\n };\n}\n\n/** One component's movement across the renders a change showed up in. */\nexport interface Across {\n /**\n * Renders whose baseline carried hashes to compare — never their band count.\n *\n * Zero is the state every other field here is unreadable in: `bands: []` then\n * means *nothing was measured*, and printed as *nothing moved* it is a claim\n * made on the authority of something that never looked.\n */\n readonly measured: number;\n /** Every band this component moved in, anywhere, loudest first. */\n readonly bands: readonly string[];\n /** Renders it moved in where no region carried its name. */\n readonly missedIn: readonly string[];\n /** Renders whose baseline had no hashes, so nothing was measured there. */\n readonly unmeasured: readonly string[];\n /**\n * What else moved in the same renders, and in what sense.\n *\n * The answer to the complaint this page kept earning: a card whose price string\n * changed reports the button, because the button is what the largest box\n * resolved to. Naming the other movers with their own bands is what lets a\n * reviewer see that the text moved in the parent and the button moved in its\n * style — two edits, correctly separated, on one screen.\n */\n readonly alongside: readonly { readonly component: string; readonly bands: readonly string[] }[];\n}\n\n/**\n * The same reading, folded over every render one change appeared in.\n *\n * Union rather than intersection, and the difference is not cosmetic. A restyled\n * button in a card that also grew moves in `token` everywhere and in `geometry`\n * only where the row reflowed; an intersection would drop the second and report\n * a size change as a repaint.\n */\nexport function senseAcross(component: string, appearances: readonly Appearance[]): Across {\n const bands = new Set<string>();\n const missedIn: string[] = [];\n const unmeasured: string[] = [];\n const others = new Map<string, Set<string>>();\n\n for (const { subject } of appearances) {\n const sense = senseOfSubject(subject);\n if (!sense.measured) {\n unmeasured.push(subject.subject);\n continue;\n }\n\n for (const entry of sense.moved) {\n if (entry.component === component) {\n for (const band of entry.bands) bands.add(band);\n if (!entry.drawn) missedIn.push(subject.subject);\n continue;\n }\n const seen = others.get(entry.component) ?? new Set<string>();\n for (const band of entry.bands) seen.add(band);\n others.set(entry.component, seen);\n }\n }\n\n return {\n measured: appearances.length - unmeasured.length,\n bands: loudestFirst(bands),\n missedIn,\n unmeasured,\n alongside: [...others.entries()]\n .map(([name, seen]) => ({ component: name, bands: loudestFirst(seen) }))\n .sort((left, right) => left.component.localeCompare(right.component)),\n };\n}\n\n/**\n * Loudest first, and held here rather than imported.\n *\n * `BANDS` is `core`'s, and this package reads reports rather than core's compare\n * tier — a surface that pulled the ordering across would tie the words on a page\n * to a library it does not otherwise need.\n */\nexport const ORDER: readonly string[] = ['a11y', 'geometry', 'token', 'content', 'texture'];\n\n/**\n * The known bands in their own order, then anything this build has not heard of.\n *\n * A newer report may carry a band added after this deployment shipped, and a\n * filter over {@link ORDER} would drop it — the surface would report four bands\n * where the run recorded five, and nothing on the page would say so. Unknown\n * words go last because the order is a loudness claim this build cannot make\n * about them, and alphabetically because that is an order rather than an\n * accident of iteration.\n */\nexport function loudestFirst(bands: Iterable<string>): readonly string[] {\n const present = new Set(bands);\n const known = ORDER.filter((band) => present.has(band));\n const rest = [...present].filter((band) => !ORDER.includes(band)).sort();\n return [...known, ...rest];\n}\n\n/** A render this component moved in that some other change owns. */\nexport interface Elsewhere {\n readonly subject: string;\n /** The change the docket filed this render under, when a region named one. */\n readonly filedUnder?: string;\n /** A region here carries this component, so its picture is a picture of it. */\n readonly drawn: boolean;\n}\n\n/**\n * The renders a change moved in and does not appear on.\n *\n * A subject is filed under the component its **leading region** resolved to, and\n * a render has exactly one of those — so a commit that edits a button and a\n * price string files every card under whichever of the two happened to own the\n * larger differing box. The button's page then lists seven renders of the twelve\n * it moved in, and says nothing at all about the other five.\n *\n * That is the reviewer's problem and not a presentation detail: pressing approve\n * on this page decides the renders on this page. The five it never mentioned\n * stay open, under a name the person looking for this change would not think to\n * open. Naming them is the difference between a docket and a docket that can be\n * finished.\n *\n * `here` is the set already listed, passed in rather than derived, because the\n * caller's list is the definition of *already shown* and re-deriving it here\n * would be a second opinion about the same question.\n */\nexport function movedElsewhere(\n component: string,\n subjects: readonly SubjectView[],\n here: ReadonlySet<string>,\n): readonly Elsewhere[] {\n const found: Elsewhere[] = [];\n\n for (const subject of subjects) {\n if (subject.verdict !== 'changed') continue;\n if (here.has(subject.subject)) continue;\n // Caused, not merely moved. A render where this component's box was pushed\n // by somebody else's edit is not a render of this change, and listing it\n // would put the collateral the ranking exists to demote back on the page.\n const moved = (subject.moved ?? []).some(\n (entry) => entry.component === component && entry.cause,\n );\n if (!moved) continue;\n\n const under = causeOf(subject);\n found.push({\n subject: subject.subject,\n ...(under === undefined ? {} : { filedUnder: under }),\n drawn: subject.regions.some((region) => region.component === component),\n });\n }\n\n return found;\n}\n\n/**\n * What each band is read from, for the reviewer who asks how the page knows.\n *\n * *What it announces moved* is a claim, and until this line existed the page made\n * it with nothing behind it. It is a digest comparison: the run hashes the role,\n * name and state of every node inside a component boundary, the baseline sidecar\n * keeps that hash, and a later run's hash disagrees. Which is a real answer, and\n * also a short one — the values are not stored, so the band says *these differ*\n * and cannot say what the name became. Printing the first half without the second\n * lets a reviewer read a precision into it that is not there.\n */\nexport const READS: Readonly<Record<string, string>> = {\n a11y: 'the role, name and state of every node in it',\n geometry: 'its nodes’ boxes, and the layout properties they compute to',\n token: 'the style properties it declares, and the tokens behind them',\n content: 'the text its nodes carry',\n texture: 'the pixels themselves, after the other four agreed',\n};\n"]}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The subjects that came back green, and which kind of green each one is.
|
|
3
|
+
*
|
|
4
|
+
* The rail carries what needs a decision, and a subject that was absorbed by an
|
|
5
|
+
* ignore does not — the rule already made the decision, on a day somebody wrote
|
|
6
|
+
* it into the config. But green by measurement and green by declaration are not
|
|
7
|
+
* the same fact, and the second is the one that goes wrong silently: a rule that
|
|
8
|
+
* has grown over a real regression reports `ignored` every run and reads exactly
|
|
9
|
+
* like a subject nothing happened to.
|
|
10
|
+
*
|
|
11
|
+
* So they are kept, folded. Behind a `<details>` because on a run of three
|
|
12
|
+
* hundred stories this is three hundred lines of nothing, and open in one click
|
|
13
|
+
* because the moment a reviewer wants it is the moment they are asking *what did
|
|
14
|
+
* this build not tell me*.
|
|
15
|
+
*
|
|
16
|
+
* The sentence under each name is the observer's own, unedited. This surface did
|
|
17
|
+
* not measure anything and has nothing to add — and where the record does not say
|
|
18
|
+
* which rule absorbed a subject, the note says that rather than leaving a reader
|
|
19
|
+
* to conclude the difference was small.
|
|
20
|
+
*
|
|
21
|
+
* Which rule that was is read by `greenBecause` in the report package, the same
|
|
22
|
+
* call the HTML report makes over the same block. The report and this page are
|
|
23
|
+
* two renderings of one run and are read by one person, often minutes apart; a
|
|
24
|
+
* second derivation here is a second answer waiting to happen.
|
|
25
|
+
*/
|
|
26
|
+
import type { ReactElement } from 'react';
|
|
27
|
+
import { type IgnoreLedger } from '@variance-authority/report';
|
|
28
|
+
import type { SubjectView } from '../review-types.js';
|
|
29
|
+
/**
|
|
30
|
+
* Whether a subject is one the rail carries.
|
|
31
|
+
*
|
|
32
|
+
* The report's own predicate, word for word, and it is shared by copy rather than
|
|
33
|
+
* import because the two live in different packages. Both green verdicts are out:
|
|
34
|
+
* `unchanged` because nothing moved, and `ignored` because a declaration already
|
|
35
|
+
* decided it. A rail that carried `ignored` would put a subject nobody can act on
|
|
36
|
+
* in the queue, under a header that says how many are awaiting review.
|
|
37
|
+
*/
|
|
38
|
+
export declare function needsReview(verdict: SubjectView['verdict']): boolean;
|
|
39
|
+
export declare function Settled({ subjects, ignores, }: {
|
|
40
|
+
readonly subjects: readonly SubjectView[];
|
|
41
|
+
readonly ignores: IgnoreLedger | null;
|
|
42
|
+
}): ReactElement | null;
|
|
43
|
+
//# sourceMappingURL=settled.d.ts.map
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { greenBecause } from '@variance-authority/report';
|
|
3
|
+
import { count, number } from './text.js';
|
|
4
|
+
/** The two green verdicts, in the order they are worth reading. */
|
|
5
|
+
const GREEN = ['ignored', 'unchanged'];
|
|
6
|
+
/**
|
|
7
|
+
* Whether a subject is one the rail carries.
|
|
8
|
+
*
|
|
9
|
+
* The report's own predicate, word for word, and it is shared by copy rather than
|
|
10
|
+
* import because the two live in different packages. Both green verdicts are out:
|
|
11
|
+
* `unchanged` because nothing moved, and `ignored` because a declaration already
|
|
12
|
+
* decided it. A rail that carried `ignored` would put a subject nobody can act on
|
|
13
|
+
* in the queue, under a header that says how many are awaiting review.
|
|
14
|
+
*/
|
|
15
|
+
export function needsReview(verdict) {
|
|
16
|
+
return verdict !== 'unchanged' && verdict !== 'ignored';
|
|
17
|
+
}
|
|
18
|
+
export function Settled({ subjects, ignores, }) {
|
|
19
|
+
const green = subjects.filter((subject) => !needsReview(subject.verdict));
|
|
20
|
+
if (green.length === 0)
|
|
21
|
+
return null;
|
|
22
|
+
const absorbed = green.filter((subject) => subject.verdict === 'ignored');
|
|
23
|
+
return (_jsxs("details", { className: "va-settled", children: [_jsxs("summary", { children: [count(green.length - absorbed.length, 'subject'), " unchanged", absorbed.length === 0
|
|
24
|
+
? ''
|
|
25
|
+
: ` · ${count(absorbed.length, 'subject')} green because a rule absorbed the difference`] }), GREEN.map((verdict) => {
|
|
26
|
+
const rows = green.filter((subject) => subject.verdict === verdict);
|
|
27
|
+
if (rows.length === 0)
|
|
28
|
+
return null;
|
|
29
|
+
return (_jsx("ul", { className: "va-settled-list", children: rows.map((subject) => (_jsxs("li", { children: [_jsx("span", { className: `va-dot va-${subject.verdict}` }), _jsx("strong", { children: subject.subject }), _jsx("span", { className: "va-note", children: subject.because }), _jsx(Because, { subject: subject })] }, subject.subject))) }, verdict));
|
|
30
|
+
}), absorbed.length > 0 && ignores === null ? (_jsx("p", { className: "va-note", children: "This build carried no ignore ledger, so which rule absorbed each of these \u2014 and whether it absorbed anything anywhere else \u2014 is not recorded here." })) : null] }));
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Which declaration decided this subject, when the record says.
|
|
34
|
+
*
|
|
35
|
+
* `unsaid` is rendered rather than skipped. A green-by-declaration row with no
|
|
36
|
+
* rule beside it reads as a subject whose difference was too small to name, and
|
|
37
|
+
* the difference between *nobody wrote it down* and *nothing happened* is the
|
|
38
|
+
* whole reason `ignored` is a word of its own.
|
|
39
|
+
*
|
|
40
|
+
* `masked` is the row worth the most and belongs to `unchanged`: a rule stood
|
|
41
|
+
* over this subject and caught nothing. One run of that is ordinary; it is the
|
|
42
|
+
* count in the ledger below, run after run, that turns it into a finding.
|
|
43
|
+
*/
|
|
44
|
+
function Because({ subject }) {
|
|
45
|
+
const green = greenBecause(subject);
|
|
46
|
+
switch (green.kind) {
|
|
47
|
+
case 'measured':
|
|
48
|
+
return null;
|
|
49
|
+
case 'masked':
|
|
50
|
+
return (_jsxs("span", { className: "va-note", children: ["masked in ", count(green.boxes, 'place'), ", and nothing differed under them"] }));
|
|
51
|
+
case 'relaxed':
|
|
52
|
+
return (_jsxs("span", { className: "va-mark va-ignored", children: ["asserted on ", green.level, " \u00B7 ", _jsx("code", { children: green.rule }), " absorbed ", green.bands.join(', ')] }));
|
|
53
|
+
case 'unsaid':
|
|
54
|
+
return (_jsx("span", { className: "va-note", children: "this build does not record which rule absorbed it" }));
|
|
55
|
+
default:
|
|
56
|
+
return (_jsxs("span", { className: "va-mark va-ignored", children: ["absorbed by ", _jsx("code", { children: green.rules.join(', ') }), " \u00B7 ", number(green.pixels), " px"] }));
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
//# sourceMappingURL=settled.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"settled.js","sourceRoot":"","sources":["../../src/ui/settled.tsx"],"names":[],"mappings":"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","sourcesContent":["/**\n * The subjects that came back green, and which kind of green each one is.\n *\n * The rail carries what needs a decision, and a subject that was absorbed by an\n * ignore does not — the rule already made the decision, on a day somebody wrote\n * it into the config. But green by measurement and green by declaration are not\n * the same fact, and the second is the one that goes wrong silently: a rule that\n * has grown over a real regression reports `ignored` every run and reads exactly\n * like a subject nothing happened to.\n *\n * So they are kept, folded. Behind a `<details>` because on a run of three\n * hundred stories this is three hundred lines of nothing, and open in one click\n * because the moment a reviewer wants it is the moment they are asking *what did\n * this build not tell me*.\n *\n * The sentence under each name is the observer's own, unedited. This surface did\n * not measure anything and has nothing to add — and where the record does not say\n * which rule absorbed a subject, the note says that rather than leaving a reader\n * to conclude the difference was small.\n *\n * Which rule that was is read by `greenBecause` in the report package, the same\n * call the HTML report makes over the same block. The report and this page are\n * two renderings of one run and are read by one person, often minutes apart; a\n * second derivation here is a second answer waiting to happen.\n */\n\nimport type { ReactElement } from 'react';\nimport { greenBecause, type Green, type IgnoreLedger } from '@variance-authority/report';\nimport type { SubjectView } from '../review-types.js';\nimport { count, number } from './text.js';\n\n/** The two green verdicts, in the order they are worth reading. */\nconst GREEN: readonly SubjectView['verdict'][] = ['ignored', 'unchanged'];\n\n/**\n * Whether a subject is one the rail carries.\n *\n * The report's own predicate, word for word, and it is shared by copy rather than\n * import because the two live in different packages. Both green verdicts are out:\n * `unchanged` because nothing moved, and `ignored` because a declaration already\n * decided it. A rail that carried `ignored` would put a subject nobody can act on\n * in the queue, under a header that says how many are awaiting review.\n */\nexport function needsReview(verdict: SubjectView['verdict']): boolean {\n return verdict !== 'unchanged' && verdict !== 'ignored';\n}\n\nexport function Settled({\n subjects,\n ignores,\n}: {\n readonly subjects: readonly SubjectView[];\n readonly ignores: IgnoreLedger | null;\n}): ReactElement | null {\n const green = subjects.filter((subject) => !needsReview(subject.verdict));\n if (green.length === 0) return null;\n\n const absorbed = green.filter((subject) => subject.verdict === 'ignored');\n\n return (\n <details className=\"va-settled\">\n <summary>\n {count(green.length - absorbed.length, 'subject')} unchanged\n {absorbed.length === 0\n ? ''\n : ` · ${count(absorbed.length, 'subject')} green because a rule absorbed the difference`}\n </summary>\n\n {GREEN.map((verdict) => {\n const rows = green.filter((subject) => subject.verdict === verdict);\n if (rows.length === 0) return null;\n return (\n <ul key={verdict} className=\"va-settled-list\">\n {rows.map((subject) => (\n <li key={subject.subject}>\n <span className={`va-dot va-${subject.verdict}`} />\n <strong>{subject.subject}</strong>\n <span className=\"va-note\">{subject.because}</span>\n <Because subject={subject} />\n </li>\n ))}\n </ul>\n );\n })}\n\n {absorbed.length > 0 && ignores === null ? (\n <p className=\"va-note\">\n This build carried no ignore ledger, so which rule absorbed each of these — and whether\n it absorbed anything anywhere else — is not recorded here.\n </p>\n ) : null}\n </details>\n );\n}\n\n/**\n * Which declaration decided this subject, when the record says.\n *\n * `unsaid` is rendered rather than skipped. A green-by-declaration row with no\n * rule beside it reads as a subject whose difference was too small to name, and\n * the difference between *nobody wrote it down* and *nothing happened* is the\n * whole reason `ignored` is a word of its own.\n *\n * `masked` is the row worth the most and belongs to `unchanged`: a rule stood\n * over this subject and caught nothing. One run of that is ordinary; it is the\n * count in the ledger below, run after run, that turns it into a finding.\n */\nfunction Because({ subject }: { readonly subject: SubjectView }): ReactElement | null {\n const green: Green = greenBecause(subject);\n\n switch (green.kind) {\n case 'measured':\n return null;\n case 'masked':\n return (\n <span className=\"va-note\">\n masked in {count(green.boxes, 'place')}, and nothing differed under them\n </span>\n );\n case 'relaxed':\n return (\n <span className=\"va-mark va-ignored\">\n asserted on {green.level} · <code>{green.rule}</code> absorbed {green.bands.join(', ')}\n </span>\n );\n case 'unsaid':\n return (\n <span className=\"va-note\">\n this build does not record which rule absorbed it\n </span>\n );\n default:\n return (\n <span className=\"va-mark va-ignored\">\n absorbed by <code>{green.rules.join(', ')}</code> · {number(green.pixels)} px\n </span>\n );\n }\n}\n"]}
|