@michaelmagan/dnumb 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.
Files changed (133) hide show
  1. package/AGENTS.md +180 -0
  2. package/LICENSE +21 -0
  3. package/README.md +297 -0
  4. package/dist/analysis/cluster.cjs +114 -0
  5. package/dist/analysis/cluster.d.cts +68 -0
  6. package/dist/analysis/cluster.d.ts +68 -0
  7. package/dist/analysis/cluster.js +109 -0
  8. package/dist/analysis/distances.cjs +124 -0
  9. package/dist/analysis/distances.d.cts +58 -0
  10. package/dist/analysis/distances.d.ts +58 -0
  11. package/dist/analysis/distances.js +119 -0
  12. package/dist/analysis/geometry.cjs +60 -0
  13. package/dist/analysis/geometry.d.cts +34 -0
  14. package/dist/analysis/geometry.d.ts +34 -0
  15. package/dist/analysis/geometry.js +53 -0
  16. package/dist/analysis/layout-tree.cjs +245 -0
  17. package/dist/analysis/layout-tree.d.cts +45 -0
  18. package/dist/analysis/layout-tree.d.ts +45 -0
  19. package/dist/analysis/layout-tree.js +242 -0
  20. package/dist/analysis/region-query.cjs +71 -0
  21. package/dist/analysis/region-query.d.cts +25 -0
  22. package/dist/analysis/region-query.d.ts +25 -0
  23. package/dist/analysis/region-query.js +67 -0
  24. package/dist/analysis/types.cjs +7 -0
  25. package/dist/analysis/types.d.cts +143 -0
  26. package/dist/analysis/types.d.ts +143 -0
  27. package/dist/analysis/types.js +6 -0
  28. package/dist/bundle.cjs +182 -0
  29. package/dist/bundle.d.cts +91 -0
  30. package/dist/bundle.d.ts +91 -0
  31. package/dist/bundle.js +175 -0
  32. package/dist/cli.cjs +755 -0
  33. package/dist/cli.d.cts +2 -0
  34. package/dist/cli.d.ts +2 -0
  35. package/dist/cli.js +719 -0
  36. package/dist/diff/changes.cjs +656 -0
  37. package/dist/diff/changes.d.cts +2 -0
  38. package/dist/diff/changes.d.ts +2 -0
  39. package/dist/diff/changes.js +653 -0
  40. package/dist/diff/identity.cjs +167 -0
  41. package/dist/diff/identity.d.cts +62 -0
  42. package/dist/diff/identity.d.ts +62 -0
  43. package/dist/diff/identity.js +159 -0
  44. package/dist/diff/index.cjs +84 -0
  45. package/dist/diff/index.d.cts +76 -0
  46. package/dist/diff/index.d.ts +76 -0
  47. package/dist/diff/index.js +68 -0
  48. package/dist/diff/match.cjs +156 -0
  49. package/dist/diff/match.d.cts +17 -0
  50. package/dist/diff/match.d.ts +17 -0
  51. package/dist/diff/match.js +152 -0
  52. package/dist/diff/render.cjs +540 -0
  53. package/dist/diff/render.d.cts +2 -0
  54. package/dist/diff/render.d.ts +2 -0
  55. package/dist/diff/render.js +537 -0
  56. package/dist/diff/types.cjs +2 -0
  57. package/dist/diff/types.d.cts +211 -0
  58. package/dist/diff/types.d.ts +211 -0
  59. package/dist/diff/types.js +1 -0
  60. package/dist/digest/index.cjs +8 -0
  61. package/dist/digest/index.d.cts +4 -0
  62. package/dist/digest/index.d.ts +4 -0
  63. package/dist/digest/index.js +2 -0
  64. package/dist/digest/parse.cjs +163 -0
  65. package/dist/digest/parse.d.cts +80 -0
  66. package/dist/digest/parse.d.ts +80 -0
  67. package/dist/digest/parse.js +160 -0
  68. package/dist/digest/render.cjs +1575 -0
  69. package/dist/digest/render.d.cts +41 -0
  70. package/dist/digest/render.d.ts +41 -0
  71. package/dist/digest/render.js +1571 -0
  72. package/dist/digest/types.cjs +10 -0
  73. package/dist/digest/types.d.cts +86 -0
  74. package/dist/digest/types.d.ts +86 -0
  75. package/dist/digest/types.js +9 -0
  76. package/dist/index.cjs +61 -0
  77. package/dist/index.d.cts +27 -0
  78. package/dist/index.d.ts +27 -0
  79. package/dist/index.js +19 -0
  80. package/dist/page.cjs +35 -0
  81. package/dist/page.d.cts +40 -0
  82. package/dist/page.d.ts +40 -0
  83. package/dist/page.js +31 -0
  84. package/dist/playwright.cjs +138 -0
  85. package/dist/playwright.d.cts +69 -0
  86. package/dist/playwright.d.ts +69 -0
  87. package/dist/playwright.js +131 -0
  88. package/dist/query.cjs +1152 -0
  89. package/dist/query.d.cts +47 -0
  90. package/dist/query.d.ts +47 -0
  91. package/dist/query.js +1146 -0
  92. package/dist/snapshot/capture.cjs +80 -0
  93. package/dist/snapshot/capture.d.cts +34 -0
  94. package/dist/snapshot/capture.d.ts +34 -0
  95. package/dist/snapshot/capture.js +77 -0
  96. package/dist/snapshot/cdp.cjs +450 -0
  97. package/dist/snapshot/cdp.d.cts +36 -0
  98. package/dist/snapshot/cdp.d.ts +36 -0
  99. package/dist/snapshot/cdp.js +447 -0
  100. package/dist/snapshot/clip.cjs +100 -0
  101. package/dist/snapshot/clip.d.cts +26 -0
  102. package/dist/snapshot/clip.d.ts +26 -0
  103. package/dist/snapshot/clip.js +96 -0
  104. package/dist/snapshot/collect.cjs +358 -0
  105. package/dist/snapshot/collect.d.cts +18 -0
  106. package/dist/snapshot/collect.d.ts +18 -0
  107. package/dist/snapshot/collect.js +355 -0
  108. package/dist/snapshot/color.cjs +1039 -0
  109. package/dist/snapshot/color.d.cts +316 -0
  110. package/dist/snapshot/color.d.ts +316 -0
  111. package/dist/snapshot/color.js +1026 -0
  112. package/dist/snapshot/facts.cjs +167 -0
  113. package/dist/snapshot/facts.d.cts +48 -0
  114. package/dist/snapshot/facts.d.ts +48 -0
  115. package/dist/snapshot/facts.js +161 -0
  116. package/dist/snapshot/limits.cjs +69 -0
  117. package/dist/snapshot/limits.d.cts +59 -0
  118. package/dist/snapshot/limits.d.ts +59 -0
  119. package/dist/snapshot/limits.js +65 -0
  120. package/dist/snapshot/not-measured.cjs +44 -0
  121. package/dist/snapshot/not-measured.d.cts +14 -0
  122. package/dist/snapshot/not-measured.d.ts +14 -0
  123. package/dist/snapshot/not-measured.js +41 -0
  124. package/dist/snapshot/types.cjs +2 -0
  125. package/dist/snapshot/types.d.cts +238 -0
  126. package/dist/snapshot/types.d.ts +238 -0
  127. package/dist/snapshot/types.js +1 -0
  128. package/dist/spec/index.cjs +9 -0
  129. package/dist/spec/index.d.cts +15 -0
  130. package/dist/spec/index.d.ts +15 -0
  131. package/dist/spec/index.js +6 -0
  132. package/package.json +141 -0
  133. package/skills/dnumb/SKILL.md +136 -0
