@augurworks/augur 0.15.1
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/CANVAS.md +858 -0
- package/INSTALL.md +603 -0
- package/LICENSE +27 -0
- package/README.md +150 -0
- package/TRADEMARKS.md +72 -0
- package/agents/README.md +65 -0
- package/agents/canon-extract.md +117 -0
- package/agents/canon.md +151 -0
- package/agents/canvas.md +88 -0
- package/agents/currency.md +74 -0
- package/agents/drafts.md +92 -0
- package/agents/identity.md +148 -0
- package/agents/prototype-contract.md +69 -0
- package/agents/publishing.md +324 -0
- package/agents/review-feedback.md +76 -0
- package/agents/space-json.md +132 -0
- package/agents/ui-skill.md +101 -0
- package/agents/working-marks.md +86 -0
- package/brand/augur-eye.svg +3 -0
- package/brand/augur-icon.svg +41 -0
- package/brand/augur-mark.svg +5 -0
- package/brand/lentianova-bulky.otf +0 -0
- package/brand/lentianova-median.otf +0 -0
- package/brand/lentianova.woff2 +0 -0
- package/build.js +8409 -0
- package/changelog.md +133 -0
- package/fonts/inter-latin-wght-normal.woff2 +0 -0
- package/package.json +57 -0
- package/pitis/PETS.md +121 -0
- package/pitis/README.md +117 -0
- package/pitis/index.html +266 -0
- package/pitis/piti.build.js +81 -0
- package/pitis/piti.js +929 -0
- package/pitis/roast-agent.md +185 -0
- package/realtime/src/index.js +119 -0
- package/realtime/wrangler.example.toml +44 -0
- package/scripts/adopt.mjs +106 -0
- package/scripts/board-snapshot.mjs +197 -0
- package/scripts/build-seed-pack.mjs +33 -0
- package/scripts/bundle-rekey.mjs +112 -0
- package/scripts/bundle-tenancy-rehearsal.mjs +781 -0
- package/scripts/canon-extract.mjs +578 -0
- package/scripts/canon.mjs +599 -0
- package/scripts/canvas-screen.mjs +333 -0
- package/scripts/check-local.mjs +136 -0
- package/scripts/clawd-art.mjs +75 -0
- package/scripts/clawd-canvas.mjs +741 -0
- package/scripts/cli.mjs +100 -0
- package/scripts/clone.mjs +422 -0
- package/scripts/close.mjs +30 -0
- package/scripts/connect.mjs +103 -0
- package/scripts/deploy.mjs +157 -0
- package/scripts/dev.mjs +54 -0
- package/scripts/doc-lint.mjs +143 -0
- package/scripts/export.mjs +300 -0
- package/scripts/fork.mjs +64 -0
- package/scripts/freeze.mjs +74 -0
- package/scripts/frontdoor-parity.mjs +180 -0
- package/scripts/hook.mjs +67 -0
- package/scripts/identity-rekey.mjs +112 -0
- package/scripts/init.mjs +122 -0
- package/scripts/land.mjs +40 -0
- package/scripts/lib/adapters.mjs +156 -0
- package/scripts/lib/board-snapshot.mjs +511 -0
- package/scripts/lib/draft.mjs +385 -0
- package/scripts/lib/graduate.mjs +276 -0
- package/scripts/lib/instance.mjs +74 -0
- package/scripts/lib/marks.mjs +107 -0
- package/scripts/lib/materialize.mjs +138 -0
- package/scripts/lib/merge3.mjs +124 -0
- package/scripts/lib/offline-posture.mjs +120 -0
- package/scripts/lib/offline-respawn.mjs +18 -0
- package/scripts/lib/offline-wrangler.mjs +78 -0
- package/scripts/lib/publish-compose.mjs +12 -0
- package/scripts/lib/publish-conflict.mjs +78 -0
- package/scripts/lib/publish-evidence.mjs +144 -0
- package/scripts/lib/refine-compare.mjs +140 -0
- package/scripts/lib/refine-ledger.mjs +150 -0
- package/scripts/lib/refine-png.mjs +149 -0
- package/scripts/lib/seed-pack-build.mjs +141 -0
- package/scripts/lib/state-compare.mjs +68 -0
- package/scripts/lib/store.mjs +122 -0
- package/scripts/login.mjs +84 -0
- package/scripts/mark.mjs +112 -0
- package/scripts/migrate-board-keys.mjs +164 -0
- package/scripts/migrate.mjs +265 -0
- package/scripts/no-foreign-vocabulary.mjs +216 -0
- package/scripts/no-product-names.mjs +157 -0
- package/scripts/no-tenant-globals.mjs +1134 -0
- package/scripts/offline.mjs +247 -0
- package/scripts/og.mjs +170 -0
- package/scripts/one-tenant-resolver.mjs +146 -0
- package/scripts/open.mjs +63 -0
- package/scripts/publish.mjs +1041 -0
- package/scripts/read.mjs +31 -0
- package/scripts/refine.mjs +455 -0
- package/scripts/release-drift.mjs +138 -0
- package/scripts/restore.mjs +322 -0
- package/scripts/review.mjs +125 -0
- package/scripts/save.mjs +21 -0
- package/scripts/shell-lint.mjs +189 -0
- package/scripts/ship.mjs +460 -0
- package/scripts/shoot.mjs +264 -0
- package/scripts/smoke-invite.mjs +113 -0
- package/scripts/state-inventory.mjs +223 -0
- package/scripts/status.mjs +177 -0
- package/scripts/sync.mjs +27 -0
- package/scripts/tenant-do-rehearsal.mjs +1314 -0
- package/scripts/ui-copy-lint.mjs +202 -0
- package/scripts/watch.mjs +32 -0
- package/scripts/wrangler-preflight.mjs +246 -0
- package/seed/CANON.md +43 -0
- package/seed/README.md +119 -0
- package/seed/prototype-status.json +9 -0
- package/seed/registry.json +88 -0
- package/seed/skills/starter-ui/SKILL.md +52 -0
- package/seed/skills/starter-ui/skill.json +11 -0
- package/seed/skills/starter-ui/starter-tokens.css +73 -0
- package/seed/skills/starter-ui/starter-ui.css +318 -0
- package/seed/skills/starter-ui/starter-ui.js +50 -0
- package/seed/space.json +5 -0
- package/seed/start-here/prototypes/connect-your-terminal/index.html +428 -0
- package/seed/start-here/prototypes/connect-your-terminal/preview.webp +0 -0
- package/seed/start-here/prototypes/sample-with-comments/index.html +172 -0
- package/seed/start-here/prototypes/sample-with-comments/preview.webp +0 -0
- package/seed/start-here/prototypes/set-up-your-design-system/index.html +353 -0
- package/seed/start-here/prototypes/set-up-your-design-system/preview.webp +0 -0
- package/seed/threads.json +52 -0
- package/seed/worked-examples/prototypes/field-readings/index.html +273 -0
- package/seed/worked-examples/prototypes/field-readings/preview.webp +0 -0
- package/seed/worked-examples/prototypes/slide-deck/index.html +214 -0
- package/seed/worked-examples/prototypes/slide-deck/preview.webp +0 -0
- package/seed/worked-examples/prototypes/specimen-viewer/index.html +203 -0
- package/seed/worked-examples/prototypes/specimen-viewer/preview.webp +0 -0
- package/src/_worker.js +12668 -0
- package/src/board-key.mjs +57 -0
- package/src/board-room.mjs +881 -0
- package/src/bundle-keys.mjs +138 -0
- package/src/canon/collect-in-browser.js +215 -0
- package/src/canon/emit.mjs +232 -0
- package/src/canon/observe.mjs +260 -0
- package/src/canon/schema.mjs +451 -0
- package/src/canvas/DSEG-LICENSE.txt +95 -0
- package/src/canvas/DSEG7Classic-Bold.woff2 +0 -0
- package/src/canvas/canvas.css +885 -0
- package/src/canvas/canvas.js +5780 -0
- package/src/canvas/capture.js +485 -0
- package/src/chrome/appchrome.mjs +568 -0
- package/src/currency.mjs +203 -0
- package/src/delete-confirmation.mjs +254 -0
- package/src/drafts/drafts.js +326 -0
- package/src/entry.js +58 -0
- package/src/galleries.mjs +400 -0
- package/src/health-cron.mjs +173 -0
- package/src/identity.json +1 -0
- package/src/kv-codec.mjs +135 -0
- package/src/kv-identity.mjs +259 -0
- package/src/mail.mjs +599 -0
- package/src/provenance.mjs +74 -0
- package/src/publish-compose.mjs +363 -0
- package/src/publish-fork.mjs +221 -0
- package/src/publish-units.mjs +44 -0
- package/src/purge.mjs +81 -0
- package/src/review/COMMENTING-UX.md +306 -0
- package/src/review/cat.png +0 -0
- package/src/review/comment-cursor.svg +7 -0
- package/src/review/comments.js +1738 -0
- package/src/room-ticket.mjs +96 -0
- package/src/roster-chip.mjs +32 -0
- package/src/seed-pack.mjs +261 -0
- package/src/state-inventory.mjs +249 -0
- package/src/sw-logic.mjs +48 -0
- package/src/tenant-cache.mjs +141 -0
- package/src/tenant-context.mjs +386 -0
- package/src/tenant-do.js +2940 -0
- package/src/tenant-host.mjs +144 -0
- package/src/tenant-quotas.mjs +140 -0
- package/src/unit-core.mjs +103 -0
- package/src/unit-object.mjs +338 -0
- package/templates/README.md +67 -0
- package/templates/shell/deploy.yml +145 -0
- package/templates/shell/engine-bump.yml +142 -0
- package/templates/shell/health.yml +233 -0
- package/templates/shell/kv-backup.yml +201 -0
- package/templates/shell/roster-update.yml +110 -0
- package/templates/shell/space-preflight.yml +35 -0
- package/templates/shell/space-rebake.yml +79 -0
- package/templates/shell/store-backup.yml +152 -0
- package/templates/shell/wrangler.example.toml +266 -0
- package/templates/space/publish.yml +91 -0
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* canon/observe — turn a real product's pages into EVIDENCE, and stop there.
|
|
3
|
+
*
|
|
4
|
+
* This is the half of extraction a machine can do honestly: count what a product
|
|
5
|
+
* actually uses. Which of the eleven greys is the hairline, and which single colour is
|
|
6
|
+
* the one a person is meant to act on, is judgement — that belongs to the user's own
|
|
7
|
+
* agent, working from this file's output. Nothing here decides anything.
|
|
8
|
+
*
|
|
9
|
+
* TWO DOORS, ONE SHAPE. A public page can be read over plain HTTP, which is what this
|
|
10
|
+
* module does. A product behind a login cannot, and that is the case that matters — the
|
|
11
|
+
* person holding the login is a designer or a PM, not someone with repo access. For them
|
|
12
|
+
* the collector runs INSIDE their own already-signed-in browser
|
|
13
|
+
* (`collect-in-browser.js`) and emits this same object, so everything downstream is
|
|
14
|
+
* identical whichever door was used. The browser door is also the better evidence:
|
|
15
|
+
* computed styles are what the product actually renders, after cascade, media queries
|
|
16
|
+
* and whatever the framework did at runtime.
|
|
17
|
+
*
|
|
18
|
+
* The output is `observation.json`, version 1. Every list is ranked most-used first,
|
|
19
|
+
* and every entry keeps its count so a reader can see how thin the tail is.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
export const OBSERVATION_VERSION = 1;
|
|
23
|
+
|
|
24
|
+
const COLOR_PROPS = new Set([
|
|
25
|
+
"color", "background", "background-color", "border-color", "border-top-color",
|
|
26
|
+
"border-right-color", "border-bottom-color", "border-left-color", "outline-color",
|
|
27
|
+
"fill", "stroke", "caret-color", "text-decoration-color", "accent-color",
|
|
28
|
+
]);
|
|
29
|
+
const SPACE_PROPS = new Set([
|
|
30
|
+
"padding", "padding-top", "padding-right", "padding-bottom", "padding-left",
|
|
31
|
+
"margin", "margin-top", "margin-right", "margin-bottom", "margin-left",
|
|
32
|
+
"gap", "row-gap", "column-gap", "grid-gap",
|
|
33
|
+
]);
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* @param {{url?:string, html?:string, sheets?:Array<{href?:string,text:string}>}} input
|
|
37
|
+
* @returns {object} observation
|
|
38
|
+
*/
|
|
39
|
+
export function observe({ url = "", html = "", sheets = [], collectedAt = new Date().toISOString() } = {}) {
|
|
40
|
+
const colors = new Tally();
|
|
41
|
+
const fontStacks = new Tally();
|
|
42
|
+
const fontSizes = new Tally();
|
|
43
|
+
const lineHeights = new Tally();
|
|
44
|
+
const fontWeights = new Tally();
|
|
45
|
+
const spacings = new Tally();
|
|
46
|
+
const radii = new Tally();
|
|
47
|
+
const shadows = new Tally();
|
|
48
|
+
const motions = new Tally();
|
|
49
|
+
const vars = new Map();
|
|
50
|
+
let rules = 0, declarations = 0;
|
|
51
|
+
|
|
52
|
+
const eat = (prop, value, where) => {
|
|
53
|
+
declarations++;
|
|
54
|
+
const p = prop.toLowerCase();
|
|
55
|
+
const v = value.trim();
|
|
56
|
+
if (!v || v.startsWith("var(")) { if (p.startsWith("--")) vars.set(p, v); return; }
|
|
57
|
+
if (p.startsWith("--")) { vars.set(p, v); return; }
|
|
58
|
+
if (COLOR_PROPS.has(p)) for (const c of colorsIn(v)) colors.add(c, where, p);
|
|
59
|
+
else if (p === "font" || p === "font-family") { const f = familyOf(v); if (f) fontStacks.add(f, where, p); }
|
|
60
|
+
if (p === "font-size" || p === "font") { const s = sizeOf(v); if (s) fontSizes.add(s, where, p); }
|
|
61
|
+
if (p === "line-height") lineHeights.add(v, where, p);
|
|
62
|
+
if (p === "font-weight" && /^\d+$|^(bold|normal|lighter|bolder)$/.test(v)) fontWeights.add(v, where, p);
|
|
63
|
+
if (SPACE_PROPS.has(p)) for (const l of lengthsIn(v)) spacings.add(l, where, p);
|
|
64
|
+
if (p === "border-radius" || p.startsWith("border-") && p.endsWith("-radius")) for (const l of lengthsIn(v)) radii.add(l, where, p);
|
|
65
|
+
if (p === "box-shadow" && v !== "none") shadows.add(v, where, p);
|
|
66
|
+
if (p === "transition" || p === "transition-duration" || p === "animation-duration") motions.add(v, where, p);
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
for (const sheet of sheets) {
|
|
70
|
+
for (const rule of cssRules(sheet.text || "")) {
|
|
71
|
+
rules++;
|
|
72
|
+
for (const [prop, value] of declsOf(rule.body)) eat(prop, value, rule.selector);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
// Inline style attributes: a product's most load-bearing colours are often here.
|
|
76
|
+
for (const m of String(html).matchAll(/\sstyle\s*=\s*"([^"]*)"/gi)) {
|
|
77
|
+
for (const [prop, value] of declsOf(m[1])) eat(prop, value, "[inline]");
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return {
|
|
81
|
+
observationVersion: OBSERVATION_VERSION,
|
|
82
|
+
source: { url, collectedAt, how: "fetch", pages: url ? [url] : [] },
|
|
83
|
+
stats: { sheets: sheets.length, rules, declarations, elements: 0 },
|
|
84
|
+
colors: colors.ranked(colorEntry),
|
|
85
|
+
fontStacks: fontStacks.ranked(),
|
|
86
|
+
fontSizes: fontSizes.ranked(sizeEntry),
|
|
87
|
+
lineHeights: lineHeights.ranked(),
|
|
88
|
+
fontWeights: fontWeights.ranked(),
|
|
89
|
+
spacings: spacings.ranked(sizeEntry),
|
|
90
|
+
radii: radii.ranked(sizeEntry),
|
|
91
|
+
shadows: shadows.ranked(),
|
|
92
|
+
motions: motions.ranked(),
|
|
93
|
+
customProperties: [...vars.entries()].map(([name, value]) => ({ name, value })).sort((a, b) => a.name.localeCompare(b.name)),
|
|
94
|
+
classFamilies: classFamilies(html),
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/* ── Tally ─────────────────────────────────────────────────────────────────── */
|
|
99
|
+
|
|
100
|
+
class Tally {
|
|
101
|
+
constructor() { this.m = new Map(); }
|
|
102
|
+
add(value, where, prop) {
|
|
103
|
+
let e = this.m.get(value);
|
|
104
|
+
if (!e) this.m.set(value, (e = { value, count: 0, props: new Set(), where: [] }));
|
|
105
|
+
e.count++;
|
|
106
|
+
e.props.add(prop);
|
|
107
|
+
if (e.where.length < 4 && where && !e.where.includes(where)) e.where.push(where.slice(0, 90));
|
|
108
|
+
}
|
|
109
|
+
ranked(decorate) {
|
|
110
|
+
return [...this.m.values()]
|
|
111
|
+
.sort((a, b) => b.count - a.count || a.value.localeCompare(b.value))
|
|
112
|
+
.slice(0, 60)
|
|
113
|
+
.map((e) => {
|
|
114
|
+
const out = { value: e.value, count: e.count, props: [...e.props].slice(0, 6), where: e.where };
|
|
115
|
+
return decorate ? decorate(out) : out;
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function colorEntry(e) {
|
|
121
|
+
// Deliberately NOT normalised away: the agent should see the product's own spelling.
|
|
122
|
+
return e;
|
|
123
|
+
}
|
|
124
|
+
function sizeEntry(e) { return e; }
|
|
125
|
+
|
|
126
|
+
/* ── A tolerant CSS reader ─────────────────────────────────────────────────── */
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Split a stylesheet into `{selector, body}` rules. Handles nesting (`@media`, `@supports`,
|
|
130
|
+
* `@layer`) by recursing, skips at-rules with no block, and never throws on malformed
|
|
131
|
+
* input — a real product's CSS is minified, vendored and occasionally broken, and a
|
|
132
|
+
* parser that gives up on the first surprise reads nothing at all.
|
|
133
|
+
*/
|
|
134
|
+
export function cssRules(text) {
|
|
135
|
+
const out = [];
|
|
136
|
+
const src = String(text).replace(/\/\*[\s\S]*?\*\//g, "");
|
|
137
|
+
let i = 0;
|
|
138
|
+
const parse = (from, to) => {
|
|
139
|
+
let start = from;
|
|
140
|
+
for (let j = from; j < to; j++) {
|
|
141
|
+
const ch = src[j];
|
|
142
|
+
if (ch === "{") {
|
|
143
|
+
let depth = 1, k = j + 1;
|
|
144
|
+
while (k < to && depth) { if (src[k] === "{") depth++; else if (src[k] === "}") depth--; k++; }
|
|
145
|
+
const selector = src.slice(start, j).trim();
|
|
146
|
+
const body = src.slice(j + 1, k - 1);
|
|
147
|
+
if (/^@(media|supports|layer|container|scope|document)/i.test(selector)) parse(j + 1, k - 1);
|
|
148
|
+
else if (/^@(keyframes|font-face|property|page|counter-style)/i.test(selector)) out.push({ selector, body });
|
|
149
|
+
else if (selector) out.push({ selector, body });
|
|
150
|
+
j = k - 1;
|
|
151
|
+
start = k;
|
|
152
|
+
} else if (ch === ";" && start === j) start = j + 1;
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
parse(i, src.length);
|
|
156
|
+
return out;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/** Split a declaration block into `[prop, value]` pairs, respecting parens and strings. */
|
|
160
|
+
export function declsOf(body) {
|
|
161
|
+
const out = [];
|
|
162
|
+
let buf = "", depth = 0, quote = "";
|
|
163
|
+
const flush = () => {
|
|
164
|
+
const s = buf.trim(); buf = "";
|
|
165
|
+
if (!s) return;
|
|
166
|
+
const c = s.indexOf(":");
|
|
167
|
+
if (c < 1) return;
|
|
168
|
+
const prop = s.slice(0, c).trim();
|
|
169
|
+
const value = s.slice(c + 1).trim();
|
|
170
|
+
if (prop && value && !prop.includes("{")) out.push([prop, value]);
|
|
171
|
+
};
|
|
172
|
+
for (const ch of String(body)) {
|
|
173
|
+
if (quote) { buf += ch; if (ch === quote) quote = ""; continue; }
|
|
174
|
+
if (ch === '"' || ch === "'") { quote = ch; buf += ch; continue; }
|
|
175
|
+
if (ch === "(") depth++;
|
|
176
|
+
if (ch === ")") depth = Math.max(0, depth - 1);
|
|
177
|
+
if (ch === ";" && !depth) { flush(); continue; }
|
|
178
|
+
if (ch === "{" || ch === "}") { buf = ""; depth = 0; continue; }
|
|
179
|
+
buf += ch;
|
|
180
|
+
}
|
|
181
|
+
flush();
|
|
182
|
+
return out;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const COLOR_RE = /#[0-9a-fA-F]{3,8}\b|\b(?:rgba?|hsla?|oklch|oklab|lab|lch|color)\([^)]*\)|\b(?:black|white|red|green|blue|gray|grey|silver|maroon|navy|olive|purple|teal|aqua|fuchsia|lime|yellow|orange)\b/g;
|
|
186
|
+
export function colorsIn(value) { return [...String(value).matchAll(COLOR_RE)].map((m) => m[0]); }
|
|
187
|
+
|
|
188
|
+
const LEN_RE = /(?<![\w.#-])(-?[\d.]+)(px|rem|em|ch|vh|vw|%)/g;
|
|
189
|
+
export function lengthsIn(value) {
|
|
190
|
+
return [...String(value).matchAll(LEN_RE)].map((m) => m[1] + m[2]).filter((v) => !v.startsWith("-"));
|
|
191
|
+
}
|
|
192
|
+
function sizeOf(value) { return lengthsIn(value)[0] || null; }
|
|
193
|
+
function familyOf(value) {
|
|
194
|
+
const v = String(value).replace(/^[^a-zA-Z"']*/, "").trim();
|
|
195
|
+
return v && !v.startsWith("var(") ? v.slice(0, 160) : null;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Class-name families in the markup: the candidate component list. A family is the first
|
|
200
|
+
* one or two hyphen-segments of a class, ranked by how many distinct classes share it —
|
|
201
|
+
* so a design system's own namespace surfaces above one-off utility classes.
|
|
202
|
+
*/
|
|
203
|
+
export function classFamilies(html) {
|
|
204
|
+
const fams = new Map();
|
|
205
|
+
for (const m of String(html).matchAll(/\sclass\s*=\s*"([^"]*)"/gi)) {
|
|
206
|
+
for (const cl of m[1].split(/\s+/)) {
|
|
207
|
+
if (!/^[a-zA-Z][\w-]{2,}$/.test(cl)) continue;
|
|
208
|
+
const parts = cl.split(/[-_]/);
|
|
209
|
+
if (parts.length < 2) continue;
|
|
210
|
+
const root = parts.slice(0, 2).join("-");
|
|
211
|
+
let e = fams.get(root);
|
|
212
|
+
if (!e) fams.set(root, (e = { root, members: new Set(), uses: 0 }));
|
|
213
|
+
e.members.add(cl);
|
|
214
|
+
e.uses++;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
return [...fams.values()]
|
|
218
|
+
.filter((e) => e.uses > 1)
|
|
219
|
+
.sort((a, b) => b.members.size - a.members.size || b.uses - a.uses)
|
|
220
|
+
.slice(0, 40)
|
|
221
|
+
.map((e) => ({ root: e.root, uses: e.uses, members: [...e.members].slice(0, 10) }));
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/* ── Merging several pages ─────────────────────────────────────────────────── */
|
|
225
|
+
|
|
226
|
+
/** Fold observations of several pages into one. One page is never a design system. */
|
|
227
|
+
export function mergeObservations(list) {
|
|
228
|
+
const [first, ...rest] = list;
|
|
229
|
+
if (!first) throw new Error("nothing to merge");
|
|
230
|
+
if (!rest.length) return first;
|
|
231
|
+
const out = JSON.parse(JSON.stringify(first));
|
|
232
|
+
const LISTS = ["colors", "fontStacks", "fontSizes", "lineHeights", "fontWeights", "spacings", "radii", "shadows", "motions"];
|
|
233
|
+
for (const o of rest) {
|
|
234
|
+
out.source.pages = [...new Set([...(out.source.pages || []), ...(o.source?.pages || [])])];
|
|
235
|
+
for (const k of ["sheets", "rules", "declarations", "elements"]) out.stats[k] = (out.stats[k] || 0) + (o.stats?.[k] || 0);
|
|
236
|
+
for (const key of LISTS) {
|
|
237
|
+
const by = new Map((out[key] || []).map((e) => [e.value, e]));
|
|
238
|
+
for (const e of o[key] || []) {
|
|
239
|
+
const hit = by.get(e.value);
|
|
240
|
+
if (hit) {
|
|
241
|
+
hit.count += e.count;
|
|
242
|
+
if (e.area != null) hit.area = (hit.area || 0) + e.area;
|
|
243
|
+
hit.props = [...new Set([...(hit.props || []), ...(e.props || [])])].slice(0, 6);
|
|
244
|
+
} else { by.set(e.value, { ...e }); }
|
|
245
|
+
}
|
|
246
|
+
out[key] = [...by.values()].sort((a, b) => (b.area || 0) - (a.area || 0) || b.count - a.count).slice(0, 60);
|
|
247
|
+
}
|
|
248
|
+
const props = new Map((out.customProperties || []).map((p) => [p.name, p]));
|
|
249
|
+
for (const p of o.customProperties || []) if (!props.has(p.name)) props.set(p.name, p);
|
|
250
|
+
out.customProperties = [...props.values()].sort((a, b) => a.name.localeCompare(b.name));
|
|
251
|
+
const fams = new Map((out.classFamilies || []).map((f) => [f.root, f]));
|
|
252
|
+
for (const f of o.classFamilies || []) {
|
|
253
|
+
const hit = fams.get(f.root);
|
|
254
|
+
if (hit) { hit.uses += f.uses; hit.members = [...new Set([...hit.members, ...f.members])].slice(0, 10); }
|
|
255
|
+
else fams.set(f.root, { ...f });
|
|
256
|
+
}
|
|
257
|
+
out.classFamilies = [...fams.values()].sort((a, b) => b.members.length - a.members.length || b.uses - a.uses).slice(0, 40);
|
|
258
|
+
}
|
|
259
|
+
return out;
|
|
260
|
+
}
|