@variance-authority/core 0.20.0 → 0.22.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 +17 -0
- package/dist/attribute/call-site.d.ts +1 -1
- package/dist/attribute/call-site.js +1 -1
- package/dist/attribute/call-site.js.map +1 -1
- package/dist/format/document.d.ts +5 -1
- package/dist/format/document.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/lfs-pointer.d.ts +13 -0
- package/dist/lfs-pointer.js +23 -0
- package/dist/lfs-pointer.js.map +1 -0
- package/dist/relate/narrow.js +3 -3
- package/dist/relate/narrow.js.map +1 -1
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
# @variance-authority/core
|
|
2
2
|
|
|
3
|
+
## 0.22.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 050e333: A git-LFS pointer is no longer read as the file it stands for. The text of a source file at a recorded commit, when git holds it as a pointer, is the content `git lfs smudge` gives, and the read fails with git-lfs's own words when it cannot give it. A baseline record that is a pointer is refused as one, by `find`, `describe` and `accept` alike, rather than called a truncated write or written over. `isLfsPointer` and `LFS_POINTER_HEAD_BYTES` now come from `@variance-authority/core`; `@variance-authority/store/lfs` no longer exports them.
|
|
8
|
+
|
|
9
|
+
### Patch Changes
|
|
10
|
+
|
|
11
|
+
- d2cc975: Seven docs say what their declaration does in the words it is asked for
|
|
12
|
+
|
|
13
|
+
`ask search` now shows these among its rows: `snapshotValue` writes a test case's value as a snapshot file, `locateSites` turns a stack frame into its source line, `BlockKind` names the construct behind each region kind, and the Playwright Locator recording in eyes is documented. The lead of `testSelectionProbes` now says it is the Vite plugin whose `transform` hook instruments each module. The tool-order doc of `@variance-authority/help` sits on the tool list rather than on the wrapper that dates each answer.
|
|
14
|
+
- 659925c: An accepted baseline is kept as a lossless WebP, in about a quarter of the bytes of the PNG it was captured as. It is encoded once, on the machine that accepts, by `keepImage` in `@variance-authority/png-sharp` — sharp's libwebp at effort 2 — which decodes its output back and throws unless every pixel with any opacity matches in all four channels and every fully transparent pixel stays fully transparent. The colour under a fully transparent pixel is not kept; no comparison sees it. `@variance-authority/store/keep` exports `keptRaster`, which every accept calls before `put` and which loads sharp only then, so a run that accepts nothing never loads it, and an accept where sharp does not load fails and names why. A durable store's `put` encodes nothing: it refuses any image but a lossless WebP, naming what it was given, so a remote store receives the WebP and runs no codec. A read checks the image's header against the sidecar and refuses a `.webp` holding a PNG or a lossy WebP, or an `image` format it does not know, as damage. Captures and the render cache stay PNG; a report's before image is the baseline as it was kept, so `.before.webp` beside a WebP baseline, attached as `image/webp`. The sidecar records `"image": "webp"`; a baseline kept as a `.png` before has no such field, stays readable, and its next accept replaces it. A WebP holds at most 16,383 pixels on a side, so an accept refuses a larger image and writes nothing. A `sharp` upgrade that brings a new libwebp can write different bytes for the same pixels, so a baseline rewritten after it shows in git as a changed file whose pixels did not. `createLfsStore` tracks `*-*.webp` by default and leaves an existing `*-*.png` entry in place; a `pattern` you set yourself must end in `.webp` or `*`, or the store refuses to open. `doctor --baselines` and the carried pathspecs count both extensions. `@variance-authority/png` reads WebP without an addon through libwebp's WebAssembly decoder and adds `decodePortable`, `imageFormat`, `refuseUnkept`, `isLosslessWebp`, `refuseOversize` and `KEEP_LIMIT`; its decoders read either format by the bytes' header, `imageSize` replaces `pngSize`, `diffImage` is now async — it resolves to its PNG, since either side may be a WebP — and takes an optional decoder, `changeImages` takes decoded images rather than bytes, and `@variance-authority/png/header` reads a header without loading a codec. `@variance-authority/store` exports `KEPT`, `KeptAs` and `isKeptImage`, and a path template that ends in `.webp` is refused like one ending in `.png`. The tribunal worker labels each image by the format its bytes declare.
|
|
15
|
+
|
|
16
|
+
## 0.21.0
|
|
17
|
+
|
|
18
|
+
Lockstep release — nothing in this package changed. Every `@variance-authority/*` package shares one version.
|
|
19
|
+
|
|
3
20
|
## 0.20.0
|
|
4
21
|
|
|
5
22
|
Lockstep release — nothing in this package changed. Every `@variance-authority/*` package shares one version.
|
|
@@ -89,7 +89,7 @@ export interface LocatableSite {
|
|
|
89
89
|
readonly source?: SourceLocation;
|
|
90
90
|
}
|
|
91
91
|
/**
|
|
92
|
-
* The line
|
|
92
|
+
* The source line of each site a report is about to name, from its stack frame.
|
|
93
93
|
*
|
|
94
94
|
* This is the demand side of the zero-install path. The page reads frames off
|
|
95
95
|
* every fiber because reading them is nearly free; turning one into a file is
|
|
@@ -103,7 +103,7 @@ export async function locateProvenance(provenance, resolver) {
|
|
|
103
103
|
return source === null ? carried : { ...carried, source };
|
|
104
104
|
}
|
|
105
105
|
/**
|
|
106
|
-
* The line
|
|
106
|
+
* The source line of each site a report is about to name, from its stack frame.
|
|
107
107
|
*
|
|
108
108
|
* This is the demand side of the zero-install path. The page reads frames off
|
|
109
109
|
* every fiber because reading them is nearly free; turning one into a file is
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"call-site.js","sourceRoot":"","sources":["../../src/attribute/call-site.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n relativizeSource,\n type Provenance,\n type SourceLocation,\n type StackFrame,\n} from '../format/provenance.js';\nimport type { NodePath, SemanticNode, SemanticSnapshot } from '../format/snapshot.js';\nimport {\n inlineSourceMapOf,\n originalPositionFor,\n parseSourceMap,\n sourceMappingUrlOf,\n type SourceMap,\n} from './source-map.js';\nimport { isVendorPath, servedPath, writerLocationOf } from './stack.js';\n\n/**\n * Spending the frames a page read, to get the file a reviewer opens.\n *\n * This is the half of the zero-install path that cannot happen in the page. A\n * frame names the module the browser was *served*, and turning that into a line\n * in the repository means fetching that module and reading the source map the\n * build already emitted beside it. Fetching is not something a page agent should\n * do — it runs inside somebody else's application, on a budget measured against\n * their render loop — and it is exactly what the collector is for.\n *\n * **The economics are what make this worth doing rather than clever.** Measured\n * on a 4211-node document: every fiber carried a stack, and between them they\n * held **14 distinct call sites**. A hundred-row table writes two thousand cells\n * from one line of JSX. So the work is per *call site*, not per node, and the\n * cache below is not an optimization but the thing that makes the cost bounded —\n * a handful of module fetches for a whole page.\n *\n * **And it is asked on a signal rather than on every capture.** Fourteen sites is\n * a bounded cost, not a free one, and a run that settles every subject on its\n * document digest has nobody to hand a location to: no region was drawn, no\n * finding was raised, and the fetches would answer a question nothing asked. So\n * frames ride the snapshot — they are provenance, and no hash projects\n * provenance — and {@link locateSites} spends them for the handful of nodes a\n * report is about to name. A page whose only change is one button resolves one\n * call site, not fourteen; a page that did not change resolves none.\n *\n * `fetchModule` is injected because `core` may not assume a network (ADR-0013),\n * and because the right way to fetch differs by caller: a browser-driving\n * collector should fetch from the page's own context, where the origin, the\n * cookies and the dev server's module graph are already correct.\n */\n\n/** Fetch a module's text, or answer that it cannot be had. Never throws. */\nexport type FetchModule = (url: string) => Promise<string | null>;\n\nexport interface CallSiteOptions {\n /**\n * The repository root, for a location that arrives holding an absolute path.\n *\n * Two kinds of frame reach {@link CallSiteResolver.locate} and only one of\n * them is relative already. A frame that needed a map is answered in the map's\n * own terms, which are the build's and so the repository's. A frame that\n * needed none — a Node stack, or the synthetic frame React manufactures for a\n * server component — is answered in the terms of the disk it was captured on,\n * which means somebody's home directory in the middle of a path that is\n * otherwise a fact about the project.\n *\n * Given, the two shapes come out as one. Absent, a location is returned as it\n * arrived, because guessing at a root is how a path stops resolving for the\n * reader who opens it.\n */\n readonly sourceRoot?: string;\n}\n\nexport interface CallSiteResolver {\n /** The location that wrote an element, from the frames its fiber carried. */\n locate(frames: readonly StackFrame[]): Promise<SourceLocation | null>;\n /** How many modules were fetched, and how many call sites they answered. */\n readonly stats: CallSiteStats;\n}\n\nexport interface CallSiteStats {\n /** Distinct modules fetched. The number that costs anything. */\n readonly modules: number;\n /** Distinct call sites asked about — the ratio to `modules` is the point. */\n readonly sites: number;\n /** Call sites that resolved to a file. */\n readonly located: number;\n}\n\nexport function createCallSiteResolver(\n fetchModule: FetchModule,\n options: CallSiteOptions = {},\n): CallSiteResolver {\n /** Module URL → its map, `null` once we know it has none. Also the in-flight guard. */\n const maps = new Map<string, Promise<readonly SourceMap[] | null>>();\n /** `url:line:column` → the answer, so a page's 4211 nodes cost 14 resolutions. */\n const sites = new Map<string, Promise<SourceLocation | null>>();\n\n const stats = { modules: 0, sites: 0, located: 0 };\n\n async function mapsFor(url: string): Promise<readonly SourceMap[] | null> {\n const cached = maps.get(url);\n if (cached !== undefined) return cached;\n\n const pending = (async () => {\n stats.modules += 1;\n\n const code = await fetchModule(url);\n if (code === null) return null;\n\n const annotation = sourceMappingUrlOf(code);\n if (annotation === null) return null;\n\n const inline = inlineSourceMapOf(annotation);\n if (inline !== null) return parseSourceMap(inline);\n\n // A sibling file, which is what a production bundler writes. Resolved\n // against the module rather than the page: `//# sourceMappingURL=out.js.map`\n // means \"beside this module\", and a page several directories deep would\n // otherwise ask for it in the wrong place.\n const sibling = absolute(annotation, url);\n const text = sibling === null ? null : await fetchModule(sibling);\n return text === null ? null : parseSourceMap(text);\n })();\n\n maps.set(url, pending);\n return pending;\n }\n\n async function locateFrame(frame: StackFrame): Promise<SourceLocation | null> {\n const parsed = await mapsFor(frame.url);\n\n // No map is not failure, and it is not an answer either. A Node test runner\n // applies source maps to `Error.stack` itself, so its frame arrives already\n // original and there is nothing left to add. A served module carries no such\n // guarantee: a position in the text a server sent is a position in the\n // repository only if a map says so, so `writerLocationOf` refuses it.\n if (parsed === null || parsed.length === 0) return null;\n\n const original = originalPositionFor(parsed, frame.line, frame.column);\n if (original === null) return null;\n\n return {\n file: sourcePath(original.source, frame.url),\n line: original.line,\n column: original.column,\n };\n }\n\n async function locate(frames: readonly StackFrame[]): Promise<SourceLocation | null> {\n for (const frame of frames) {\n if (isVendorPath(frame.url)) continue;\n\n const key = `${frame.url}:${frame.line}:${frame.column}`;\n let answer = sites.get(key);\n\n if (answer === undefined) {\n stats.sites += 1;\n answer = locateFrame(frame);\n sites.set(key, answer);\n }\n\n const located = await answer;\n\n // The frame's own coordinates, for a frame that names a file on disk.\n // Delegated so there is one statement of the choosing rule rather than two.\n const chosen =\n located === null\n ? writerLocationOf([frame], () => null)\n : isVendorPath(located.file)\n ? null\n : located;\n\n if (chosen !== null) {\n stats.located += 1;\n return options.sourceRoot === undefined\n ? chosen\n : relativizeSource(chosen, options.sourceRoot);\n }\n }\n\n return null;\n }\n\n return { locate, stats: stats as CallSiteStats };\n}\n\n/**\n * Provenance with its frames spent: `source` filled in, `stack` gone.\n *\n * Returned unchanged when there is nothing to do — which is the common case once\n * a project installs `jsx-source`, and the case a production build is always in.\n * Never throws: a report that names a location for most of its nodes is the\n * normal outcome, and one node's unreachable module must not take the run down.\n */\nexport async function locateProvenance(\n provenance: Provenance,\n resolver: CallSiteResolver,\n): Promise<Provenance> {\n const { stack, ...carried } = provenance;\n if (stack === undefined || stack.length === 0) return provenance;\n\n let source: SourceLocation | null = null;\n try {\n source = await resolver.locate(stack);\n } catch {\n source = null;\n }\n\n return source === null ? carried : { ...carried, source };\n}\n\n/**\n * Anything a report points at: it names a node, and may already know its line.\n *\n * An `AttributedRegion` and a `Finding` are both this, arrived at from opposite\n * directions — one from a mask, one from an inspection — and both are the *few*.\n * That is the whole reason this shape is worth having rather than two functions:\n * what makes resolution affordable is being asked about a handful of nodes, and\n * a handful is what a region list and a finding list are.\n */\nexport interface LocatableSite {\n readonly path?: NodePath;\n readonly source?: SourceLocation;\n}\n\n/**\n * The line for each site a report is about to name, and for nothing else.\n *\n * This is the demand side of the zero-install path. The page reads frames off\n * every fiber because reading them is nearly free; turning one into a file is\n * not, and this is the only place that spends it.\n *\n * Three ways a site costs nothing, all of them the common case:\n *\n * - **It has no `path`.** A region no box contains names no node.\n * - **It already has a `source`.** React ≤18 and `@variance-authority/jsx-source`\n * both record the location outright, so there is nothing to resolve. Only\n * React 19, which throws its location away and captures an `Error` instead,\n * reaches the map.\n * - **Its node carried no frames.** A production build captures nothing.\n *\n * And a subject that settled on its document digest never calls this at all,\n * which is the point: no region, no finding, no fetch.\n *\n * Sites are resolved concurrently. The resolver's own cache collapses them onto\n * the modules they share, so two regions in one component cost one fetch.\n */\nexport async function locateSites<T extends LocatableSite>(\n sites: readonly T[],\n snapshot: SemanticSnapshot,\n resolver: CallSiteResolver,\n): Promise<readonly T[]> {\n const wanted = sites.some((site) => site.source === undefined && site.path !== undefined);\n if (!wanted) return sites;\n\n // Built once and only when something asked. A path lookup on a tree is a walk,\n // and doing it per site would make a fifty-region page walk it fifty times.\n const nodes = index(snapshot.root);\n\n const located = await Promise.all(\n sites.map(async (site) => {\n if (site.source !== undefined || site.path === undefined) return site;\n\n const provenance = nodes.get(site.path)?.provenance;\n if (provenance?.stack === undefined) return site;\n\n const spent = await locateProvenance(provenance, resolver);\n return spent.source === undefined ? site : { ...site, source: spent.source };\n }),\n );\n\n return located.every((site, at) => site === sites[at]) ? sites : located;\n}\n\nfunction index(root: SemanticNode): ReadonlyMap<NodePath, SemanticNode> {\n const nodes = new Map<NodePath, SemanticNode>();\n\n const walk = (node: SemanticNode): void => {\n nodes.set(node.path, node);\n for (const child of node.children) walk(child);\n };\n walk(root);\n\n return nodes;\n}\n\n/**\n * A map's `sources` entry as a path, resolved against the module it describes.\n *\n * Maps state sources relatively — Vite writes `probe.jsx` for `/src/probe.jsx` —\n * so the entry alone is ambiguous between two directories. Resolved against the\n * module first, then stripped of the origin it resolved against, which is a fact\n * about the machine that served it rather than about the code.\n */\nfunction sourcePath(source: string, moduleUrl: string): string {\n return servedPath(absolute(source, moduleUrl) ?? source);\n}\n\nfunction absolute(url: string, base: string): string | null {\n return parseUrl(url, base)?.href ?? null;\n}\n\n/**\n * The one host global this file needs, asked for rather than assumed.\n *\n * `core` types no host library (ADR-0001) and joining URLs by hand is a bug farm\n * — `..` past the root, a query string on the base, a protocol-relative\n * specifier. `URL` is present in every Node release this project supports and in\n * every browser, so the honest thing is to use it and to degrade if it is not\n * there, exactly as `inlineSourceMapOf` does for `atob`.\n */\ninterface ParsedUrl {\n readonly href: string;\n readonly pathname: string;\n}\n\nconst URL_OF = (globalThis as { URL?: new (url: string, base?: string) => ParsedUrl }).URL;\n\nfunction parseUrl(url: string, base?: string): ParsedUrl | null {\n if (URL_OF === undefined) return null;\n\n try {\n return new URL_OF(url, base);\n } catch {\n return null;\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"call-site.js","sourceRoot":"","sources":["../../src/attribute/call-site.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n relativizeSource,\n type Provenance,\n type SourceLocation,\n type StackFrame,\n} from '../format/provenance.js';\nimport type { NodePath, SemanticNode, SemanticSnapshot } from '../format/snapshot.js';\nimport {\n inlineSourceMapOf,\n originalPositionFor,\n parseSourceMap,\n sourceMappingUrlOf,\n type SourceMap,\n} from './source-map.js';\nimport { isVendorPath, servedPath, writerLocationOf } from './stack.js';\n\n/**\n * Spending the frames a page read, to get the file a reviewer opens.\n *\n * This is the half of the zero-install path that cannot happen in the page. A\n * frame names the module the browser was *served*, and turning that into a line\n * in the repository means fetching that module and reading the source map the\n * build already emitted beside it. Fetching is not something a page agent should\n * do — it runs inside somebody else's application, on a budget measured against\n * their render loop — and it is exactly what the collector is for.\n *\n * **The economics are what make this worth doing rather than clever.** Measured\n * on a 4211-node document: every fiber carried a stack, and between them they\n * held **14 distinct call sites**. A hundred-row table writes two thousand cells\n * from one line of JSX. So the work is per *call site*, not per node, and the\n * cache below is not an optimization but the thing that makes the cost bounded —\n * a handful of module fetches for a whole page.\n *\n * **And it is asked on a signal rather than on every capture.** Fourteen sites is\n * a bounded cost, not a free one, and a run that settles every subject on its\n * document digest has nobody to hand a location to: no region was drawn, no\n * finding was raised, and the fetches would answer a question nothing asked. So\n * frames ride the snapshot — they are provenance, and no hash projects\n * provenance — and {@link locateSites} spends them for the handful of nodes a\n * report is about to name. A page whose only change is one button resolves one\n * call site, not fourteen; a page that did not change resolves none.\n *\n * `fetchModule` is injected because `core` may not assume a network (ADR-0013),\n * and because the right way to fetch differs by caller: a browser-driving\n * collector should fetch from the page's own context, where the origin, the\n * cookies and the dev server's module graph are already correct.\n */\n\n/** Fetch a module's text, or answer that it cannot be had. Never throws. */\nexport type FetchModule = (url: string) => Promise<string | null>;\n\nexport interface CallSiteOptions {\n /**\n * The repository root, for a location that arrives holding an absolute path.\n *\n * Two kinds of frame reach {@link CallSiteResolver.locate} and only one of\n * them is relative already. A frame that needed a map is answered in the map's\n * own terms, which are the build's and so the repository's. A frame that\n * needed none — a Node stack, or the synthetic frame React manufactures for a\n * server component — is answered in the terms of the disk it was captured on,\n * which means somebody's home directory in the middle of a path that is\n * otherwise a fact about the project.\n *\n * Given, the two shapes come out as one. Absent, a location is returned as it\n * arrived, because guessing at a root is how a path stops resolving for the\n * reader who opens it.\n */\n readonly sourceRoot?: string;\n}\n\nexport interface CallSiteResolver {\n /** The location that wrote an element, from the frames its fiber carried. */\n locate(frames: readonly StackFrame[]): Promise<SourceLocation | null>;\n /** How many modules were fetched, and how many call sites they answered. */\n readonly stats: CallSiteStats;\n}\n\nexport interface CallSiteStats {\n /** Distinct modules fetched. The number that costs anything. */\n readonly modules: number;\n /** Distinct call sites asked about — the ratio to `modules` is the point. */\n readonly sites: number;\n /** Call sites that resolved to a file. */\n readonly located: number;\n}\n\nexport function createCallSiteResolver(\n fetchModule: FetchModule,\n options: CallSiteOptions = {},\n): CallSiteResolver {\n /** Module URL → its map, `null` once we know it has none. Also the in-flight guard. */\n const maps = new Map<string, Promise<readonly SourceMap[] | null>>();\n /** `url:line:column` → the answer, so a page's 4211 nodes cost 14 resolutions. */\n const sites = new Map<string, Promise<SourceLocation | null>>();\n\n const stats = { modules: 0, sites: 0, located: 0 };\n\n async function mapsFor(url: string): Promise<readonly SourceMap[] | null> {\n const cached = maps.get(url);\n if (cached !== undefined) return cached;\n\n const pending = (async () => {\n stats.modules += 1;\n\n const code = await fetchModule(url);\n if (code === null) return null;\n\n const annotation = sourceMappingUrlOf(code);\n if (annotation === null) return null;\n\n const inline = inlineSourceMapOf(annotation);\n if (inline !== null) return parseSourceMap(inline);\n\n // A sibling file, which is what a production bundler writes. Resolved\n // against the module rather than the page: `//# sourceMappingURL=out.js.map`\n // means \"beside this module\", and a page several directories deep would\n // otherwise ask for it in the wrong place.\n const sibling = absolute(annotation, url);\n const text = sibling === null ? null : await fetchModule(sibling);\n return text === null ? null : parseSourceMap(text);\n })();\n\n maps.set(url, pending);\n return pending;\n }\n\n async function locateFrame(frame: StackFrame): Promise<SourceLocation | null> {\n const parsed = await mapsFor(frame.url);\n\n // No map is not failure, and it is not an answer either. A Node test runner\n // applies source maps to `Error.stack` itself, so its frame arrives already\n // original and there is nothing left to add. A served module carries no such\n // guarantee: a position in the text a server sent is a position in the\n // repository only if a map says so, so `writerLocationOf` refuses it.\n if (parsed === null || parsed.length === 0) return null;\n\n const original = originalPositionFor(parsed, frame.line, frame.column);\n if (original === null) return null;\n\n return {\n file: sourcePath(original.source, frame.url),\n line: original.line,\n column: original.column,\n };\n }\n\n async function locate(frames: readonly StackFrame[]): Promise<SourceLocation | null> {\n for (const frame of frames) {\n if (isVendorPath(frame.url)) continue;\n\n const key = `${frame.url}:${frame.line}:${frame.column}`;\n let answer = sites.get(key);\n\n if (answer === undefined) {\n stats.sites += 1;\n answer = locateFrame(frame);\n sites.set(key, answer);\n }\n\n const located = await answer;\n\n // The frame's own coordinates, for a frame that names a file on disk.\n // Delegated so there is one statement of the choosing rule rather than two.\n const chosen =\n located === null\n ? writerLocationOf([frame], () => null)\n : isVendorPath(located.file)\n ? null\n : located;\n\n if (chosen !== null) {\n stats.located += 1;\n return options.sourceRoot === undefined\n ? chosen\n : relativizeSource(chosen, options.sourceRoot);\n }\n }\n\n return null;\n }\n\n return { locate, stats: stats as CallSiteStats };\n}\n\n/**\n * Provenance with its frames spent: `source` filled in, `stack` gone.\n *\n * Returned unchanged when there is nothing to do — which is the common case once\n * a project installs `jsx-source`, and the case a production build is always in.\n * Never throws: a report that names a location for most of its nodes is the\n * normal outcome, and one node's unreachable module must not take the run down.\n */\nexport async function locateProvenance(\n provenance: Provenance,\n resolver: CallSiteResolver,\n): Promise<Provenance> {\n const { stack, ...carried } = provenance;\n if (stack === undefined || stack.length === 0) return provenance;\n\n let source: SourceLocation | null = null;\n try {\n source = await resolver.locate(stack);\n } catch {\n source = null;\n }\n\n return source === null ? carried : { ...carried, source };\n}\n\n/**\n * Anything a report points at: it names a node, and may already know its line.\n *\n * An `AttributedRegion` and a `Finding` are both this, arrived at from opposite\n * directions — one from a mask, one from an inspection — and both are the *few*.\n * That is the whole reason this shape is worth having rather than two functions:\n * what makes resolution affordable is being asked about a handful of nodes, and\n * a handful is what a region list and a finding list are.\n */\nexport interface LocatableSite {\n readonly path?: NodePath;\n readonly source?: SourceLocation;\n}\n\n/**\n * The source line of each site a report is about to name, from its stack frame.\n *\n * This is the demand side of the zero-install path. The page reads frames off\n * every fiber because reading them is nearly free; turning one into a file is\n * not, and this is the only place that spends it.\n *\n * Three ways a site costs nothing, all of them the common case:\n *\n * - **It has no `path`.** A region no box contains names no node.\n * - **It already has a `source`.** React ≤18 and `@variance-authority/jsx-source`\n * both record the location outright, so there is nothing to resolve. Only\n * React 19, which throws its location away and captures an `Error` instead,\n * reaches the map.\n * - **Its node carried no frames.** A production build captures nothing.\n *\n * And a subject that settled on its document digest never calls this at all,\n * which is the point: no region, no finding, no fetch.\n *\n * Sites are resolved concurrently. The resolver's own cache collapses them onto\n * the modules they share, so two regions in one component cost one fetch.\n */\nexport async function locateSites<T extends LocatableSite>(\n sites: readonly T[],\n snapshot: SemanticSnapshot,\n resolver: CallSiteResolver,\n): Promise<readonly T[]> {\n const wanted = sites.some((site) => site.source === undefined && site.path !== undefined);\n if (!wanted) return sites;\n\n // Built once and only when something asked. A path lookup on a tree is a walk,\n // and doing it per site would make a fifty-region page walk it fifty times.\n const nodes = index(snapshot.root);\n\n const located = await Promise.all(\n sites.map(async (site) => {\n if (site.source !== undefined || site.path === undefined) return site;\n\n const provenance = nodes.get(site.path)?.provenance;\n if (provenance?.stack === undefined) return site;\n\n const spent = await locateProvenance(provenance, resolver);\n return spent.source === undefined ? site : { ...site, source: spent.source };\n }),\n );\n\n return located.every((site, at) => site === sites[at]) ? sites : located;\n}\n\nfunction index(root: SemanticNode): ReadonlyMap<NodePath, SemanticNode> {\n const nodes = new Map<NodePath, SemanticNode>();\n\n const walk = (node: SemanticNode): void => {\n nodes.set(node.path, node);\n for (const child of node.children) walk(child);\n };\n walk(root);\n\n return nodes;\n}\n\n/**\n * A map's `sources` entry as a path, resolved against the module it describes.\n *\n * Maps state sources relatively — Vite writes `probe.jsx` for `/src/probe.jsx` —\n * so the entry alone is ambiguous between two directories. Resolved against the\n * module first, then stripped of the origin it resolved against, which is a fact\n * about the machine that served it rather than about the code.\n */\nfunction sourcePath(source: string, moduleUrl: string): string {\n return servedPath(absolute(source, moduleUrl) ?? source);\n}\n\nfunction absolute(url: string, base: string): string | null {\n return parseUrl(url, base)?.href ?? null;\n}\n\n/**\n * The one host global this file needs, asked for rather than assumed.\n *\n * `core` types no host library (ADR-0001) and joining URLs by hand is a bug farm\n * — `..` past the root, a query string on the base, a protocol-relative\n * specifier. `URL` is present in every Node release this project supports and in\n * every browser, so the honest thing is to use it and to degrade if it is not\n * there, exactly as `inlineSourceMapOf` does for `atob`.\n */\ninterface ParsedUrl {\n readonly href: string;\n readonly pathname: string;\n}\n\nconst URL_OF = (globalThis as { URL?: new (url: string, base?: string) => ParsedUrl }).URL;\n\nfunction parseUrl(url: string, base?: string): ParsedUrl | null {\n if (URL_OF === undefined) return null;\n\n try {\n return new URL_OF(url, base);\n } catch {\n return null;\n }\n}\n"]}
|
|
@@ -245,7 +245,11 @@ export interface Raster {
|
|
|
245
245
|
*/
|
|
246
246
|
readonly width?: number;
|
|
247
247
|
readonly height?: number;
|
|
248
|
-
/**
|
|
248
|
+
/**
|
|
249
|
+
* The image, base64, in the format its own header declares: a capture is a
|
|
250
|
+
* PNG, and a kept baseline is the lossless WebP it was kept as, or a PNG kept
|
|
251
|
+
* before. Absent when the subject occupies no pixels.
|
|
252
|
+
*/
|
|
249
253
|
readonly bytes?: string;
|
|
250
254
|
/** Fonts the document declared that the renderer did not have. */
|
|
251
255
|
readonly missingFonts: readonly string[];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"document.js","sourceRoot":"","sources":["../../src/format/document.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Diagnostic, SubjectRef } from './capture.js';\nimport type { Viewport } from './environment.js';\nimport { digestValue, type Digest } from './hash.js';\nimport { pageOrigin, withoutOrigin } from './origin.js';\nimport type { PackedSnapshot } from './packed.js';\nimport type { ComponentHash } from './snapshot.js';\n\n/**\n * The render document: what gets *sent* somewhere to become an image.\n *\n * This is the acquisition phase's output and the only thing the rendering side\n * ever sees. It is plain serializable data for the same reason `RawCapture` is\n * (ADR-0006): the renderer may be a page in this process, a browser on this\n * machine, or a pinned container two networks away, and no code upstream of it\n * may assume which.\n *\n * Two routes produce one of these, and the point of the type is that they are\n * interchangeable:\n *\n * 1. **jsdom → document → render elsewhere.** jsdom cannot rasterize. It can\n * describe exactly what to rasterize, which is the sub-renderer split ADR-0002\n * asks for: the cheap tier decides almost everything, and the residue is\n * handed to something that owns a GPU.\n * 2. **playwright → document → render on a server.** The same payload, acquired\n * from a real engine. Used when the *deciding* machine and the *pinned*\n * machine are different, which is the case every CI setup actually has.\n *\n * A browser may instead capture a `Raster` in place. That is the sibling branch\n * of `CaptureMaterial`, not a third route that somehow produces this type.\n *\n * ## Why this is small\n *\n * `css` holds the *applicable* rules only — the pruning of ADR-0003, which on\n * `@variance-authority/dom`'s accretion fixture takes 1010 rules to 1. That\n * number is a cost claim as much as a\n * correctness one: shipping a document over a network hop is only sensible if the\n * document is not the entire design system plus Storybook's chrome.\n */\nexport interface RenderDocument {\n readonly documentVersion: 1;\n readonly subject: SubjectRef;\n\n /**\n * The subject subtree, serialized.\n *\n * Serialized rather than referenced by URL: a URL makes the renderer\n * responsible for reproducing the application's state, which it cannot do and\n * should not try. What is sent is the markup that existed at the moment of\n * capture, which is the only definition of \"this render\" that survives a hop.\n */\n readonly html: string;\n\n /**\n * The ancestor context the subject was rendered inside.\n *\n * Not decoration. Pruning keeps rules like `html.dark .card` and\n * `.app .list > li`, whose left-hand side lives *above* the subject — render\n * the subtree bare and those rules match nothing, so the image is missing\n * exactly the styling the collector went to the trouble of proving applies.\n *\n * Reproduced as empty open tags rather than by re-rendering the application:\n * what a selector needs is a chain of elements with the right tags, ids,\n * classes, and attributes, and nothing above the subject contributes anything\n * else to it that {@link RenderDocument.inherited} does not already carry.\n */\n readonly frame: RenderFrame;\n\n /**\n * Applicable stylesheet text, in cascade order.\n *\n * Order is load-bearing and is the caller's to preserve — the cascade breaks\n * specificity ties by document order, so a set that arrives shuffled paints\n * differently while hashing the same if this were a set rather than a list.\n */\n readonly css: readonly string[];\n\n readonly viewport: Viewport;\n\n /**\n * Inherited values in force at the subject root, applied to its wrapper.\n *\n * The same requirement as `RawCapture.inheritedSeed` and for the same reason:\n * pruning drops rules on ancestors outside the subtree, and a font-size\n * inherited from `<html>` is not optional decoration — dropping it changes\n * every metric in the image.\n */\n readonly inherited: Readonly<Record<string, string>>;\n\n /**\n * Fonts the acquiring side observed as loaded.\n *\n * Carried, not resolved. A renderer that lacks one of these will substitute\n * and produce different metrics, and that has to be *detectable* rather than\n * silently absorbed — see {@link RenderIdentity}.\n */\n readonly fonts: readonly string[];\n\n /** External references, url → content hash. Present so a swap is visible. */\n readonly assets?: Readonly<Record<string, string>>;\n\n /**\n * Base against which relative resource references were resolved.\n *\n * Preserves relative-reference resolution for either document kind. Resource\n * closure is denoted by `resources`, not by the presence of this field.\n */\n readonly baseUrl?: string;\n\n /**\n * Immutable responses needed to paint this document, keyed by absolute URL.\n *\n * Presence — including an empty object — means the document is\n * resource-closed: a renderer must not consult the network for a missing URL.\n * Absence preserves the older network-capable document contract and must not\n * be described as portable to another machine.\n */\n readonly resources?: Readonly<Record<string, RenderResource>>;\n\n readonly diagnostics: readonly Diagnostic[];\n}\n\nexport interface RenderResource {\n readonly contentType: string;\n /** Response bytes, base64. */\n readonly bytes: string;\n readonly digest: Digest;\n\n /**\n * The status a renderer answers this URL with. Absent means 200.\n *\n * A subject is allowed to reference something that is not there: a fallback\n * test does it on purpose, and the browser it was acquired in painted the\n * broken state rather than the image. Recording the failure as a failure is\n * what lets a renderer reproduce that state without asking the network.\n */\n readonly status?: number;\n}\n\nexport interface RenderFrame {\n /** Attributes on `<html>`. `class=\"dark\"` here decides half a design system. */\n readonly html: Readonly<Record<string, string>>;\n readonly body: Readonly<Record<string, string>>;\n /** Elements between `<body>` and the subject root, outermost first. */\n readonly ancestors: readonly FrameElement[];\n\n /**\n * Used `width` of the subject's parent, in CSS pixels and in the parent's own\n * `box-sizing`, when layout was observable. Written back on the same box, it\n * gives the content box a percentage on the subject resolves against.\n *\n * A subject declaring `width: 100%` is a different size in a 1024px page and\n * inside a 600px panel, and nothing else in this document says which it was.\n * Absent under a profile with no layout engine, in which case the render falls\n * back to the viewport and the difference, if any, is real rather than hidden.\n */\n readonly containerWidth?: number;\n}\n\nexport interface FrameElement {\n readonly tag: string;\n readonly attributes: Readonly<Record<string, string>>;\n}\n\n/**\n * Content address of a document: *what is to be painted*.\n *\n * This is the deferral lever. Rendering is the expensive phase, and a document\n * whose digest has been rendered before under the same identity has an image\n * already — so the cheapest render is the one that is skipped. Nothing about the\n * branch, the commit, or the file path enters this (Principle 4), so moving a\n * story between files does not cost a re-render.\n *\n * Deliberately covers `viewport` in full, `deviceScaleFactor` included: unlike\n * the semantic key (ADR-0010), a raster genuinely differs at 2x and the whole\n * purpose of this digest is to address the raster.\n *\n * **Limit, stated rather than discovered.** Declaration values arrive serialized\n * by the acquiring engine's CSSOM — jsdom writes `rgb(18, 52, 86)` where the\n * author wrote `#123456` — and this phase deliberately does not canonicalize\n * them, because a collector that normalizes is a second ruleset versioned by\n * nothing (ADR-0001). So two engines can address the same page differently. That\n * makes a render cache *per acquiring engine*, which is a missed cache hit and\n * never a wrong image.\n */\nexport function documentDigest(document: RenderDocument): Digest {\n const origin = pageOrigin(document.baseUrl);\n return digestValue({\n documentVersion: document.documentVersion,\n html: document.html,\n frame: {\n html: { ...document.frame.html },\n body: { ...document.frame.body },\n ancestors: document.frame.ancestors.map((element) => ({\n tag: element.tag,\n attributes: { ...element.attributes },\n })),\n ...(document.frame.containerWidth !== undefined\n ? { containerWidth: document.frame.containerWidth }\n : {}),\n },\n css: [...document.css],\n viewport: { ...document.viewport },\n inherited: { ...document.inherited },\n fonts: [...document.fonts],\n ...(document.assets ? { assets: { ...document.assets } } : {}),\n // The base and the page's own resources as paths: a collector serves the\n // build on a free port, and a digest holding it would miss the render cache\n // on every run. The document keeps both absolute, because the renderer\n // resolves against them; a resource another origin serves stays absolute.\n ...(document.baseUrl !== undefined ? { baseUrl: withoutOrigin(document.baseUrl, origin) } : {}),\n ...(document.resources !== undefined\n ? {\n resources: Object.fromEntries(\n Object.entries(document.resources).map(([url, resource]) => [\n withoutOrigin(url, origin),\n { ...resource },\n ]),\n ),\n }\n : {}),\n });\n}\n\n/**\n * Who painted it.\n *\n * The durable half of this system rests on one requirement — *images compared\n * against each other must come from the same machine* — and a requirement that\n * is merely stated gets violated by a CI runner upgrade nobody announced. This\n * makes it a value, so it can be compared rather than assumed.\n *\n * Every field is something that has been observed to move pixels without moving\n * markup. None of them is a guess about what *might* matter.\n */\nexport interface RenderIdentity {\n /** `playwright-chromium@1.49.0`, `remote:render.internal`, … */\n readonly renderer: string;\n /** Engine build. A Chromium bump repaints text; that is not a regression. */\n readonly engine: string;\n /** OS and architecture. Font rasterization differs across both. */\n readonly platform: string;\n readonly deviceScaleFactor: number;\n /**\n * Fonts the *renderer* actually has.\n *\n * Compared against {@link RenderDocument.fonts} at render time, because a\n * substituted font is the single most common way two machines disagree, and\n * it is invisible in every artifact except the image itself.\n */\n readonly fonts: readonly string[];\n\n /**\n * Digest of what the renderer did *to* the page so it could be observed.\n *\n * Pausing animations, hiding a caret, waiting for fonts — each is necessary\n * and each widens the gap between what was measured and what a person sees.\n * The gap itself is acceptable; a *difference* in it between two runs is not,\n * because the images then differ for a reason that is not the code and the\n * report blames whichever component sits under the pixels. Folding it in here\n * names that gap as the cause of the change instead, by the same mechanism\n * that names a moved machine.\n *\n * Optional only because a renderer may predate the field. Absent means the\n * renderer did not record what it did, which is emphatically not the same as\n * \"it did nothing\" — and two such renderers will compare, which is the hazard\n * that argues for every renderer setting it.\n */\n readonly stabilization?: Digest;\n\n /**\n * Digest of pixel-affecting browser launch and raster settings.\n *\n * Separate from stabilization: one describes how the browser paints, the\n * other what was done to the page before it was read.\n */\n readonly rasterization?: Digest;\n}\n\nexport function identityDigest(identity: RenderIdentity): Digest {\n return digestValue({\n renderer: identity.renderer,\n engine: identity.engine,\n platform: identity.platform,\n ...(identity.stabilization !== undefined ? { stabilization: identity.stabilization } : {}),\n ...(identity.rasterization !== undefined ? { rasterization: identity.rasterization } : {}),\n deviceScaleFactor: identity.deviceScaleFactor,\n fonts: [...identity.fonts],\n });\n}\n\n/**\n * A rendered image and the conditions it was produced under.\n *\n * `bytes` is base64 rather than a `Buffer` so the whole value survives the same\n * hops the document does. A raster that could only exist in Node would make the\n * remote route a special case in every function that touches one.\n *\n * The image is optional, and the three fields that describe it move together.\n * A subject that occupies no pixels — a wrapper whose only child went to a\n * portal, a `describeConformance` mount with no children — still has a\n * document, rules, components and an accessibility tree, all of which compare.\n * Refusing to photograph it is right; refusing the subject reported a quarter\n * of Material UI's unit tier as unobserved over an axis nothing was asking\n * about. Absent here means *this subject has no pixels to observe*, which is a\n * measurement; it never means *the image was lost*.\n */\nexport interface Raster {\n readonly documentDigest: Digest;\n readonly identity: RenderIdentity;\n /**\n * Device pixels. `width / viewport.width` is the scale, and is asserted.\n *\n * Absent exactly when {@link bytes} is, and {@link pictured} is the one place\n * that reads the three together.\n */\n readonly width?: number;\n readonly height?: number;\n /** PNG, base64. Absent when the subject occupies no pixels. */\n readonly bytes?: string;\n /** Fonts the document declared that the renderer did not have. */\n readonly missingFonts: readonly string[];\n\n /**\n * The browser-computed accessibility tree from the acquisition that produced\n * this candidate. It is independent evidence: pixel equality cannot settle it.\n * Absent means the acquisition host did not observe this boundary.\n */\n readonly accessibility?: import('./accessibility.js').AccessibilitySnapshot;\n\n /**\n * What the document that painted this said about its own components (ADR-0018).\n *\n * The field that makes a baseline self-describing, and the reason it is here\n * rather than only in a history store. Separating the component that *caused* a\n * change from the components the change merely moved needs both revisions, and\n * a stored baseline is an image: without this, a run has one document and ranks\n * by area — which\n * [journal 0013](https://github.com/Variance-Authority/variance-authority/blob/main/docs/context/journal/0013-observability.md)\n * measured as backwards.\n *\n * Text, and small: one line per component boundary. It rides in the sidecar\n * beside a PNG that dominates it, so a baseline carries its own explanation\n * wherever it is copied, and a store or a transport that drops it degrades to\n * ranking by area rather than to a wrong answer.\n *\n * Optional because a baseline written before this existed does not have it, and\n * because a profile or a collector that supplies no snapshot cannot produce it.\n * Absent means *unknown*, never *no components*.\n */\n readonly components?: readonly ComponentHash[];\n\n /**\n * What inspection found in the document that painted this, as marks.\n *\n * The field that lets a later run say *this defect was already here* instead of\n * printing every defect it finds with no indication of when it arrived. A\n * finding is produced from one render with no baseline consulted — that is the\n * point of having one — and the consequence is that the list is identical on\n * the run that introduced a defect and on the two hundred runs after it. A\n * reviewer reading `a control inside another control` has no way to tell an\n * inherited condition from something they just wrote, and the two are different\n * decisions.\n *\n * Marks rather than findings, from {@link findingMark}. The sentence, the\n * landmark phrase and the source line are all rewritten by edits that do not\n * touch the defect, so storing them would report a defect as new every time the\n * copy beside it changed — and they are the bulk of the bytes, in a sidecar\n * that is committed beside every baseline.\n *\n * **Absent means nothing inspected it, never that it was clean.** A baseline\n * written before this existed, or by a collector that supplies no snapshot, has\n * no list, and a run that read absence as `[]` would announce every standing\n * defect in the suite as newly introduced on the first run after an upgrade.\n */\n readonly findingMarks?: readonly string[];\n\n /**\n * The semantic snapshot of the document that painted this, packed.\n *\n * The region join needs to know where each element was in the render the\n * pixels are compared against. A changed region is the union of what left and\n * what arrived, and a stored baseline is an image: without its own tree, a\n * control replaced by a larger element changes pixels outside every box this\n * render has, and the join names the row around it. With it, the join pairs\n * the two renders' nodes with the matcher the semantic diff reads, so a\n * region and a delta agree about which element a node was.\n *\n * Packed (see {@link PackedSnapshot}) because the tree is an order of\n * magnitude larger than the component hashes, and it rides in every sidecar.\n * Every store and transport carries the text as it is, and only the run that\n * decides unpacks it.\n *\n * **Absent means not recorded.** A baseline written before this existed, or\n * from a collector that supplied no snapshot, has none, and a verdict whose\n * regions were joined without it says so.\n */\n readonly snapshot?: PackedSnapshot;\n}\n\n/**\n * A raster whose image is in hand.\n *\n * Every pixel comparison in the codebase needs all three of `bytes`, `width`\n * and `height`, and they are absent together or present together. Narrowing\n * them one at a time is how a caller ends up reading the bytes of a subject\n * that was never photographed, so the narrowing lives here and is done once.\n */\nexport type Pictured = Raster & {\n readonly width: number;\n readonly height: number;\n readonly bytes: string;\n};\n\n/** Whether this record carries an image, narrowing all three fields together. */\nexport function pictured(raster: Raster): raster is Pictured {\n return raster.bytes !== undefined && raster.width !== undefined && raster.height !== undefined;\n}\n\n/**\n * Whether the subject occupied any pixels, answered without the image.\n *\n * The question {@link pictured} cannot be asked of a sidecar. Bytes live in a\n * separate file, row or object, so a record read without them carries the width\n * and the height and nothing else — and those *are* the store's record of\n * whether there was ever anything to photograph, which is why `sidecarFrom`\n * refuses a sidecar carrying one of them.\n *\n * So: this for a record, {@link pictured} for an image in hand. A caller that\n * reached for `pictured` here would read every sidecar in the store as a subject\n * with no pixels, because none of them have bytes.\n */\nexport function occupiesPixels(raster: Omit<Raster, 'bytes'>): boolean {\n return raster.width !== undefined && raster.height !== undefined;\n}\n\n/**\n * The image, or a refusal naming the subject that has none.\n *\n * For the paths where a missing image is a programming error rather than a\n * measurement — a diff, a PNG encode — so they read as assertions instead of\n * as a non-null assertion nobody can audit.\n */\nexport function picture(raster: Raster, subject: string): Pictured {\n if (!pictured(raster)) {\n throw new Error(\n `\\`${subject}\\` occupies no pixels, and this path needs an image; ` +\n 'a subject with no raster compares on its document alone',\n );\n }\n return raster;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"document.js","sourceRoot":"","sources":["../../src/format/document.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Diagnostic, SubjectRef } from './capture.js';\nimport type { Viewport } from './environment.js';\nimport { digestValue, type Digest } from './hash.js';\nimport { pageOrigin, withoutOrigin } from './origin.js';\nimport type { PackedSnapshot } from './packed.js';\nimport type { ComponentHash } from './snapshot.js';\n\n/**\n * The render document: what gets *sent* somewhere to become an image.\n *\n * This is the acquisition phase's output and the only thing the rendering side\n * ever sees. It is plain serializable data for the same reason `RawCapture` is\n * (ADR-0006): the renderer may be a page in this process, a browser on this\n * machine, or a pinned container two networks away, and no code upstream of it\n * may assume which.\n *\n * Two routes produce one of these, and the point of the type is that they are\n * interchangeable:\n *\n * 1. **jsdom → document → render elsewhere.** jsdom cannot rasterize. It can\n * describe exactly what to rasterize, which is the sub-renderer split ADR-0002\n * asks for: the cheap tier decides almost everything, and the residue is\n * handed to something that owns a GPU.\n * 2. **playwright → document → render on a server.** The same payload, acquired\n * from a real engine. Used when the *deciding* machine and the *pinned*\n * machine are different, which is the case every CI setup actually has.\n *\n * A browser may instead capture a `Raster` in place. That is the sibling branch\n * of `CaptureMaterial`, not a third route that somehow produces this type.\n *\n * ## Why this is small\n *\n * `css` holds the *applicable* rules only — the pruning of ADR-0003, which on\n * `@variance-authority/dom`'s accretion fixture takes 1010 rules to 1. That\n * number is a cost claim as much as a\n * correctness one: shipping a document over a network hop is only sensible if the\n * document is not the entire design system plus Storybook's chrome.\n */\nexport interface RenderDocument {\n readonly documentVersion: 1;\n readonly subject: SubjectRef;\n\n /**\n * The subject subtree, serialized.\n *\n * Serialized rather than referenced by URL: a URL makes the renderer\n * responsible for reproducing the application's state, which it cannot do and\n * should not try. What is sent is the markup that existed at the moment of\n * capture, which is the only definition of \"this render\" that survives a hop.\n */\n readonly html: string;\n\n /**\n * The ancestor context the subject was rendered inside.\n *\n * Not decoration. Pruning keeps rules like `html.dark .card` and\n * `.app .list > li`, whose left-hand side lives *above* the subject — render\n * the subtree bare and those rules match nothing, so the image is missing\n * exactly the styling the collector went to the trouble of proving applies.\n *\n * Reproduced as empty open tags rather than by re-rendering the application:\n * what a selector needs is a chain of elements with the right tags, ids,\n * classes, and attributes, and nothing above the subject contributes anything\n * else to it that {@link RenderDocument.inherited} does not already carry.\n */\n readonly frame: RenderFrame;\n\n /**\n * Applicable stylesheet text, in cascade order.\n *\n * Order is load-bearing and is the caller's to preserve — the cascade breaks\n * specificity ties by document order, so a set that arrives shuffled paints\n * differently while hashing the same if this were a set rather than a list.\n */\n readonly css: readonly string[];\n\n readonly viewport: Viewport;\n\n /**\n * Inherited values in force at the subject root, applied to its wrapper.\n *\n * The same requirement as `RawCapture.inheritedSeed` and for the same reason:\n * pruning drops rules on ancestors outside the subtree, and a font-size\n * inherited from `<html>` is not optional decoration — dropping it changes\n * every metric in the image.\n */\n readonly inherited: Readonly<Record<string, string>>;\n\n /**\n * Fonts the acquiring side observed as loaded.\n *\n * Carried, not resolved. A renderer that lacks one of these will substitute\n * and produce different metrics, and that has to be *detectable* rather than\n * silently absorbed — see {@link RenderIdentity}.\n */\n readonly fonts: readonly string[];\n\n /** External references, url → content hash. Present so a swap is visible. */\n readonly assets?: Readonly<Record<string, string>>;\n\n /**\n * Base against which relative resource references were resolved.\n *\n * Preserves relative-reference resolution for either document kind. Resource\n * closure is denoted by `resources`, not by the presence of this field.\n */\n readonly baseUrl?: string;\n\n /**\n * Immutable responses needed to paint this document, keyed by absolute URL.\n *\n * Presence — including an empty object — means the document is\n * resource-closed: a renderer must not consult the network for a missing URL.\n * Absence preserves the older network-capable document contract and must not\n * be described as portable to another machine.\n */\n readonly resources?: Readonly<Record<string, RenderResource>>;\n\n readonly diagnostics: readonly Diagnostic[];\n}\n\nexport interface RenderResource {\n readonly contentType: string;\n /** Response bytes, base64. */\n readonly bytes: string;\n readonly digest: Digest;\n\n /**\n * The status a renderer answers this URL with. Absent means 200.\n *\n * A subject is allowed to reference something that is not there: a fallback\n * test does it on purpose, and the browser it was acquired in painted the\n * broken state rather than the image. Recording the failure as a failure is\n * what lets a renderer reproduce that state without asking the network.\n */\n readonly status?: number;\n}\n\nexport interface RenderFrame {\n /** Attributes on `<html>`. `class=\"dark\"` here decides half a design system. */\n readonly html: Readonly<Record<string, string>>;\n readonly body: Readonly<Record<string, string>>;\n /** Elements between `<body>` and the subject root, outermost first. */\n readonly ancestors: readonly FrameElement[];\n\n /**\n * Used `width` of the subject's parent, in CSS pixels and in the parent's own\n * `box-sizing`, when layout was observable. Written back on the same box, it\n * gives the content box a percentage on the subject resolves against.\n *\n * A subject declaring `width: 100%` is a different size in a 1024px page and\n * inside a 600px panel, and nothing else in this document says which it was.\n * Absent under a profile with no layout engine, in which case the render falls\n * back to the viewport and the difference, if any, is real rather than hidden.\n */\n readonly containerWidth?: number;\n}\n\nexport interface FrameElement {\n readonly tag: string;\n readonly attributes: Readonly<Record<string, string>>;\n}\n\n/**\n * Content address of a document: *what is to be painted*.\n *\n * This is the deferral lever. Rendering is the expensive phase, and a document\n * whose digest has been rendered before under the same identity has an image\n * already — so the cheapest render is the one that is skipped. Nothing about the\n * branch, the commit, or the file path enters this (Principle 4), so moving a\n * story between files does not cost a re-render.\n *\n * Deliberately covers `viewport` in full, `deviceScaleFactor` included: unlike\n * the semantic key (ADR-0010), a raster genuinely differs at 2x and the whole\n * purpose of this digest is to address the raster.\n *\n * **Limit, stated rather than discovered.** Declaration values arrive serialized\n * by the acquiring engine's CSSOM — jsdom writes `rgb(18, 52, 86)` where the\n * author wrote `#123456` — and this phase deliberately does not canonicalize\n * them, because a collector that normalizes is a second ruleset versioned by\n * nothing (ADR-0001). So two engines can address the same page differently. That\n * makes a render cache *per acquiring engine*, which is a missed cache hit and\n * never a wrong image.\n */\nexport function documentDigest(document: RenderDocument): Digest {\n const origin = pageOrigin(document.baseUrl);\n return digestValue({\n documentVersion: document.documentVersion,\n html: document.html,\n frame: {\n html: { ...document.frame.html },\n body: { ...document.frame.body },\n ancestors: document.frame.ancestors.map((element) => ({\n tag: element.tag,\n attributes: { ...element.attributes },\n })),\n ...(document.frame.containerWidth !== undefined\n ? { containerWidth: document.frame.containerWidth }\n : {}),\n },\n css: [...document.css],\n viewport: { ...document.viewport },\n inherited: { ...document.inherited },\n fonts: [...document.fonts],\n ...(document.assets ? { assets: { ...document.assets } } : {}),\n // The base and the page's own resources as paths: a collector serves the\n // build on a free port, and a digest holding it would miss the render cache\n // on every run. The document keeps both absolute, because the renderer\n // resolves against them; a resource another origin serves stays absolute.\n ...(document.baseUrl !== undefined ? { baseUrl: withoutOrigin(document.baseUrl, origin) } : {}),\n ...(document.resources !== undefined\n ? {\n resources: Object.fromEntries(\n Object.entries(document.resources).map(([url, resource]) => [\n withoutOrigin(url, origin),\n { ...resource },\n ]),\n ),\n }\n : {}),\n });\n}\n\n/**\n * Who painted it.\n *\n * The durable half of this system rests on one requirement — *images compared\n * against each other must come from the same machine* — and a requirement that\n * is merely stated gets violated by a CI runner upgrade nobody announced. This\n * makes it a value, so it can be compared rather than assumed.\n *\n * Every field is something that has been observed to move pixels without moving\n * markup. None of them is a guess about what *might* matter.\n */\nexport interface RenderIdentity {\n /** `playwright-chromium@1.49.0`, `remote:render.internal`, … */\n readonly renderer: string;\n /** Engine build. A Chromium bump repaints text; that is not a regression. */\n readonly engine: string;\n /** OS and architecture. Font rasterization differs across both. */\n readonly platform: string;\n readonly deviceScaleFactor: number;\n /**\n * Fonts the *renderer* actually has.\n *\n * Compared against {@link RenderDocument.fonts} at render time, because a\n * substituted font is the single most common way two machines disagree, and\n * it is invisible in every artifact except the image itself.\n */\n readonly fonts: readonly string[];\n\n /**\n * Digest of what the renderer did *to* the page so it could be observed.\n *\n * Pausing animations, hiding a caret, waiting for fonts — each is necessary\n * and each widens the gap between what was measured and what a person sees.\n * The gap itself is acceptable; a *difference* in it between two runs is not,\n * because the images then differ for a reason that is not the code and the\n * report blames whichever component sits under the pixels. Folding it in here\n * names that gap as the cause of the change instead, by the same mechanism\n * that names a moved machine.\n *\n * Optional only because a renderer may predate the field. Absent means the\n * renderer did not record what it did, which is emphatically not the same as\n * \"it did nothing\" — and two such renderers will compare, which is the hazard\n * that argues for every renderer setting it.\n */\n readonly stabilization?: Digest;\n\n /**\n * Digest of pixel-affecting browser launch and raster settings.\n *\n * Separate from stabilization: one describes how the browser paints, the\n * other what was done to the page before it was read.\n */\n readonly rasterization?: Digest;\n}\n\nexport function identityDigest(identity: RenderIdentity): Digest {\n return digestValue({\n renderer: identity.renderer,\n engine: identity.engine,\n platform: identity.platform,\n ...(identity.stabilization !== undefined ? { stabilization: identity.stabilization } : {}),\n ...(identity.rasterization !== undefined ? { rasterization: identity.rasterization } : {}),\n deviceScaleFactor: identity.deviceScaleFactor,\n fonts: [...identity.fonts],\n });\n}\n\n/**\n * A rendered image and the conditions it was produced under.\n *\n * `bytes` is base64 rather than a `Buffer` so the whole value survives the same\n * hops the document does. A raster that could only exist in Node would make the\n * remote route a special case in every function that touches one.\n *\n * The image is optional, and the three fields that describe it move together.\n * A subject that occupies no pixels — a wrapper whose only child went to a\n * portal, a `describeConformance` mount with no children — still has a\n * document, rules, components and an accessibility tree, all of which compare.\n * Refusing to photograph it is right; refusing the subject reported a quarter\n * of Material UI's unit tier as unobserved over an axis nothing was asking\n * about. Absent here means *this subject has no pixels to observe*, which is a\n * measurement; it never means *the image was lost*.\n */\nexport interface Raster {\n readonly documentDigest: Digest;\n readonly identity: RenderIdentity;\n /**\n * Device pixels. `width / viewport.width` is the scale, and is asserted.\n *\n * Absent exactly when {@link bytes} is, and {@link pictured} is the one place\n * that reads the three together.\n */\n readonly width?: number;\n readonly height?: number;\n /**\n * The image, base64, in the format its own header declares: a capture is a\n * PNG, and a kept baseline is the lossless WebP it was kept as, or a PNG kept\n * before. Absent when the subject occupies no pixels.\n */\n readonly bytes?: string;\n /** Fonts the document declared that the renderer did not have. */\n readonly missingFonts: readonly string[];\n\n /**\n * The browser-computed accessibility tree from the acquisition that produced\n * this candidate. It is independent evidence: pixel equality cannot settle it.\n * Absent means the acquisition host did not observe this boundary.\n */\n readonly accessibility?: import('./accessibility.js').AccessibilitySnapshot;\n\n /**\n * What the document that painted this said about its own components (ADR-0018).\n *\n * The field that makes a baseline self-describing, and the reason it is here\n * rather than only in a history store. Separating the component that *caused* a\n * change from the components the change merely moved needs both revisions, and\n * a stored baseline is an image: without this, a run has one document and ranks\n * by area — which\n * [journal 0013](https://github.com/Variance-Authority/variance-authority/blob/main/docs/context/journal/0013-observability.md)\n * measured as backwards.\n *\n * Text, and small: one line per component boundary. It rides in the sidecar\n * beside a PNG that dominates it, so a baseline carries its own explanation\n * wherever it is copied, and a store or a transport that drops it degrades to\n * ranking by area rather than to a wrong answer.\n *\n * Optional because a baseline written before this existed does not have it, and\n * because a profile or a collector that supplies no snapshot cannot produce it.\n * Absent means *unknown*, never *no components*.\n */\n readonly components?: readonly ComponentHash[];\n\n /**\n * What inspection found in the document that painted this, as marks.\n *\n * The field that lets a later run say *this defect was already here* instead of\n * printing every defect it finds with no indication of when it arrived. A\n * finding is produced from one render with no baseline consulted — that is the\n * point of having one — and the consequence is that the list is identical on\n * the run that introduced a defect and on the two hundred runs after it. A\n * reviewer reading `a control inside another control` has no way to tell an\n * inherited condition from something they just wrote, and the two are different\n * decisions.\n *\n * Marks rather than findings, from {@link findingMark}. The sentence, the\n * landmark phrase and the source line are all rewritten by edits that do not\n * touch the defect, so storing them would report a defect as new every time the\n * copy beside it changed — and they are the bulk of the bytes, in a sidecar\n * that is committed beside every baseline.\n *\n * **Absent means nothing inspected it, never that it was clean.** A baseline\n * written before this existed, or by a collector that supplies no snapshot, has\n * no list, and a run that read absence as `[]` would announce every standing\n * defect in the suite as newly introduced on the first run after an upgrade.\n */\n readonly findingMarks?: readonly string[];\n\n /**\n * The semantic snapshot of the document that painted this, packed.\n *\n * The region join needs to know where each element was in the render the\n * pixels are compared against. A changed region is the union of what left and\n * what arrived, and a stored baseline is an image: without its own tree, a\n * control replaced by a larger element changes pixels outside every box this\n * render has, and the join names the row around it. With it, the join pairs\n * the two renders' nodes with the matcher the semantic diff reads, so a\n * region and a delta agree about which element a node was.\n *\n * Packed (see {@link PackedSnapshot}) because the tree is an order of\n * magnitude larger than the component hashes, and it rides in every sidecar.\n * Every store and transport carries the text as it is, and only the run that\n * decides unpacks it.\n *\n * **Absent means not recorded.** A baseline written before this existed, or\n * from a collector that supplied no snapshot, has none, and a verdict whose\n * regions were joined without it says so.\n */\n readonly snapshot?: PackedSnapshot;\n}\n\n/**\n * A raster whose image is in hand.\n *\n * Every pixel comparison in the codebase needs all three of `bytes`, `width`\n * and `height`, and they are absent together or present together. Narrowing\n * them one at a time is how a caller ends up reading the bytes of a subject\n * that was never photographed, so the narrowing lives here and is done once.\n */\nexport type Pictured = Raster & {\n readonly width: number;\n readonly height: number;\n readonly bytes: string;\n};\n\n/** Whether this record carries an image, narrowing all three fields together. */\nexport function pictured(raster: Raster): raster is Pictured {\n return raster.bytes !== undefined && raster.width !== undefined && raster.height !== undefined;\n}\n\n/**\n * Whether the subject occupied any pixels, answered without the image.\n *\n * The question {@link pictured} cannot be asked of a sidecar. Bytes live in a\n * separate file, row or object, so a record read without them carries the width\n * and the height and nothing else — and those *are* the store's record of\n * whether there was ever anything to photograph, which is why `sidecarFrom`\n * refuses a sidecar carrying one of them.\n *\n * So: this for a record, {@link pictured} for an image in hand. A caller that\n * reached for `pictured` here would read every sidecar in the store as a subject\n * with no pixels, because none of them have bytes.\n */\nexport function occupiesPixels(raster: Omit<Raster, 'bytes'>): boolean {\n return raster.width !== undefined && raster.height !== undefined;\n}\n\n/**\n * The image, or a refusal naming the subject that has none.\n *\n * For the paths where a missing image is a programming error rather than a\n * measurement — a diff, a PNG encode — so they read as assertions instead of\n * as a non-null assertion nobody can audit.\n */\nexport function picture(raster: Raster, subject: string): Pictured {\n if (!pictured(raster)) {\n throw new Error(\n `\\`${subject}\\` occupies no pixels, and this path needs an image; ` +\n 'a subject with no raster compares on its document alone',\n );\n }\n return raster;\n}\n"]}
|
package/dist/index.d.ts
CHANGED
|
@@ -21,8 +21,9 @@
|
|
|
21
21
|
*
|
|
22
22
|
* Every name lives in exactly one of them; nothing is reachable from two paths.
|
|
23
23
|
* This entrypoint holds only what belongs to no group: the artifact a capture is
|
|
24
|
-
* written to,
|
|
24
|
+
* written to, the name offered for a mistyped one, and a git-LFS pointer's head.
|
|
25
25
|
*/
|
|
26
26
|
export * from './artifact.js';
|
|
27
|
+
export { isLfsPointer, LFS_POINTER_HEAD_BYTES } from './lfs-pointer.js';
|
|
27
28
|
export { didYouMean, nearest } from './nearest.js';
|
|
28
29
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.js
CHANGED
|
@@ -21,8 +21,9 @@
|
|
|
21
21
|
*
|
|
22
22
|
* Every name lives in exactly one of them; nothing is reachable from two paths.
|
|
23
23
|
* This entrypoint holds only what belongs to no group: the artifact a capture is
|
|
24
|
-
* written to,
|
|
24
|
+
* written to, the name offered for a mistyped one, and a git-LFS pointer's head.
|
|
25
25
|
*/
|
|
26
26
|
export * from './artifact.js';
|
|
27
|
+
export { isLfsPointer, LFS_POINTER_HEAD_BYTES } from './lfs-pointer.js';
|
|
27
28
|
export { didYouMean, nearest } from './nearest.js';
|
|
28
29
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,cAAc,eAAe,CAAC;AAC9B,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC","sourcesContent":["/**\n * `@variance-authority/core` — the format, the rules, and the verdict model.\n *\n * This package is pure data in, pure data out: no DOM, no React, no I/O, no\n * async. That is enforced by its `tsconfig` (`lib: ES2022` only), so \"the core\n * cannot peek at a live document\" is a compile error rather than a convention\n * (ADR-0001). Collectors extract; core normalizes and adjudicates.\n *\n * Seven groups, each its own entrypoint, in the order an answer travels through\n * them:\n *\n * | entrypoint | what it holds |\n * |---|---|\n * | `core/format` | what a subject *is*: capture, snapshot, document, identity |\n * | `core/rules` | the versioned opinions: allowlist, cascade, canonicalization |\n * | `core/compare` | two snapshots become deltas — and no verdict |\n * | `core/attribute` | a position becomes a component becomes a file |\n * | `core/relate` | what reaches what: the graph a change is traversed over |\n * | `core/judge` | policy: verdicts, intent, the docket a reader is handed |\n * | `core/plan` | a composition as a value, and the identity derived from it |\n *\n * Every name lives in exactly one of them; nothing is reachable from two paths.\n * This entrypoint holds only what belongs to no group: the artifact a capture is\n * written to,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,cAAc,eAAe,CAAC;AAC9B,OAAO,EAAE,YAAY,EAAE,sBAAsB,EAAE,MAAM,kBAAkB,CAAC;AACxE,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC","sourcesContent":["/**\n * `@variance-authority/core` — the format, the rules, and the verdict model.\n *\n * This package is pure data in, pure data out: no DOM, no React, no I/O, no\n * async. That is enforced by its `tsconfig` (`lib: ES2022` only), so \"the core\n * cannot peek at a live document\" is a compile error rather than a convention\n * (ADR-0001). Collectors extract; core normalizes and adjudicates.\n *\n * Seven groups, each its own entrypoint, in the order an answer travels through\n * them:\n *\n * | entrypoint | what it holds |\n * |---|---|\n * | `core/format` | what a subject *is*: capture, snapshot, document, identity |\n * | `core/rules` | the versioned opinions: allowlist, cascade, canonicalization |\n * | `core/compare` | two snapshots become deltas — and no verdict |\n * | `core/attribute` | a position becomes a component becomes a file |\n * | `core/relate` | what reaches what: the graph a change is traversed over |\n * | `core/judge` | policy: verdicts, intent, the docket a reader is handed |\n * | `core/plan` | a composition as a value, and the identity derived from it |\n *\n * Every name lives in exactly one of them; nothing is reachable from two paths.\n * This entrypoint holds only what belongs to no group: the artifact a capture is\n * written to, the name offered for a mistyped one, and a git-LFS pointer's head.\n */\n\nexport * from './artifact.js';\nexport { isLfsPointer, LFS_POINTER_HEAD_BYTES } from './lfs-pointer.js';\nexport { didYouMean, nearest } from './nearest.js';\n"]}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/** How many leading bytes {@link isLfsPointer} needs to answer. */
|
|
2
|
+
export declare const LFS_POINTER_HEAD_BYTES: number;
|
|
3
|
+
/**
|
|
4
|
+
* Whether a file's first bytes are a git-LFS pointer rather than the file.
|
|
5
|
+
*
|
|
6
|
+
* One signature for every reader: the store refusing a baseline, `variance
|
|
7
|
+
* doctor` looking for the files the store would refuse, and the source index
|
|
8
|
+
* refusing a source text git handed back as a pointer. Two copies of a prefix are
|
|
9
|
+
* two chances for one reader to call a file fine that another refuses. The
|
|
10
|
+
* native addon holds the one copy a second language forces, in `lfs.rs`.
|
|
11
|
+
*/
|
|
12
|
+
export declare function isLfsPointer(head: Uint8Array): boolean;
|
|
13
|
+
//# sourceMappingURL=lfs-pointer.d.ts.map
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/** First bytes of an LFS pointer file, per the git-lfs v1 pointer spec. */
|
|
2
|
+
const POINTER_PREFIX = 'version https://git-lfs.github.com/spec/';
|
|
3
|
+
/** How many leading bytes {@link isLfsPointer} needs to answer. */
|
|
4
|
+
export const LFS_POINTER_HEAD_BYTES = POINTER_PREFIX.length;
|
|
5
|
+
/**
|
|
6
|
+
* Whether a file's first bytes are a git-LFS pointer rather than the file.
|
|
7
|
+
*
|
|
8
|
+
* One signature for every reader: the store refusing a baseline, `variance
|
|
9
|
+
* doctor` looking for the files the store would refuse, and the source index
|
|
10
|
+
* refusing a source text git handed back as a pointer. Two copies of a prefix are
|
|
11
|
+
* two chances for one reader to call a file fine that another refuses. The
|
|
12
|
+
* native addon holds the one copy a second language forces, in `lfs.rs`.
|
|
13
|
+
*/
|
|
14
|
+
export function isLfsPointer(head) {
|
|
15
|
+
if (head.length < POINTER_PREFIX.length)
|
|
16
|
+
return false;
|
|
17
|
+
for (let at = 0; at < POINTER_PREFIX.length; at += 1) {
|
|
18
|
+
if (head[at] !== POINTER_PREFIX.charCodeAt(at))
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
return true;
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=lfs-pointer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lfs-pointer.js","sourceRoot":"","sources":["../src/lfs-pointer.ts"],"names":[],"mappings":"AAAA,2EAA2E;AAC3E,MAAM,cAAc,GAAG,0CAA0C,CAAC;AAElE,mEAAmE;AACnE,MAAM,CAAC,MAAM,sBAAsB,GAAG,cAAc,CAAC,MAAM,CAAC;AAE5D;;;;;;;;GAQG;AACH,MAAM,UAAU,YAAY,CAAC,IAAgB;IAC3C,IAAI,IAAI,CAAC,MAAM,GAAG,cAAc,CAAC,MAAM;QAAE,OAAO,KAAK,CAAC;IACtD,KAAK,IAAI,EAAE,GAAG,CAAC,EAAE,EAAE,GAAG,cAAc,CAAC,MAAM,EAAE,EAAE,IAAI,CAAC,EAAE,CAAC;QACrD,IAAI,IAAI,CAAC,EAAE,CAAC,KAAK,cAAc,CAAC,UAAU,CAAC,EAAE,CAAC;YAAE,OAAO,KAAK,CAAC;IAC/D,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC","sourcesContent":["/** First bytes of an LFS pointer file, per the git-lfs v1 pointer spec. */\nconst POINTER_PREFIX = 'version https://git-lfs.github.com/spec/';\n\n/** How many leading bytes {@link isLfsPointer} needs to answer. */\nexport const LFS_POINTER_HEAD_BYTES = POINTER_PREFIX.length;\n\n/**\n * Whether a file's first bytes are a git-LFS pointer rather than the file.\n *\n * One signature for every reader: the store refusing a baseline, `variance\n * doctor` looking for the files the store would refuse, and the source index\n * refusing a source text git handed back as a pointer. Two copies of a prefix are\n * two chances for one reader to call a file fine that another refuses. The\n * native addon holds the one copy a second language forces, in `lfs.rs`.\n */\nexport function isLfsPointer(head: Uint8Array): boolean {\n if (head.length < POINTER_PREFIX.length) return false;\n for (let at = 0; at < POINTER_PREFIX.length; at += 1) {\n if (head[at] !== POINTER_PREFIX.charCodeAt(at)) return false;\n }\n return true;\n}\n"]}
|
package/dist/relate/narrow.js
CHANGED
|
@@ -65,9 +65,9 @@ export function narrowed(dependents, names, seeds, moved, uses, options = {}) {
|
|
|
65
65
|
const use = uses(names[next], names[node]);
|
|
66
66
|
if (kind === IMPORTS) {
|
|
67
67
|
const imported = use?.imports;
|
|
68
|
-
//
|
|
69
|
-
//
|
|
70
|
-
//
|
|
68
|
+
// An importer that reads a moved name is entered whole: an import hop
|
|
69
|
+
// two out is charged whole, and narrowing past it is the coverage
|
|
70
|
+
// record's job, not the walk's.
|
|
71
71
|
if (imported === undefined)
|
|
72
72
|
return undefined;
|
|
73
73
|
return imported.some((name) => held.has(name)) ? WHOLE : NOTHING;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"narrow.js","sourceRoot":"","sources":["../../src/relate/narrow.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,KAAK,EAAgC,MAAM,cAAc,CAAC;AAChG,OAAO,EAAE,UAAU,EAA+B,MAAM,YAAY,CAAC;AAuBrE,MAAM,OAAO,GAAG,UAAU,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;AAC9C,MAAM,SAAS,GAAG,UAAU,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC;AAElD,sFAAsF;AACtF,SAAS,WAAW,CAAC,MAA8C,EAAE,KAA0B;IAC7F,MAAM,GAAG,GAAa,EAAE,CAAC;IACzB,KAAK,MAAM,CAAC,QAAQ,EAAE,QAAQ,CAAC,IAAI,MAAM,EAAE,CAAC;QAC1C,IAAI,QAAQ,KAAK,GAAG,IAAI,QAAQ,KAAK,GAAG,EAAE,CAAC;YACzC,KAAK,MAAM,IAAI,IAAI,KAAK;gBAAE,IAAI,IAAI,KAAK,SAAS;oBAAE,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACnE,CAAC;aAAM,IAAI,QAAQ,KAAK,GAAG,IAAI,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC;YACnD,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACrB,CAAC;IACH,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,QAAQ,CACtB,UAAqB,EACrB,KAAwB,EACxB,KAAuB,EACvB,KAA6C,EAC7C,IAAU,EACV,OAAO,GAAgD,EAAE;IAEzD,MAAM,GAAG,GAAG,IAAI,UAAU,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;IAClD,MAAM,MAAM,GAAG,QAAQ,CAAC;QACtB,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YACzB,MAAM,OAAO,GAAG,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;YAChC,MAAM,CAAC,IAAI,EAAE,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,CAAU,CAAC;QAC1E,CAAC;IACH,CAAC,CAAC;IAEF,sEAAsE;IACtE,2EAA2E;IAC3E,MAAM,IAAI,GAAwB;QAChC,MAAM,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI;YACnC,IAAI,IAAI,KAAK,KAAK,IAAI,CAAC,IAAI,KAAK,OAAO,IAAI,IAAI,KAAK,SAAS,CAAC;gBAAE,OAAO,KAAK,CAAC;YAC7E,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAE,EAAE,KAAK,CAAC,IAAI,CAAE,CAAC,CAAC;YAC7C,IAAI,IAAI,KAAK,OAAO,EAAE,CAAC;gBACrB,MAAM,QAAQ,GAAG,GAAG,EAAE,OAAO,CAAC;gBAC9B,sEAAsE;gBACtE,
|
|
1
|
+
{"version":3,"file":"narrow.js","sourceRoot":"","sources":["../../src/relate/narrow.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The walk against the arrows, narrowed by the names each importer uses.\n *\n * A file-graph edge says *this file imports that one*, and a plain walk charges\n * every importer for every change. An importer reaches a module only through\n * the names it binds, though, so a change that moved `total` and left `label`\n * alone reaches the files that import `total` and not the ones that import\n * `label`. That is tree shaking, asked of reach instead of a bundle.\n *\n * Each visited file carries one of two states:\n *\n * - **whole** — what it does as it loads moved, or nobody said which of its\n * exports did. Every dependent is entered, as the plain walk does.\n * - **the exports that moved** — read from the file's two texts for a seed,\n * or carried through a re-export for a barrel. A dependent that imports one\n * of them is entered whole: which of *its* exports read the name is a\n * question about its body, and nothing here reads one. A dependent that\n * re-exports one is entered carrying the names it republishes them under.\n *\n * Every answer the use cannot give is whole: an edge the lookup does not\n * know, a namespace import, a `require`, an `import()`, a stylesheet, a\n * component declared in the file. A narrower walk has to be earned by a name\n * the parse recorded, so an unknown can only cost a wider one.\n *\n * A file's names only grow, and a file that grew is expanded again, so the walk\n * reaches the same fixpoint whatever order the seeds arrive in.\n */\n\nimport { carried, inIdOrder, NOTHING, WHOLE, type Carry, type CarriedStep } from './carried.js';\nimport { EDGE_KINDS, type Adjacency, type NodeId } from './graph.js';\nimport type { Traversal, TraversalOptions } from './reach.js';\n\n/** How one file uses one file it imports, read from the parse that recorded the edge. */\nexport interface EdgeUse {\n /**\n * The names an `import` binds from the target, `default` for a default\n * import. Absent when the use is the whole module: a namespace, a `require`,\n * an `import` that binds nothing, which a `require` is stored as.\n */\n readonly imports?: readonly string[];\n /**\n * What a re-export republishes, as `[the target's name, this file's name]`.\n * `['*', '*']` is `export * from`, which republishes every name but\n * `default`; `['*', 'ns']` is `export * as ns from`. Absent when the parse\n * did not record what the file publishes.\n */\n readonly reexports?: readonly (readonly [string, string])[];\n}\n\n/** How `importer` uses `target`, both by file path; `undefined` is whole. */\nexport type Uses = (importer: string, target: string) => EdgeUse | undefined;\n\nconst IMPORTS = EDGE_KINDS.indexOf('imports');\nconst REEXPORTS = EDGE_KINDS.indexOf('reexports');\n\n/** What a re-export hands on from `moved`, under the names the importer publishes. */\nfunction republished(passes: readonly (readonly [string, string])[], moved: ReadonlySet<string>): string[] {\n const out: string[] = [];\n for (const [imported, exported] of passes) {\n if (imported === '*' && exported === '*') {\n for (const name of moved) if (name !== 'default') out.push(name);\n } else if (imported === '*' || moved.has(imported)) {\n out.push(exported);\n }\n }\n return out;\n}\n\n/**\n * Breadth-first against the arrows from `seeds`, each seed in `moved` carrying\n * the exports its change moved and every other one whole.\n */\nexport function narrowed(\n dependents: Adjacency,\n names: readonly string[],\n seeds: Iterable<NodeId>,\n moved: ReadonlyMap<NodeId, readonly string[]>,\n uses: Uses,\n options: Pick<TraversalOptions, 'avoid' | 'through'> = {},\n): Traversal {\n const via = new Int32Array(names.length).fill(-1);\n const starts = function* () {\n for (const seed of seeds) {\n const exports = moved.get(seed);\n yield [seed, exports === undefined ? WHOLE : namesOf(exports)] as const;\n }\n };\n\n // Each edge reads everything the node holds, not only what this entry\n // carried: a name that reached it by another path is as moved as this one.\n const step: CarriedStep<string> = {\n arrive(next, node, kind, _carry, held): Carry<string> | undefined {\n if (held === WHOLE || (kind !== IMPORTS && kind !== REEXPORTS)) return WHOLE;\n const use = uses(names[next]!, names[node]!);\n if (kind === IMPORTS) {\n const imported = use?.imports;\n // An importer that reads a moved name is entered whole: an import hop\n // two out is charged whole, and narrowing past it is the coverage\n // record's job, not the walk's.\n if (imported === undefined) return undefined;\n return imported.some((name) => held.has(name)) ? WHOLE : NOTHING;\n }\n if (use?.reexports === undefined) return undefined;\n return namesOf(republished(use.reexports, held));\n },\n };\n\n const { mask } = carried(dependents, starts(), step, { ...options, via });\n return { mask, via, nodes: inIdOrder(mask) };\n}\n\nfunction namesOf(exports: readonly string[]): ReadonlyMap<string, string> {\n return new Map(exports.map((name) => [name, name]));\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@variance-authority/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.22.0",
|
|
4
4
|
"description": "The Variance Authority format, rules, comparison, attribution and verdicts. Pure data in, pure data out, no DOM and no I/O.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"variance-authority",
|
|
@@ -79,6 +79,6 @@
|
|
|
79
79
|
"build": "tsc --build"
|
|
80
80
|
},
|
|
81
81
|
"devDependencies": {
|
|
82
|
-
"@variance-authority/sense": "^0.
|
|
82
|
+
"@variance-authority/sense": "^0.22.0"
|
|
83
83
|
}
|
|
84
84
|
}
|