package/dist/bundle.js ADDED
@@ -0,0 +1,175 @@
1
+ import { SPEC_VERSION } from "./spec/index.js";
2
+ /**
3
+ * The four color-capture string fields and the two overlap-signal fields are
4
+ * required on `ElementRecord` (`snapshot/types.ts`) -- both real capture
5
+ * paths always set all six, the same as `rect` or `overflowX`. A bundle
6
+ * written before they existed simply has no key for them at all in its JSON,
7
+ * though, which is not the same violation as a caller hand-building a record
8
+ * and skipping one: this is a format-migration problem, not a construction
9
+ * error, so it gets a format-migration fix here rather than the clear thrown
10
+ * error `snapshot/color.ts#requireColorField` gives a hand-built record that
11
+ * is missing a required field. Reading `undefined.trim()` or
12
+ * `undefined.indexOf(...)` off an unmigrated old record would throw deep
13
+ * inside `snapshot/color.ts` regardless of which of the two situations
14
+ * produced it, which is exactly the failure this function exists to
15
+ * pre-empt for the bundle case specifically.
16
+ *
17
+ * `textShadow`/`textStrokeWidth`/`textStrokeColor` are backfilled with the
18
+ * same defaults a fresh capture with nothing to report would itself have
19
+ * written -- `'none'`/`'0px'`/opaque black -- because a missing value and a
20
+ * genuine "no such style" both render identically for these three (each is
21
+ * an optional counter-fact token, printed only when present; see
22
+ * `snapshot/color.ts#colorFactFor`'s tail tokens), so there is no
23
+ * fabrication risk in picking the harmless default.
24
+ *
25
+ * `backgroundImage` does NOT get that treatment (A3, fixed here): it feeds
26
+ * `resolveBackdrop`'s gradient/image detection directly, so defaulting a
27
+ * genuinely-uncaptured value to `'none'` used to manufacture the positive
28
+ * claim "this element has no background image" -- which then silently
29
+ * terminated the compositing walk and printed a specific, fabricated ratio.
30
+ * `null` here means exactly what it means on `hitStackIds`/`paintOrder`
31
+ * below: this specific capture never measured this field, and
32
+ * `snapshot/color.ts` refuses to guess past it (reports
33
+ * `unmeasured (not-captured ...)` instead) -- the same "backfill the honest
34
+ * unknown, not a false positive" discipline `NO_CAPTURE_NOT_MEASURED` below
35
+ * uses for shadow roots and iframes.
36
+ *
37
+ * Every record `parseBundle` returns satisfies the required contract by
38
+ * construction; `requireColorField` exists for the different case of a
39
+ * record that never went through this function at all.
40
+ */
41
+ function normalizeElement(raw) {
42
+ const el = raw;
43
+ return {
44
+ ...el,
45
+ backgroundImage: el.backgroundImage ?? null,
46
+ textShadow: el.textShadow ?? 'none',
47
+ textStrokeWidth: el.textStrokeWidth ?? '0px',
48
+ textStrokeColor: el.textStrokeColor ?? 'rgb(0, 0, 0)',
49
+ hitStackIds: el.hitStackIds ?? null,
50
+ paintOrder: el.paintOrder ?? null,
51
+ };
52
+ }
53
+ /**
54
+ * Stand-in for `notMeasured` when a `CaptureResult` has none -- both real
55
+ * capture paths always set it, so this only fires for a hand-built result
56
+ * that skipped them entirely (a test fixture, typically). `closed: 'unknown'`
57
+ * because that is true regardless of how the result was built; the rest
58
+ * default to zero rather than to a matching 'unknown', which is a real
59
+ * "no measurement was taken" gap this constant papers over for a
60
+ * non-production caller, not a claim about the page.
61
+ */
62
+ const NO_CAPTURE_NOT_MEASURED = {
63
+ shadowRoots: { open: 0, closed: 'unknown' },
64
+ iframes: { sameOrigin: 0, crossOrigin: 0, unknownOrigin: 0 },
65
+ };
66
+ export function buildBundle(capture, options) {
67
+ return {
68
+ spec: SPEC_VERSION,
69
+ name: options.name,
70
+ url: options.url,
71
+ capturedAt: options.capturedAt,
72
+ viewport: options.viewportName
73
+ ? { name: options.viewportName, ...capture.viewport }
74
+ : capture.viewport,
75
+ strategy: capture.strategy,
76
+ truncated: capture.truncated,
77
+ ...(capture.cdpFellBack ? { cdpFellBack: true } : {}),
78
+ notMeasured: capture.notMeasured ?? NO_CAPTURE_NOT_MEASURED,
79
+ documentBackground: capture.documentBackground ?? null,
80
+ elements: capture.elements,
81
+ ...(options.labels ? { labels: options.labels } : {}),
82
+ };
83
+ }
84
+ export class BundleFormatError extends Error {
85
+ name = 'BundleFormatError';
86
+ }
87
+ /**
88
+ * Parse and validate. Structural, not schema-library: the check exists so a
89
+ * consumer gets "this is not a dnumb bundle" rather than `undefined is not an
90
+ * object` fourteen frames deep, and that does not need a dependency.
91
+ */
92
+ export function parseBundle(text, source = '<input>') {
93
+ let value;
94
+ try {
95
+ value = JSON.parse(text);
96
+ }
97
+ catch (error) {
98
+ throw new BundleFormatError(`${source} is not valid JSON: ${error instanceof Error ? error.message : String(error)}`);
99
+ }
100
+ if (typeof value !== 'object' || value === null) {
101
+ throw new BundleFormatError(`${source} is not a dnumb bundle (expected a JSON object)`);
102
+ }
103
+ const bundle = value;
104
+ if (typeof bundle.spec !== 'number') {
105
+ throw new BundleFormatError(`${source} is not a dnumb bundle (no "spec" field)`);
106
+ }
107
+ if (bundle.spec > SPEC_VERSION) {
108
+ throw new BundleFormatError(`${source} was written by a newer dnumb (spec ${bundle.spec}; this build reads ${SPEC_VERSION}). Upgrade dnumb.`);
109
+ }
110
+ if (!Array.isArray(bundle.elements)) {
111
+ throw new BundleFormatError(`${source} has no "elements" array`);
112
+ }
113
+ if (typeof bundle.viewport !== 'object' || bundle.viewport === null) {
114
+ throw new BundleFormatError(`${source} has no "viewport"`);
115
+ }
116
+ // `Bundle.strategy`'s own doc comment: "Recorded, never inferred" (B4).
117
+ // `bundle.strategy ?? 'walk'` used to assert a specific capture path a
118
+ // bundle never actually recorded -- a `cdp` bundle whose `"strategy"` key
119
+ // happened to be missing (an old writer, a hand-edited file) would parse
120
+ // back claiming to be `walk`, which is a different, real fact this bundle
121
+ // never measured. Neither value is an honest default for "don't know", so
122
+ // this refuses to parse rather than guess one -- the same register the
123
+ // `notMeasured` check just below uses for the identical shape of problem.
124
+ if (bundle.strategy !== 'cdp' && bundle.strategy !== 'walk') {
125
+ throw new BundleFormatError(`${source} has no valid "strategy" (must be "cdp" or "walk", got ` +
126
+ `${JSON.stringify(bundle.strategy)}): this build cannot honestly say which capture path ` +
127
+ 'produced these records, and defaulting to "walk" would assert a specific path this ' +
128
+ 'bundle never recorded. Re-capture the page with a current dnumb.');
129
+ }
130
+ // `NotMeasured`'s own doc comment: "every other field here is always a
131
+ // real, counted number." `NO_CAPTURE_NOT_MEASURED`'s zeroed shape is that
132
+ // promise's one sanctioned exception, and it is scoped narrowly -- "a
133
+ // hand-built [CaptureResult] that skipped them entirely (a test fixture,
134
+ // typically)", i.e. `buildBundle`'s in-memory case just above, where no
135
+ // capture happened at all and nothing claims otherwise. A bundle read back
136
+ // from disk is different: it is a *real* prior capture, and reusing that
137
+ // same all-zero shape for one that predates `notMeasured` used to print a
138
+ // specific, confident zero cross-origin-iframe count a page might not
139
+ // actually have (B4) -- exactly the "always a real, counted number"
140
+ // promise broken. There is no honest partial value this format can hand
141
+ // back for that case (unlike `strategy`, `NotMeasured`'s numeric fields
142
+ // have no `| undefined`/`'unknown'` escape hatch), so this refuses outright
143
+ // rather than fabricate one: re-capture with a current dnumb instead.
144
+ if (bundle.notMeasured === undefined) {
145
+ throw new BundleFormatError(`${source} predates capture accounting ("notMeasured") and cannot be read by this build: ` +
146
+ 'it has no honest way to report shadow-DOM/iframe coverage for a bundle this old, and ' +
147
+ 'silently reporting zero would claim a specific count this bundle never measured. ' +
148
+ 're-capture the page with a current dnumb to get a bundle this build can read.');
149
+ }
150
+ return {
151
+ // The value this bundle actually claims, preserved -- never rewritten to
152
+ // this build's own `SPEC_VERSION` (B4). Already validated above: a
153
+ // number, and no greater than `SPEC_VERSION`.
154
+ spec: bundle.spec,
155
+ name: bundle.name ?? 'capture',
156
+ url: bundle.url ?? '',
157
+ capturedAt: bundle.capturedAt ?? '',
158
+ viewport: bundle.viewport,
159
+ // Already validated above: exactly `'cdp'` or `'walk'`, never inferred.
160
+ strategy: bundle.strategy,
161
+ truncated: bundle.truncated ?? false,
162
+ ...(bundle.cdpFellBack ? { cdpFellBack: true } : {}),
163
+ notMeasured: bundle.notMeasured,
164
+ // `null` on a bundle old enough to predate color capture entirely (before
165
+ // `documentBackground` existed) -- an honest "never measured", not a
166
+ // guessed canvas color. See `DocumentBackground`.
167
+ documentBackground: bundle.documentBackground ?? null,
168
+ elements: bundle.elements.map(normalizeElement),
169
+ ...(bundle.labels ? { labels: bundle.labels } : {}),
170
+ };
171
+ }
172
+ /** The bundle as it is written to disk. Stable key order, one trailing newline. */
173
+ export function serializeBundle(bundle) {
174
+ return `${JSON.stringify(bundle, null, 2)}\n`;
175
+ }