@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 @@
|
|
|
1
|
+
{"version":3,"file":"moved.js","sourceRoot":"","sources":["../../src/ui/moved.tsx"],"names":[],"mappings":"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","sourcesContent":["/**\n * The sentence the page was missing: what changed, rather than how much of it.\n *\n * Every other line on a change page is a fact *about* the difference — how many\n * renders carry it, whether the commit reaches it, whether the last build showed\n * it already. None of them is the difference. A reviewer asked to approve\n * something has to be told what it is, and *2 distinct differences, in 7 renders,\n * 30,373 px in total* tells them what was measured instead.\n *\n * [`sense.ts`](./sense.ts) does the reading. This draws the two halves of it, and\n * the second half is the one worth defending: a component the hashes say moved\n * and no region names is not a rendering detail, it is the picture failing on\n * exactly the change that matters most. An edit that reflows its neighbours\n * merges into one blob, the blob fits no component, and the name that comes back\n * is the document root — so the change with the widest consequence is the one\n * most likely to arrive anonymous. It is printed here as a finding rather than\n * left as a silence, because until it is, the page is confidently describing a\n * subject it never mentioned.\n */\n\nimport type { ReactElement } from 'react';\nimport type { SubjectView } from '../review-types.js';\nimport { howFar, placed, type Distance, type Ruler } from './distance.js';\nimport type { Handed } from './handed.js';\nimport type { Route } from './route.js';\nimport {\n senseOf,\n senseOfSubject,\n senses,\n type Across,\n type Elsewhere,\n type Sensed,\n} from './sense.js';\nimport { Go } from './shell.js';\nimport { count } from './text.js';\n\n/** How many render names are listed before the rest become a count. */\nconst NAMED = 4;\n\n/**\n * The lead sentence: what this change is, before anything about its size.\n *\n * It goes first because it is the only line on the page a reviewer cannot decide\n * without. Nothing at all when no render here compared hashes — a page whose\n * loudest line said the baseline format is old would be spending it on a fact\n * about the store rather than about the change.\n */\nexport function MovedLead({\n component,\n across,\n handed,\n}: {\n readonly component: string;\n readonly across: Across;\n /** Bands a parent owns, subtracted here and named by {@link HandedTo}. */\n readonly handed: readonly Handed[];\n}): ReactElement | null {\n const given = new Set(handed.map((each) => each.band));\n const own = across.bands.filter((band) => !given.has(band));\n\n if (own.length > 0) {\n return (\n <p className=\"va-decide-lead\">\n <strong>{component}</strong> moved in <em>{senses(own)}</em>.\n </p>\n );\n }\n\n if (across.measured === 0) return null;\n\n // Two different nothings. Everything this component moved in belongs to\n // somebody above it, which the lines under this name — against a component\n // whose own hashes held still in every render, which is collateral wearing a\n // name because the largest differing area happened to resolve here.\n return given.size > 0 ? (\n <p className=\"va-decide-lead\">\n Nothing of <strong>{component}</strong>’s own moved.\n </p>\n ) : (\n <p className=\"va-decide-lead\">\n Nothing in <strong>{component}</strong>’s own hashes moved: every render here was pushed by\n something else on the page.\n </p>\n );\n}\n\n/**\n * The bands this component was handed, each under the name that owns it.\n *\n * Directly under the lead, because it is the other half of the same sentence:\n * the lead now says less than the hashes did, and this is where the rest went.\n * The count is the evidence and not decoration — *moved in the 6 renders\n * ProductCard draws, still in the other 10* is the partition [`handed.ts`](./handed.ts)\n * found, and it is the whole reason the band is filed here rather than on the\n * component's own line.\n *\n * The holder is the link when the docket has a page for it. A reviewer reading\n * *what it announces is `ProductCard`’s* has exactly one next move.\n */\nexport function HandedTo({\n handed,\n build,\n changes,\n go,\n}: {\n readonly handed: readonly Handed[];\n readonly build: string;\n /** Components with a change page of their own, so a link goes somewhere. */\n readonly changes: ReadonlySet<string>;\n readonly go: (route: Route) => void;\n}): ReactElement | null {\n if (handed.length === 0) return null;\n\n return (\n <>\n {handed.map(({ band, holders, moved, held }) => (\n <p key={band} className=\"va-handed\">\n <em>{senseOf(band)}</em> is{' '}\n {holders.map((holder, index) => (\n <span key={holder}>\n {index === 0 ? null : ' and '}\n {changes.has(holder) ? (\n <Go\n to={{ page: 'change', build, change: holder }}\n go={go}\n className=\"va-handed-go\"\n title={`The decision belongs to ${holder}, which is a change on this build`}\n >\n {holder}\n </Go>\n ) : (\n <strong>{holder}</strong>\n )}\n </span>\n ))}\n ’s.{' '}\n <span className=\"va-note\">\n Moved in {count(moved, 'render')} {holders.length === 1 ? 'it draws' : 'they draw'},\n still in the other {held}.\n </span>\n </p>\n ))}\n </>\n );\n}\n\n/**\n * The rest of the reading: what the picture lost, and what moved beside it.\n *\n * Separate from {@link MovedLead} because it is detail and the lead is not, and\n * it renders nothing at all when there is no detail to give. A heading over a\n * sentence saying everything was ordinary is how a reader learns to skip the\n * place the exceptions will appear.\n */\nexport function WhatMoved({\n component,\n across,\n far,\n except,\n}: {\n readonly component: string;\n readonly across: Across;\n /** How far each passenger is from this change, when a diff was read. */\n readonly far?: Ruler | undefined;\n /**\n * Names a section above already accounted for, left out of the list here.\n *\n * A consumer that reflowed is in both piles by construction — it draws this\n * component, and it moved in the same renders. Above it is placed against the\n * ones that held still, which is what makes it readable; here it would be a\n * name in an alphabetised list with a band beside it, saying the same thing\n * with the comparison removed.\n */\n readonly except?: ReadonlySet<string> | undefined;\n}): ReactElement | null {\n const alongside = across.alongside.filter((other) => except?.has(other.component) !== true);\n const quiet =\n across.missedIn.length === 0 && alongside.length === 0 && across.unmeasured.length === 0;\n if (quiet) return null;\n\n return (\n <section className=\"va-moved\">\n <h2>What moved with it</h2>\n\n {across.missedIn.length === 0 ? null : (\n <p className=\"va-moved-lost\">\n In {count(across.missedIn.length, 'render')} no region carries{' '}\n <strong>{component}</strong> — the difference merged into a larger area and resolved\n elsewhere, so the picture there is not a picture of this.{' '}\n <Renders subjects={across.missedIn} />\n </p>\n )}\n\n {alongside.length === 0 ? null : (\n <>\n {/* Only under the paragraph above. With nothing between them the two\n headings stack, and a heading whose whole job is to separate is\n noise when there is nothing on the other side of it. */}\n {across.missedIn.length === 0 ? null : <h3>Moving beside it</h3>}\n <ul className=\"va-moved-with\">\n {alongside.map((other) => (\n <li key={other.component}>\n <span className=\"va-moved-name\">{other.component}</span>{' '}\n <span className=\"va-note\">{senses(other.bands)}</span>\n <Far component={other.component} far={far} />\n </li>\n ))}\n </ul>\n <Unplaced\n anchor={component}\n components={alongside.map((other) => other.component)}\n far={far}\n />\n </>\n )}\n\n {across.unmeasured.length === 0 ? null : (\n <p className=\"va-note\">\n {count(across.unmeasured.length, 'render')} compared against a baseline carrying no\n component hashes, so nothing above was measured there.{' '}\n <Renders subjects={across.unmeasured} />\n </p>\n )}\n </section>\n );\n}\n\n/**\n * The same reading for one render, where the join is worth showing in full.\n *\n * A change page folds the renders together and answers *what did this component\n * do*. Here both tiers are in front of the reviewer at once, so the useful shape\n * is the disagreement: which names the hashes and the regions agree on, which the\n * hashes hold alone, and which the regions hold alone. The third pile is the one\n * a reader is most likely to get backwards — a component a region named while its\n * own hashes held still was pushed, and calling that a change is the ranking this\n * project exists to correct.\n */\nexport function MovedHere({\n subject,\n anchor,\n far,\n}: {\n readonly subject: SubjectView;\n /** The change this render is filed under, which `far` measures from. */\n readonly anchor?: string | undefined;\n readonly far?: Ruler | undefined;\n}): ReactElement {\n const sense = senseOfSubject(subject);\n\n if (!sense.measured) {\n return (\n <p className=\"va-note\">\n This render was compared against a baseline carrying no component hashes, so nothing says\n which components moved — only where the pixels differ.\n </p>\n );\n }\n\n if (sense.moved.length === 0) {\n return (\n <p className=\"va-note\">\n Both revisions were read and every component hash matched. Whatever differs here is beneath\n the semantic tier, or belongs to a part of the page the walk assigned no component to.\n </p>\n );\n }\n\n return (\n <>\n <ul className=\"va-moved-list\">\n {sense.moved.map((entry) => (\n <Moved key={entry.component} entry={entry} anchor={anchor} far={far} />\n ))}\n </ul>\n {anchor === undefined ? null : (\n <Unplaced\n anchor={anchor}\n components={sense.moved.map((entry) => entry.component)}\n far={far}\n />\n )}\n {sense.pushed.length === 0 ? null : (\n <p className=\"va-note\">\n A region names {sense.pushed.join(', ')}, whose own hashes held still: pushed by\n something else, not edited.\n </p>\n )}\n </>\n );\n}\n\n/** One component, its bands, and whether the picture found it. */\nfunction Moved({\n entry,\n anchor,\n far,\n}: {\n readonly entry: Sensed;\n readonly anchor?: string | undefined;\n readonly far?: Ruler | undefined;\n}): ReactElement {\n return (\n <li className={entry.cause ? 'va-moved-row' : 'va-moved-row va-moved-passenger'}>\n <span className=\"va-moved-name\">{entry.component}</span>\n <span className=\"va-note\">\n {entry.presence === undefined ? senses(entry.bands) : entry.presence}\n </span>\n {/* Distance to itself is not a measurement. The row the rest are measured\n from would otherwise carry `same file`, which is true of every component\n and its own declaration and says nothing about this one. */}\n {entry.component === anchor ? null : <Far component={entry.component} far={far} />}\n {entry.drawn ? null : (\n <span\n className=\"va-mark va-alarm\"\n title=\"The hashes say this component moved, and no region in this render carries its name — the difference merged into a larger area and resolved elsewhere.\"\n >\n no region\n </span>\n )}\n </li>\n );\n}\n\n/** A few render names, then a count — a list of twenty is not read as evidence. */\nfunction Renders({ subjects }: { readonly subjects: readonly string[] }): ReactElement {\n const named = subjects.slice(0, NAMED);\n const rest = subjects.length - named.length;\n\n return (\n <span className=\"va-note\" title={subjects.join(', ')}>\n {named.join(', ')}\n {rest === 0 ? '' : ` and ${count(rest, 'other')}`}\n </span>\n );\n}\n\n/**\n * The renders this change moved in that the docket files under another name.\n *\n * The gap between the two tiers, printed. A subject belongs to whichever\n * component its leading *region* resolved to, and a commit that edits a button\n * and a price string in the same card files every card under whichever of the two\n * drew the larger box — so the button's page can list seven renders of the twelve\n * its hashes moved in, and be silent about five.\n *\n * Silent is the problem. Approving here decides the renders here; the five stay\n * open under a name nobody looking for this change would open. So they are named,\n * with the change they were filed under, and with the one sentence that keeps a\n * reviewer from assuming the button is finished.\n */\nexport function MovedElsewhere({\n component,\n found,\n build,\n go,\n}: {\n readonly component: string;\n readonly found: readonly Elsewhere[];\n readonly build: string;\n readonly go: (route: Route) => void;\n}): ReactElement | null {\n if (found.length === 0) return null;\n\n return (\n <section className=\"va-moved\">\n <h2>Also moved, filed elsewhere</h2>\n <p className=\"va-moved-lost\">\n <strong>{component}</strong>’s hashes moved in {count(found.length, 'further render')}, where\n a larger difference took the name. Deciding this change does not decide them.\n </p>\n <ul className=\"va-moved-list\">\n {found.map((each) => (\n <li key={each.subject} className=\"va-moved-row\">\n <Go\n to={{ page: 'subject', build, subject: each.subject }}\n go={go}\n className=\"va-moved-name\"\n >\n {each.subject}\n </Go>\n <span className=\"va-note\">\n {each.filedUnder === undefined ? 'no component named the cause' : `under ${each.filedUnder}`}\n </span>\n {each.drawn ? null : (\n <span\n className=\"va-mark va-alarm\"\n title=\"No region in that render carries this component either, so its picture is not a picture of this change.\"\n >\n no region\n </span>\n )}\n </li>\n ))}\n </ul>\n </section>\n );\n}\n\n/** How far a component is from the change, when the records place it. */\nfunction Far({\n component,\n far,\n}: {\n readonly component: string;\n readonly far?: Ruler | undefined;\n}): ReactElement | null {\n const said = distanceOf(far, component);\n if (said === undefined) return null;\n\n return <span className=\"va-moved-far\">{said}</span>;\n}\n\nfunction distanceOf(far: Ruler | undefined, component: string): string | undefined {\n const distance = far?.(component);\n return distance === undefined ? undefined : howFar(distance);\n}\n\n/**\n * What the list could not place, said once instead of on every row.\n *\n * A reader who sees a distance on two rows and nothing on the other seven will\n * read the silence as *near*, which is the one thing it does not mean. So the\n * absence is stated — as a fact about the graph, not about the components, and\n * without claiming they are unrelated. Nothing at all when the run carried no\n * diff: `unknown` is the whole page's condition and the page says it elsewhere.\n */\nfunction Unplaced({\n anchor,\n components,\n far,\n}: {\n readonly anchor: string;\n readonly components: readonly string[];\n readonly far?: Ruler | undefined;\n}): ReactElement | null {\n if (far === undefined) return null;\n\n const distances = components.map((component): Distance => far(component));\n const lost = distances.filter((distance) => distance.kind === 'unreached').length;\n if (lost === 0) return null;\n\n const some = distances.some((distance) => placed(distance));\n\n return (\n <p className=\"va-note\">\n Nothing the commit walked arrives at {some ? count(lost, 'other') : 'any of these'}, and no\n file is recorded for them either, so how far they are from <strong>{anchor}</strong> is not\n something this build measured.\n </p>\n );\n}\n"]}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The address bar, as a hook — thirty lines of history API and no router.
|
|
3
|
+
*
|
|
4
|
+
* The surface has one navigation and it is a path. A router library would bring
|
|
5
|
+
* a context, a matcher, a link component and a peer dependency to do that, into a
|
|
6
|
+
* package whose entire claim is that it needs React and nothing else. The
|
|
7
|
+
* platform already has `pushState` and `popstate`.
|
|
8
|
+
*
|
|
9
|
+
* ## Controlled, when the host has its own history
|
|
10
|
+
*
|
|
11
|
+
* A Next app renders {@link ReviewApp} inside a router that already owns the URL,
|
|
12
|
+
* and two things writing to one history stack is a back button that goes
|
|
13
|
+
* sideways. So the hook takes the host's route and its navigate function when it
|
|
14
|
+
* is given them, and touches nothing. The uncontrolled path — the Node service,
|
|
15
|
+
* which serves a document and one script — is the one that reads `location`.
|
|
16
|
+
*/
|
|
17
|
+
import { type Route } from './route.js';
|
|
18
|
+
/**
|
|
19
|
+
* Where the reader is, and how to send them somewhere else.
|
|
20
|
+
*
|
|
21
|
+
* `undefined` is a real answer and is not turned into the front page. A path
|
|
22
|
+
* nobody claims is a rotted link, and quietly rendering the build list tells the
|
|
23
|
+
* reader their link worked.
|
|
24
|
+
*/
|
|
25
|
+
export declare function useRoute(route?: Route | undefined, onNavigate?: ((route: Route) => void) | undefined): readonly [Route | undefined, (route: Route) => void];
|
|
26
|
+
//# sourceMappingURL=navigation.d.ts.map
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The address bar, as a hook — thirty lines of history API and no router.
|
|
3
|
+
*
|
|
4
|
+
* The surface has one navigation and it is a path. A router library would bring
|
|
5
|
+
* a context, a matcher, a link component and a peer dependency to do that, into a
|
|
6
|
+
* package whose entire claim is that it needs React and nothing else. The
|
|
7
|
+
* platform already has `pushState` and `popstate`.
|
|
8
|
+
*
|
|
9
|
+
* ## Controlled, when the host has its own history
|
|
10
|
+
*
|
|
11
|
+
* A Next app renders {@link ReviewApp} inside a router that already owns the URL,
|
|
12
|
+
* and two things writing to one history stack is a back button that goes
|
|
13
|
+
* sideways. So the hook takes the host's route and its navigate function when it
|
|
14
|
+
* is given them, and touches nothing. The uncontrolled path — the Node service,
|
|
15
|
+
* which serves a document and one script — is the one that reads `location`.
|
|
16
|
+
*/
|
|
17
|
+
import { useCallback, useEffect, useState } from 'react';
|
|
18
|
+
import { hrefOf, parseRoute } from './route.js';
|
|
19
|
+
/** The address as it stands, or `undefined` when this surface does not answer it. */
|
|
20
|
+
function readLocation() {
|
|
21
|
+
if (typeof window === 'undefined')
|
|
22
|
+
return { page: 'builds' };
|
|
23
|
+
return parseRoute(`${window.location.pathname}${window.location.search}`);
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Where the reader is, and how to send them somewhere else.
|
|
27
|
+
*
|
|
28
|
+
* `undefined` is a real answer and is not turned into the front page. A path
|
|
29
|
+
* nobody claims is a rotted link, and quietly rendering the build list tells the
|
|
30
|
+
* reader their link worked.
|
|
31
|
+
*/
|
|
32
|
+
export function useRoute(route, onNavigate) {
|
|
33
|
+
const controlled = route !== undefined && onNavigate !== undefined;
|
|
34
|
+
const [here, setHere] = useState(readLocation);
|
|
35
|
+
useEffect(() => {
|
|
36
|
+
if (controlled || typeof window === 'undefined')
|
|
37
|
+
return undefined;
|
|
38
|
+
const listen = () => setHere(readLocation());
|
|
39
|
+
window.addEventListener('popstate', listen);
|
|
40
|
+
return () => window.removeEventListener('popstate', listen);
|
|
41
|
+
}, [controlled]);
|
|
42
|
+
const go = useCallback((next) => {
|
|
43
|
+
if (typeof window !== 'undefined') {
|
|
44
|
+
window.history.pushState(null, '', hrefOf(next));
|
|
45
|
+
// A page that keeps the previous page's scroll position is a page that
|
|
46
|
+
// opens two thirds of the way down a list the reader has not seen.
|
|
47
|
+
window.scrollTo({ top: 0 });
|
|
48
|
+
}
|
|
49
|
+
setHere(next);
|
|
50
|
+
}, []);
|
|
51
|
+
return controlled ? [route, onNavigate] : [here, go];
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=navigation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"navigation.js","sourceRoot":"","sources":["../../src/ui/navigation.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AACzD,OAAO,EAAE,MAAM,EAAE,UAAU,EAAc,MAAM,YAAY,CAAC;AAE5D,qFAAqF;AACrF,SAAS,YAAY;IACnB,IAAI,OAAO,MAAM,KAAK,WAAW;QAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC;IAC7D,OAAO,UAAU,CAAC,GAAG,MAAM,CAAC,QAAQ,CAAC,QAAQ,GAAG,MAAM,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC;AAC5E,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,QAAQ,CACtB,KAAyB,EACzB,UAAiD;IAEjD,MAAM,UAAU,GAAG,KAAK,KAAK,SAAS,IAAI,UAAU,KAAK,SAAS,CAAC;IACnE,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,GAAG,QAAQ,CAAoB,YAAY,CAAC,CAAC;IAElE,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,UAAU,IAAI,OAAO,MAAM,KAAK,WAAW;YAAE,OAAO,SAAS,CAAC;QAElE,MAAM,MAAM,GAAG,GAAS,EAAE,CAAC,OAAO,CAAC,YAAY,EAAE,CAAC,CAAC;QACnD,MAAM,CAAC,gBAAgB,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;QAC5C,OAAO,GAAG,EAAE,CAAC,MAAM,CAAC,mBAAmB,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;IAC9D,CAAC,EAAE,CAAC,UAAU,CAAC,CAAC,CAAC;IAEjB,MAAM,EAAE,GAAG,WAAW,CAAC,CAAC,IAAW,EAAQ,EAAE;QAC3C,IAAI,OAAO,MAAM,KAAK,WAAW,EAAE,CAAC;YAClC,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,IAAI,EAAE,EAAE,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC;YACjD,uEAAuE;YACvE,mEAAmE;YACnE,MAAM,CAAC,QAAQ,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC;QAC9B,CAAC;QACD,OAAO,CAAC,IAAI,CAAC,CAAC;IAChB,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,OAAO,UAAU,CAAC,CAAC,CAAC,CAAC,KAAK,EAAE,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;AACvD,CAAC","sourcesContent":["/**\n * The address bar, as a hook — thirty lines of history API and no router.\n *\n * The surface has one navigation and it is a path. A router library would bring\n * a context, a matcher, a link component and a peer dependency to do that, into a\n * package whose entire claim is that it needs React and nothing else. The\n * platform already has `pushState` and `popstate`.\n *\n * ## Controlled, when the host has its own history\n *\n * A Next app renders {@link ReviewApp} inside a router that already owns the URL,\n * and two things writing to one history stack is a back button that goes\n * sideways. So the hook takes the host's route and its navigate function when it\n * is given them, and touches nothing. The uncontrolled path — the Node service,\n * which serves a document and one script — is the one that reads `location`.\n */\n\nimport { useCallback, useEffect, useState } from 'react';\nimport { hrefOf, parseRoute, type Route } from './route.js';\n\n/** The address as it stands, or `undefined` when this surface does not answer it. */\nfunction readLocation(): Route | undefined {\n if (typeof window === 'undefined') return { page: 'builds' };\n return parseRoute(`${window.location.pathname}${window.location.search}`);\n}\n\n/**\n * Where the reader is, and how to send them somewhere else.\n *\n * `undefined` is a real answer and is not turned into the front page. A path\n * nobody claims is a rotted link, and quietly rendering the build list tells the\n * reader their link worked.\n */\nexport function useRoute(\n route?: Route | undefined,\n onNavigate?: ((route: Route) => void) | undefined,\n): readonly [Route | undefined, (route: Route) => void] {\n const controlled = route !== undefined && onNavigate !== undefined;\n const [here, setHere] = useState<Route | undefined>(readLocation);\n\n useEffect(() => {\n if (controlled || typeof window === 'undefined') return undefined;\n\n const listen = (): void => setHere(readLocation());\n window.addEventListener('popstate', listen);\n return () => window.removeEventListener('popstate', listen);\n }, [controlled]);\n\n const go = useCallback((next: Route): void => {\n if (typeof window !== 'undefined') {\n window.history.pushState(null, '', hrefOf(next));\n // A page that keeps the previous page's scroll position is a page that\n // opens two thirds of the way down a list the reader has not seen.\n window.scrollTo({ top: 0 });\n }\n setHere(next);\n }, []);\n\n return controlled ? [route, onNavigate] : [here, go];\n}\n"]}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What order the changes are read in — which is the docket's whole argument.
|
|
3
|
+
*
|
|
4
|
+
* A list of changes with no order is a list of screenshots with extra steps. The
|
|
5
|
+
* first version of this page emitted them largest-first, which is the category's
|
|
6
|
+
* ranking and is wrong for the same reason ranking regions by area is wrong: the
|
|
7
|
+
* biggest number on a restyle belongs to whichever container reflowed furthest,
|
|
8
|
+
* and a reviewer who works down that list spends their attention in inverse order
|
|
9
|
+
* to where it is worth anything.
|
|
10
|
+
*
|
|
11
|
+
* So the docket is banded, and the bands are ranked by *what it costs to be
|
|
12
|
+
* wrong about them* rather than by size or by count.
|
|
13
|
+
*
|
|
14
|
+
* 1. **Nothing reaches these.** The commit arrives at no component at all in some
|
|
15
|
+
* render each of these moved in — the one finding here no other tool
|
|
16
|
+
* produces, and the one worth waking somebody for.
|
|
17
|
+
* 2. **Same props, two renderings.** Held at one commit with its inputs equal,
|
|
18
|
+
* it did not settle. An answer, not an absence.
|
|
19
|
+
* 3. **Nothing explains these.** The diff was read, the file graph walked and
|
|
20
|
+
* the enclosure climbed, and none of the three arrive here.
|
|
21
|
+
* 4. **The commit's own causes**, one band each — the file that was edited, or
|
|
22
|
+
* the property that took a new value. [`cause.ts`](./cause.ts) resolves them.
|
|
23
|
+
* 5. **Not in the diff.** No file in the commit declares it and the run recorded
|
|
24
|
+
* no attribution to read instead.
|
|
25
|
+
* 6. **No diff read.** The run carried none, so nothing above applies.
|
|
26
|
+
* 7. **Already decided.** Off the queue, kept on the page.
|
|
27
|
+
*
|
|
28
|
+
* ## Band four used to be three bands, and they were the run's ladder
|
|
29
|
+
*
|
|
30
|
+
* *Edited*, *Owner edited* and *Token moved* were three headings, and they sorted
|
|
31
|
+
* a build by how many hops the run needed to explain each change. Nobody has that
|
|
32
|
+
* question. A reviewer edited three files and wants to know what each of the
|
|
33
|
+
* three did, and under the ladder the answer to that was spread across three
|
|
34
|
+
* headings with the file names in a hover title.
|
|
35
|
+
*
|
|
36
|
+
* So the heading is the cause. `Button` under `ui/button.tsx`; `CardFooter` under
|
|
37
|
+
* `ProductCard.tsx`, because `ProductCard` draws it. Three files in, three
|
|
38
|
+
* headings out — and the rung each change came in on is still on its row, where
|
|
39
|
+
* it explains one component instead of naming a category.
|
|
40
|
+
*
|
|
41
|
+
* The bands above and below stay as they were, and that is the ranking's whole
|
|
42
|
+
* point: a change with no cause in the commit must not be filed under one.
|
|
43
|
+
*
|
|
44
|
+
* Inside a band, **by component name**, ascending. Not by size: a reviewer works
|
|
45
|
+
* a docket by looking for the name they recognise, and a list whose order changes
|
|
46
|
+
* with the pixel count is a list they have to re-read from the top every build.
|
|
47
|
+
*
|
|
48
|
+
* ## A band is a label, not a paragraph
|
|
49
|
+
*
|
|
50
|
+
* Each of these used to ship with its definition printed underneath the heading,
|
|
51
|
+
* every build, unchanged. The definition is the same on every build; the names
|
|
52
|
+
* are not, and the names were the part left in the record. So the heading is a
|
|
53
|
+
* claim of a few words and the evidence is on the row that has it —
|
|
54
|
+
* [`sourceOf`](./grouping.ts) reads the owner, the file or the token off the
|
|
55
|
+
* movement the run wrote, and the rail prints it.
|
|
56
|
+
*
|
|
57
|
+
* ## The other three orders are the reader's, and they are addresses
|
|
58
|
+
*
|
|
59
|
+
* Someone auditing a restyle wants the largest first; someone checking blast
|
|
60
|
+
* radius wants the widest first; someone who knows the name wants the alphabet
|
|
61
|
+
* and nothing else. Those are in the URL rather than in a component's state, so
|
|
62
|
+
* the arrangement travels with the link.
|
|
63
|
+
*/
|
|
64
|
+
import type { BuildDetail } from '../review-types.js';
|
|
65
|
+
import { type Root, type Rooted } from './cause.js';
|
|
66
|
+
import type { Origin } from './grouping.js';
|
|
67
|
+
import type { Order } from './route.js';
|
|
68
|
+
/** Which band a change is read in. */
|
|
69
|
+
export type Lane = 'stranded' | 'reached' | 'contradicted' | 'unexplained' | 'upstream' | 'token' | 'unnamed' | 'unread' | 'decided';
|
|
70
|
+
/** One band of the docket: a claim of a few words, and the rows it holds. */
|
|
71
|
+
export interface Group {
|
|
72
|
+
readonly lane: Lane;
|
|
73
|
+
readonly title: string;
|
|
74
|
+
readonly changes: readonly Rooted[];
|
|
75
|
+
/**
|
|
76
|
+
* The cause in the commit this band *is*, when it is one.
|
|
77
|
+
*
|
|
78
|
+
* Present on the bands that came out of [`cause.ts`](./cause.ts), where the
|
|
79
|
+
* heading is a path or a property rather than a claim about a rung. Absent on
|
|
80
|
+
* the rest, and the absence is what a heading renderer switches on: a file is
|
|
81
|
+
* drawn as a file, and `Nothing reaches these` is drawn as a sentence.
|
|
82
|
+
*/
|
|
83
|
+
readonly root?: Root;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Which band one change belongs to.
|
|
87
|
+
*
|
|
88
|
+
* `decided` first and unconditionally: a change nobody has to act on is not an
|
|
89
|
+
* alarm however it arrived, and leaving it in the alarm band would put a settled
|
|
90
|
+
* finding at the top of the page every build until the branch merges.
|
|
91
|
+
*/
|
|
92
|
+
export declare function laneOf(origin: Origin): Lane;
|
|
93
|
+
/**
|
|
94
|
+
* The four arrangements, as the picker draws them.
|
|
95
|
+
*
|
|
96
|
+
* Named for what the reader gets rather than for the field sorted on, and the
|
|
97
|
+
* default is `Story` because the docket's own argument is that a build is read as
|
|
98
|
+
* one — the unreachable first, the work you asked for next.
|
|
99
|
+
*/
|
|
100
|
+
export declare const ORDERS: readonly {
|
|
101
|
+
readonly order: Order;
|
|
102
|
+
readonly label: string;
|
|
103
|
+
readonly why: string;
|
|
104
|
+
}[];
|
|
105
|
+
/**
|
|
106
|
+
* The docket, arranged.
|
|
107
|
+
*
|
|
108
|
+
* Empty bands are dropped rather than drawn empty, because a heading over nothing
|
|
109
|
+
* is a heading a reader has to check. The three flat orders return one group, so
|
|
110
|
+
* every renderer downstream draws the same shape whichever arrangement it got.
|
|
111
|
+
*/
|
|
112
|
+
export declare function docketOf(build: BuildDetail, origins: readonly Origin[], order: Order): readonly Group[];
|
|
113
|
+
//# sourceMappingURL=order.d.ts.map
|
package/dist/ui/order.js
ADDED
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What order the changes are read in — which is the docket's whole argument.
|
|
3
|
+
*
|
|
4
|
+
* A list of changes with no order is a list of screenshots with extra steps. The
|
|
5
|
+
* first version of this page emitted them largest-first, which is the category's
|
|
6
|
+
* ranking and is wrong for the same reason ranking regions by area is wrong: the
|
|
7
|
+
* biggest number on a restyle belongs to whichever container reflowed furthest,
|
|
8
|
+
* and a reviewer who works down that list spends their attention in inverse order
|
|
9
|
+
* to where it is worth anything.
|
|
10
|
+
*
|
|
11
|
+
* So the docket is banded, and the bands are ranked by *what it costs to be
|
|
12
|
+
* wrong about them* rather than by size or by count.
|
|
13
|
+
*
|
|
14
|
+
* 1. **Nothing reaches these.** The commit arrives at no component at all in some
|
|
15
|
+
* render each of these moved in — the one finding here no other tool
|
|
16
|
+
* produces, and the one worth waking somebody for.
|
|
17
|
+
* 2. **Same props, two renderings.** Held at one commit with its inputs equal,
|
|
18
|
+
* it did not settle. An answer, not an absence.
|
|
19
|
+
* 3. **Nothing explains these.** The diff was read, the file graph walked and
|
|
20
|
+
* the enclosure climbed, and none of the three arrive here.
|
|
21
|
+
* 4. **The commit's own causes**, one band each — the file that was edited, or
|
|
22
|
+
* the property that took a new value. [`cause.ts`](./cause.ts) resolves them.
|
|
23
|
+
* 5. **Not in the diff.** No file in the commit declares it and the run recorded
|
|
24
|
+
* no attribution to read instead.
|
|
25
|
+
* 6. **No diff read.** The run carried none, so nothing above applies.
|
|
26
|
+
* 7. **Already decided.** Off the queue, kept on the page.
|
|
27
|
+
*
|
|
28
|
+
* ## Band four used to be three bands, and they were the run's ladder
|
|
29
|
+
*
|
|
30
|
+
* *Edited*, *Owner edited* and *Token moved* were three headings, and they sorted
|
|
31
|
+
* a build by how many hops the run needed to explain each change. Nobody has that
|
|
32
|
+
* question. A reviewer edited three files and wants to know what each of the
|
|
33
|
+
* three did, and under the ladder the answer to that was spread across three
|
|
34
|
+
* headings with the file names in a hover title.
|
|
35
|
+
*
|
|
36
|
+
* So the heading is the cause. `Button` under `ui/button.tsx`; `CardFooter` under
|
|
37
|
+
* `ProductCard.tsx`, because `ProductCard` draws it. Three files in, three
|
|
38
|
+
* headings out — and the rung each change came in on is still on its row, where
|
|
39
|
+
* it explains one component instead of naming a category.
|
|
40
|
+
*
|
|
41
|
+
* The bands above and below stay as they were, and that is the ranking's whole
|
|
42
|
+
* point: a change with no cause in the commit must not be filed under one.
|
|
43
|
+
*
|
|
44
|
+
* Inside a band, **by component name**, ascending. Not by size: a reviewer works
|
|
45
|
+
* a docket by looking for the name they recognise, and a list whose order changes
|
|
46
|
+
* with the pixel count is a list they have to re-read from the top every build.
|
|
47
|
+
*
|
|
48
|
+
* ## A band is a label, not a paragraph
|
|
49
|
+
*
|
|
50
|
+
* Each of these used to ship with its definition printed underneath the heading,
|
|
51
|
+
* every build, unchanged. The definition is the same on every build; the names
|
|
52
|
+
* are not, and the names were the part left in the record. So the heading is a
|
|
53
|
+
* claim of a few words and the evidence is on the row that has it —
|
|
54
|
+
* [`sourceOf`](./grouping.ts) reads the owner, the file or the token off the
|
|
55
|
+
* movement the run wrote, and the rail prints it.
|
|
56
|
+
*
|
|
57
|
+
* ## The other three orders are the reader's, and they are addresses
|
|
58
|
+
*
|
|
59
|
+
* Someone auditing a restyle wants the largest first; someone checking blast
|
|
60
|
+
* radius wants the widest first; someone who knows the name wants the alphabet
|
|
61
|
+
* and nothing else. Those are in the URL rather than in a component's state, so
|
|
62
|
+
* the arrangement travels with the link.
|
|
63
|
+
*/
|
|
64
|
+
import { rootsOf } from './cause.js';
|
|
65
|
+
/**
|
|
66
|
+
* The nine headings, each the shortest true claim about its rows.
|
|
67
|
+
*
|
|
68
|
+
* None of them explains itself, and the two that name a relation — `upstream`
|
|
69
|
+
* and `token` — say *that* an owner or a token moved without saying which,
|
|
70
|
+
* because that answer is per row and every row carries it.
|
|
71
|
+
*
|
|
72
|
+
* Nobody is addressed. `Edited` and `Owner edited` were *you edited these* and
|
|
73
|
+
* *you edited their owner*, which puts a person in a heading whose subject is a
|
|
74
|
+
* commit — and a reviewer reading somebody else's branch is not the *you* in it.
|
|
75
|
+
* The pair is also the docket's whole distinction, so they are two words each and
|
|
76
|
+
* they rhyme: a change the commit declares, and a change it arrived at.
|
|
77
|
+
*/
|
|
78
|
+
const LANES = [
|
|
79
|
+
{ lane: 'stranded', title: 'Nothing reaches these' },
|
|
80
|
+
{ lane: 'reached', title: 'Edited' },
|
|
81
|
+
{ lane: 'contradicted', title: 'Same props, two renderings' },
|
|
82
|
+
{ lane: 'unexplained', title: 'Nothing explains these' },
|
|
83
|
+
{ lane: 'upstream', title: 'Owner edited' },
|
|
84
|
+
{ lane: 'token', title: 'Token moved' },
|
|
85
|
+
{ lane: 'unnamed', title: 'Not in the diff' },
|
|
86
|
+
{ lane: 'unread', title: 'No diff read' },
|
|
87
|
+
{ lane: 'decided', title: 'Already decided' },
|
|
88
|
+
];
|
|
89
|
+
/**
|
|
90
|
+
* Which band one change belongs to.
|
|
91
|
+
*
|
|
92
|
+
* `decided` first and unconditionally: a change nobody has to act on is not an
|
|
93
|
+
* alarm however it arrived, and leaving it in the alarm band would put a settled
|
|
94
|
+
* finding at the top of the page every build until the branch merges.
|
|
95
|
+
*/
|
|
96
|
+
export function laneOf(origin) {
|
|
97
|
+
if (origin.appearances.every(({ subject }) => subject.decision !== null))
|
|
98
|
+
return 'decided';
|
|
99
|
+
if (origin.reached === undefined)
|
|
100
|
+
return 'unread';
|
|
101
|
+
if (origin.reached)
|
|
102
|
+
return 'reached';
|
|
103
|
+
if ((origin.stranded ?? []).length > 0)
|
|
104
|
+
return 'stranded';
|
|
105
|
+
// Everything below here is *the diff does not name it*, which was one band and
|
|
106
|
+
// is four. The reach graph has said all it can — it climbs from a changed file
|
|
107
|
+
// through its importers, so anything a changed file draws is off it by
|
|
108
|
+
// construction — and what separates these is the run's own walk in the other
|
|
109
|
+
// direction. A missing attribution stays in the last band rather than becoming
|
|
110
|
+
// a rung of its own.
|
|
111
|
+
switch (origin.cause) {
|
|
112
|
+
case 'unexplained':
|
|
113
|
+
return 'unexplained';
|
|
114
|
+
// Not folded in with the one above, which it used to be. `unexplained` says
|
|
115
|
+
// three records were asked and none of them arrive; `contradicted` says one
|
|
116
|
+
// of them answered — the component disagreed with itself at fixed props —
|
|
117
|
+
// and printing that under *none of them arrive here* is the page telling a
|
|
118
|
+
// reviewer the run found nothing about a render it had pinned.
|
|
119
|
+
case 'contradicted':
|
|
120
|
+
return 'contradicted';
|
|
121
|
+
case 'upstream':
|
|
122
|
+
return 'upstream';
|
|
123
|
+
case 'token':
|
|
124
|
+
return 'token';
|
|
125
|
+
default:
|
|
126
|
+
return 'unnamed';
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
/** How wide a change is: the renders it showed up in. */
|
|
130
|
+
function places(origin) {
|
|
131
|
+
return origin.appearances.length;
|
|
132
|
+
}
|
|
133
|
+
const BY = {
|
|
134
|
+
story: (left, right) => left.component.localeCompare(right.component),
|
|
135
|
+
name: (left, right) => left.component.localeCompare(right.component),
|
|
136
|
+
size: (left, right) => right.pixels - left.pixels || left.component.localeCompare(right.component),
|
|
137
|
+
places: (left, right) => places(right) - places(left) || left.component.localeCompare(right.component),
|
|
138
|
+
};
|
|
139
|
+
const FLAT = {
|
|
140
|
+
name: {
|
|
141
|
+
title: 'Every change, by component',
|
|
142
|
+
why: 'The alphabet, and nothing else. For when you know the name you are looking for.',
|
|
143
|
+
},
|
|
144
|
+
size: {
|
|
145
|
+
title: 'Every change, largest first',
|
|
146
|
+
why: 'By the pixels the component’s own regions moved. Useful for auditing a restyle, and misleading as a default: the biggest number on a page belongs to whatever reflowed furthest, not to what was edited.',
|
|
147
|
+
},
|
|
148
|
+
places: {
|
|
149
|
+
title: 'Every change, widest first',
|
|
150
|
+
why: 'By how many renders each showed up in. This is blast radius, not severity — one token can be the widest change in a build and the least interesting one.',
|
|
151
|
+
},
|
|
152
|
+
};
|
|
153
|
+
/**
|
|
154
|
+
* The four arrangements, as the picker draws them.
|
|
155
|
+
*
|
|
156
|
+
* Named for what the reader gets rather than for the field sorted on, and the
|
|
157
|
+
* default is `Story` because the docket's own argument is that a build is read as
|
|
158
|
+
* one — the unreachable first, the work you asked for next.
|
|
159
|
+
*/
|
|
160
|
+
export const ORDERS = [
|
|
161
|
+
{
|
|
162
|
+
order: 'story',
|
|
163
|
+
label: 'Story',
|
|
164
|
+
why: 'Banded by what it costs to be wrong: what the commit reaches nothing in, then what it declares, then what the diff cannot name.',
|
|
165
|
+
},
|
|
166
|
+
{ order: 'name', label: 'A–Z', why: FLAT.name.why },
|
|
167
|
+
{ order: 'size', label: 'Largest', why: FLAT.size.why },
|
|
168
|
+
{ order: 'places', label: 'Widest', why: FLAT.places.why },
|
|
169
|
+
];
|
|
170
|
+
/**
|
|
171
|
+
* The docket, arranged.
|
|
172
|
+
*
|
|
173
|
+
* Empty bands are dropped rather than drawn empty, because a heading over nothing
|
|
174
|
+
* is a heading a reader has to check. The three flat orders return one group, so
|
|
175
|
+
* every renderer downstream draws the same shape whichever arrangement it got.
|
|
176
|
+
*/
|
|
177
|
+
export function docketOf(build, origins, order) {
|
|
178
|
+
const sorted = [...origins].sort(BY[order]);
|
|
179
|
+
if (order !== 'story') {
|
|
180
|
+
// `why` stays on `FLAT` — it is the sort button's hover text, which a reader
|
|
181
|
+
// asks for, and not a paragraph the page prints at them.
|
|
182
|
+
const flat = FLAT[order];
|
|
183
|
+
return sorted.length === 0
|
|
184
|
+
? []
|
|
185
|
+
: [{ lane: 'reached', title: flat.title, changes: sorted.map((origin) => ({ origin })) }];
|
|
186
|
+
}
|
|
187
|
+
// Only the three rungs a commit is the top of are rooted. The rest are the
|
|
188
|
+
// findings this docket ranks *above* the commit — nothing reaches it, nothing
|
|
189
|
+
// explains it, the run read no diff — and a heading naming a file the reviewer
|
|
190
|
+
// edited is the wrong thing to file those under, because the point of each of
|
|
191
|
+
// them is that no file the reviewer edited accounts for it.
|
|
192
|
+
const rootable = sorted.filter((origin) => ROOTED.has(laneOf(origin)));
|
|
193
|
+
const { roots, loose } = rootsOf(rootable, build);
|
|
194
|
+
const rest = new Set(loose);
|
|
195
|
+
const banded = LANES.map((band) => ({
|
|
196
|
+
...band,
|
|
197
|
+
changes: sorted
|
|
198
|
+
.filter((origin) => laneOf(origin) === band.lane)
|
|
199
|
+
.filter((origin) => !ROOTED.has(band.lane) || rest.has(origin))
|
|
200
|
+
.map((origin) => ({ origin })),
|
|
201
|
+
}));
|
|
202
|
+
const causes = roots.map((root) => ({
|
|
203
|
+
lane: LANE_OF_ROOT[root.kind],
|
|
204
|
+
title: root.name,
|
|
205
|
+
changes: root.changes,
|
|
206
|
+
root,
|
|
207
|
+
}));
|
|
208
|
+
// The alarm bands keep the top, the commit's own causes come next, and what is
|
|
209
|
+
// settled or unreadable stays at the bottom. `ALARM` is the split point rather
|
|
210
|
+
// than a second list of names, so a lane added to `LANES` lands somewhere by
|
|
211
|
+
// construction instead of silently vanishing from the page.
|
|
212
|
+
const before = banded.filter((band) => ALARM.has(band.lane));
|
|
213
|
+
const after = banded.filter((band) => !ALARM.has(band.lane));
|
|
214
|
+
return [...before, ...causes, ...after].filter((band) => band.changes.length > 0);
|
|
215
|
+
}
|
|
216
|
+
/** The rungs a cause in the commit sits at the top of. */
|
|
217
|
+
const ROOTED = new Set(['reached', 'upstream', 'token']);
|
|
218
|
+
/** The bands that outrank the commit's own causes, because nothing in it explains them. */
|
|
219
|
+
const ALARM = new Set(['stranded', 'contradicted', 'unexplained']);
|
|
220
|
+
/** Which band a root is drawn in, so its colour still says how it was reached. */
|
|
221
|
+
const LANE_OF_ROOT = {
|
|
222
|
+
file: 'reached',
|
|
223
|
+
token: 'token',
|
|
224
|
+
component: 'upstream',
|
|
225
|
+
};
|
|
226
|
+
//# sourceMappingURL=order.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"order.js","sourceRoot":"","sources":["../../src/ui/order.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * What order the changes are read in — which is the docket's whole argument.\n *\n * A list of changes with no order is a list of screenshots with extra steps. The\n * first version of this page emitted them largest-first, which is the category's\n * ranking and is wrong for the same reason ranking regions by area is wrong: the\n * biggest number on a restyle belongs to whichever container reflowed furthest,\n * and a reviewer who works down that list spends their attention in inverse order\n * to where it is worth anything.\n *\n * So the docket is banded, and the bands are ranked by *what it costs to be\n * wrong about them* rather than by size or by count.\n *\n * 1. **Nothing reaches these.** The commit arrives at no component at all in some\n * render each of these moved in — the one finding here no other tool\n * produces, and the one worth waking somebody for.\n * 2. **Same props, two renderings.** Held at one commit with its inputs equal,\n * it did not settle. An answer, not an absence.\n * 3. **Nothing explains these.** The diff was read, the file graph walked and\n * the enclosure climbed, and none of the three arrive here.\n * 4. **The commit's own causes**, one band each — the file that was edited, or\n * the property that took a new value. [`cause.ts`](./cause.ts) resolves them.\n * 5. **Not in the diff.** No file in the commit declares it and the run recorded\n * no attribution to read instead.\n * 6. **No diff read.** The run carried none, so nothing above applies.\n * 7. **Already decided.** Off the queue, kept on the page.\n *\n * ## Band four used to be three bands, and they were the run's ladder\n *\n * *Edited*, *Owner edited* and *Token moved* were three headings, and they sorted\n * a build by how many hops the run needed to explain each change. Nobody has that\n * question. A reviewer edited three files and wants to know what each of the\n * three did, and under the ladder the answer to that was spread across three\n * headings with the file names in a hover title.\n *\n * So the heading is the cause. `Button` under `ui/button.tsx`; `CardFooter` under\n * `ProductCard.tsx`, because `ProductCard` draws it. Three files in, three\n * headings out — and the rung each change came in on is still on its row, where\n * it explains one component instead of naming a category.\n *\n * The bands above and below stay as they were, and that is the ranking's whole\n * point: a change with no cause in the commit must not be filed under one.\n *\n * Inside a band, **by component name**, ascending. Not by size: a reviewer works\n * a docket by looking for the name they recognise, and a list whose order changes\n * with the pixel count is a list they have to re-read from the top every build.\n *\n * ## A band is a label, not a paragraph\n *\n * Each of these used to ship with its definition printed underneath the heading,\n * every build, unchanged. The definition is the same on every build; the names\n * are not, and the names were the part left in the record. So the heading is a\n * claim of a few words and the evidence is on the row that has it —\n * [`sourceOf`](./grouping.ts) reads the owner, the file or the token off the\n * movement the run wrote, and the rail prints it.\n *\n * ## The other three orders are the reader's, and they are addresses\n *\n * Someone auditing a restyle wants the largest first; someone checking blast\n * radius wants the widest first; someone who knows the name wants the alphabet\n * and nothing else. Those are in the URL rather than in a component's state, so\n * the arrangement travels with the link.\n */\n\nimport type { BuildDetail } from '../review-types.js';\nimport { rootsOf, type Root, type Rooted } from './cause.js';\nimport type { Origin } from './grouping.js';\nimport type { Order } from './route.js';\n\n/** Which band a change is read in. */\nexport type Lane =\n | 'stranded'\n | 'reached'\n | 'contradicted'\n | 'unexplained'\n | 'upstream'\n | 'token'\n | 'unnamed'\n | 'unread'\n | 'decided';\n\n/** One band of the docket: a claim of a few words, and the rows it holds. */\nexport interface Group {\n readonly lane: Lane;\n readonly title: string;\n readonly changes: readonly Rooted[];\n /**\n * The cause in the commit this band *is*, when it is one.\n *\n * Present on the bands that came out of [`cause.ts`](./cause.ts), where the\n * heading is a path or a property rather than a claim about a rung. Absent on\n * the rest, and the absence is what a heading renderer switches on: a file is\n * drawn as a file, and `Nothing reaches these` is drawn as a sentence.\n */\n readonly root?: Root;\n}\n\n/**\n * The nine headings, each the shortest true claim about its rows.\n *\n * None of them explains itself, and the two that name a relation — `upstream`\n * and `token` — say *that* an owner or a token moved without saying which,\n * because that answer is per row and every row carries it.\n *\n * Nobody is addressed. `Edited` and `Owner edited` were *you edited these* and\n * *you edited their owner*, which puts a person in a heading whose subject is a\n * commit — and a reviewer reading somebody else's branch is not the *you* in it.\n * The pair is also the docket's whole distinction, so they are two words each and\n * they rhyme: a change the commit declares, and a change it arrived at.\n */\nconst LANES: readonly { readonly lane: Lane; readonly title: string }[] = [\n { lane: 'stranded', title: 'Nothing reaches these' },\n { lane: 'reached', title: 'Edited' },\n { lane: 'contradicted', title: 'Same props, two renderings' },\n { lane: 'unexplained', title: 'Nothing explains these' },\n { lane: 'upstream', title: 'Owner edited' },\n { lane: 'token', title: 'Token moved' },\n { lane: 'unnamed', title: 'Not in the diff' },\n { lane: 'unread', title: 'No diff read' },\n { lane: 'decided', title: 'Already decided' },\n];\n\n/**\n * Which band one change belongs to.\n *\n * `decided` first and unconditionally: a change nobody has to act on is not an\n * alarm however it arrived, and leaving it in the alarm band would put a settled\n * finding at the top of the page every build until the branch merges.\n */\nexport function laneOf(origin: Origin): Lane {\n if (origin.appearances.every(({ subject }) => subject.decision !== null)) return 'decided';\n if (origin.reached === undefined) return 'unread';\n if (origin.reached) return 'reached';\n if ((origin.stranded ?? []).length > 0) return 'stranded';\n\n // Everything below here is *the diff does not name it*, which was one band and\n // is four. The reach graph has said all it can — it climbs from a changed file\n // through its importers, so anything a changed file draws is off it by\n // construction — and what separates these is the run's own walk in the other\n // direction. A missing attribution stays in the last band rather than becoming\n // a rung of its own.\n switch (origin.cause) {\n case 'unexplained':\n return 'unexplained';\n // Not folded in with the one above, which it used to be. `unexplained` says\n // three records were asked and none of them arrive; `contradicted` says one\n // of them answered — the component disagreed with itself at fixed props —\n // and printing that under *none of them arrive here* is the page telling a\n // reviewer the run found nothing about a render it had pinned.\n case 'contradicted':\n return 'contradicted';\n case 'upstream':\n return 'upstream';\n case 'token':\n return 'token';\n default:\n return 'unnamed';\n }\n}\n\n/** How wide a change is: the renders it showed up in. */\nfunction places(origin: Origin): number {\n return origin.appearances.length;\n}\n\nconst BY: Record<Order, (left: Origin, right: Origin) => number> = {\n story: (left, right) => left.component.localeCompare(right.component),\n name: (left, right) => left.component.localeCompare(right.component),\n size: (left, right) => right.pixels - left.pixels || left.component.localeCompare(right.component),\n places: (left, right) =>\n places(right) - places(left) || left.component.localeCompare(right.component),\n};\n\nconst FLAT: Record<Exclude<Order, 'story'>, { readonly title: string; readonly why: string }> = {\n name: {\n title: 'Every change, by component',\n why: 'The alphabet, and nothing else. For when you know the name you are looking for.',\n },\n size: {\n title: 'Every change, largest first',\n why: 'By the pixels the component’s own regions moved. Useful for auditing a restyle, and misleading as a default: the biggest number on a page belongs to whatever reflowed furthest, not to what was edited.',\n },\n places: {\n title: 'Every change, widest first',\n why: 'By how many renders each showed up in. This is blast radius, not severity — one token can be the widest change in a build and the least interesting one.',\n },\n};\n\n/**\n * The four arrangements, as the picker draws them.\n *\n * Named for what the reader gets rather than for the field sorted on, and the\n * default is `Story` because the docket's own argument is that a build is read as\n * one — the unreachable first, the work you asked for next.\n */\nexport const ORDERS: readonly { readonly order: Order; readonly label: string; readonly why: string }[] =\n [\n {\n order: 'story',\n label: 'Story',\n why: 'Banded by what it costs to be wrong: what the commit reaches nothing in, then what it declares, then what the diff cannot name.',\n },\n { order: 'name', label: 'A–Z', why: FLAT.name.why },\n { order: 'size', label: 'Largest', why: FLAT.size.why },\n { order: 'places', label: 'Widest', why: FLAT.places.why },\n ];\n\n/**\n * The docket, arranged.\n *\n * Empty bands are dropped rather than drawn empty, because a heading over nothing\n * is a heading a reader has to check. The three flat orders return one group, so\n * every renderer downstream draws the same shape whichever arrangement it got.\n */\nexport function docketOf(\n build: BuildDetail,\n origins: readonly Origin[],\n order: Order,\n): readonly Group[] {\n const sorted = [...origins].sort(BY[order]);\n\n if (order !== 'story') {\n // `why` stays on `FLAT` — it is the sort button's hover text, which a reader\n // asks for, and not a paragraph the page prints at them.\n const flat = FLAT[order];\n return sorted.length === 0\n ? []\n : [{ lane: 'reached', title: flat.title, changes: sorted.map((origin) => ({ origin })) }];\n }\n\n // Only the three rungs a commit is the top of are rooted. The rest are the\n // findings this docket ranks *above* the commit — nothing reaches it, nothing\n // explains it, the run read no diff — and a heading naming a file the reviewer\n // edited is the wrong thing to file those under, because the point of each of\n // them is that no file the reviewer edited accounts for it.\n const rootable = sorted.filter((origin) => ROOTED.has(laneOf(origin)));\n const { roots, loose } = rootsOf(rootable, build);\n const rest = new Set(loose);\n\n const banded = LANES.map((band) => ({\n ...band,\n changes: sorted\n .filter((origin) => laneOf(origin) === band.lane)\n .filter((origin) => !ROOTED.has(band.lane) || rest.has(origin))\n .map((origin) => ({ origin })),\n }));\n\n const causes: Group[] = roots.map((root) => ({\n lane: LANE_OF_ROOT[root.kind],\n title: root.name,\n changes: root.changes,\n root,\n }));\n\n // The alarm bands keep the top, the commit's own causes come next, and what is\n // settled or unreadable stays at the bottom. `ALARM` is the split point rather\n // than a second list of names, so a lane added to `LANES` lands somewhere by\n // construction instead of silently vanishing from the page.\n const before = banded.filter((band) => ALARM.has(band.lane));\n const after = banded.filter((band) => !ALARM.has(band.lane));\n\n return [...before, ...causes, ...after].filter((band) => band.changes.length > 0);\n}\n\n/** The rungs a cause in the commit sits at the top of. */\nconst ROOTED: ReadonlySet<Lane> = new Set<Lane>(['reached', 'upstream', 'token']);\n\n/** The bands that outrank the commit's own causes, because nothing in it explains them. */\nconst ALARM: ReadonlySet<Lane> = new Set<Lane>(['stranded', 'contradicted', 'unexplained']);\n\n/** Which band a root is drawn in, so its colour still says how it was reached. */\nconst LANE_OF_ROOT: Record<Root['kind'], Lane> = {\n file: 'reached',\n token: 'token',\n component: 'upstream',\n};\n"]}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The docket: every change in the build, banded, in a rail beside the one open.
|
|
3
|
+
*
|
|
4
|
+
* This was a stack of cards. Each carried a component, a file path, a ratio and a
|
|
5
|
+
* fingerprint, and a reviewer arriving at a build of eleven changes met eleven of
|
|
6
|
+
* them stacked vertically — the second one below the fold, the divergence panel
|
|
7
|
+
* that would have told them they had already seen four of these nine thousand
|
|
8
|
+
* pixels further down. A page cannot be both the index and the entry, and when it
|
|
9
|
+
* tries the index wins the top of the screen and says nothing.
|
|
10
|
+
*
|
|
11
|
+
* So the list stopped carrying the story. A row says which change it is, how far
|
|
12
|
+
* it went, and the single thing about it a reviewer would want before opening it:
|
|
13
|
+
* that nothing reaches it, that build 5 showed it to them already, that it is
|
|
14
|
+
* done. Everything else moved to [`change.tsx`](./change.tsx), where there is room
|
|
15
|
+
* to say it beside the picture.
|
|
16
|
+
*
|
|
17
|
+
* ## The bands are the order
|
|
18
|
+
*
|
|
19
|
+
* A list of changes sorted by pixels answers *which of these is biggest*, and
|
|
20
|
+
* nobody has ever needed to know that. The first band is the changes nothing in
|
|
21
|
+
* the commit reaches — the ones that are either a real regression or a gap in the
|
|
22
|
+
* graph, and either way the ones worth the reviewer's first hour. Then the ones
|
|
23
|
+
* they asked for, then the ones the diff cannot name, then what is already
|
|
24
|
+
* decided. Within a band, alphabetical: a reviewer looking for `Button` should
|
|
25
|
+
* find it where `Button` goes, not wherever this run's pixel counts put it.
|
|
26
|
+
*
|
|
27
|
+
* The other orders exist because a band is a claim and a reviewer is allowed to
|
|
28
|
+
* disbelieve it. [`order.ts`](./order.ts) holds them all.
|
|
29
|
+
*/
|
|
30
|
+
import type { ReactElement } from 'react';
|
|
31
|
+
import type { BuildDetail } from '../review-types.js';
|
|
32
|
+
import type { Crossing } from './crossing.js';
|
|
33
|
+
import type { Order, Route } from './route.js';
|
|
34
|
+
export declare function OriginsPanel({ build, crossing, order, selected, go, }: {
|
|
35
|
+
readonly build: BuildDetail;
|
|
36
|
+
readonly crossing: Crossing;
|
|
37
|
+
readonly order: Order;
|
|
38
|
+
/** The change on the stage, so the rail can say which row it is. */
|
|
39
|
+
readonly selected?: string | undefined;
|
|
40
|
+
readonly go: (route: Route) => void;
|
|
41
|
+
}): ReactElement;
|
|
42
|
+
//# sourceMappingURL=origins.d.ts.map
|