@tangle-network/brand 1.17.1 → 1.18.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 +8 -0
- package/bin/drift-baseline.json +1158 -113
- package/bin/tangle-drift.mjs +46 -9
- package/package.json +1 -1
- package/src/styles/legacy-light.css +16 -1
- package/src/styles/named-themes.css +8 -0
- package/src/styles/theme.css +17 -0
- package/src/styles/tokens.css +11 -0
package/bin/tangle-drift.mjs
CHANGED
|
@@ -3,8 +3,10 @@
|
|
|
3
3
|
* tangle-drift: a ratchet on design-system drift across Tangle product surfaces.
|
|
4
4
|
*
|
|
5
5
|
* It counts raw Tailwind palette classes, hex literals, arbitrary color values,
|
|
6
|
-
* CSS custom-property definitions
|
|
7
|
-
*
|
|
6
|
+
* CSS custom-property definitions, locally defined primitives, and the sizing and
|
|
7
|
+
* type deviations that bypass the shared control scale (literal font sizes and
|
|
8
|
+
* heights, native controls, restyled shared controls) in a surface's source,
|
|
9
|
+
* reading a git tree with `git grep` (no checkout of the tree needed).
|
|
8
10
|
* The counts committed in drift-baseline.json beside this file may only fall.
|
|
9
11
|
*
|
|
10
12
|
* tangle-drift check --surface gtm --repo-dir . # consumer gate, reads HEAD
|
|
@@ -32,6 +34,7 @@ export const SURFACES = {
|
|
|
32
34
|
gtm: { repo: "tangle-network/gtm-agent", roots: [""] },
|
|
33
35
|
tax: { repo: "tangle-network/tax-agent", roots: ["apps/web"] },
|
|
34
36
|
legal: { repo: "tangle-network/legal-agent", roots: [""] },
|
|
37
|
+
insurance: { repo: "tangle-network/insurance-agent", roots: [""] },
|
|
35
38
|
creative: { repo: "tangle-network/creative-agent", roots: [""] },
|
|
36
39
|
physim: { repo: "tangle-network/physim", roots: ["apps/web"] },
|
|
37
40
|
hospitality: { repo: "tangle-network/hospitality-agent", roots: [""] },
|
|
@@ -55,6 +58,17 @@ export const PATTERNS = {
|
|
|
55
58
|
arbitrary_color: String.raw`-\[(?:#|rgb|hsl|oklch)`,
|
|
56
59
|
css_var_defs: String.raw`^\s*--[a-zA-Z0-9-]+\s*:`,
|
|
57
60
|
local_primitive_count: String.raw`export\s+(?:default\s+)?(?:function|const)\s+(?:${PRIMS})\b`,
|
|
61
|
+
// Sizes and type that bypass Brand's control and type scale. Token references such as
|
|
62
|
+
// `text-[var(--text-control)]` and `h-[var(--control-height-md)]` are not counted.
|
|
63
|
+
// A literal Tailwind size (`text-[13px]`), an inline `fontSize: 13`, or a CSS `font-size: 13px`.
|
|
64
|
+
font_size_literal: String.raw`\btext-\[(?:[0-9.]+(?:px|rem|em)\b|calc\(|clamp\()|\bfontSize:\s*["'\x60]?[0-9.]+|font-size:\s*[0-9.]+(?:px|rem|em)\b`,
|
|
65
|
+
// A literal height or square size (`h-[34px]`, `min-h-[2.4rem]`, `size-[30px]`).
|
|
66
|
+
size_literal: String.raw`(?<![\w-])(?:h|min-h|size)-\[[0-9.]+(?:px|rem)\]`,
|
|
67
|
+
// A native control element in product code; Button, Input, Textarea and Select own them.
|
|
68
|
+
native_control: String.raw`<(?:button|input|select|textarea)\b`,
|
|
69
|
+
// A shared control resized or retyped in place. Line-based: a className on its own line
|
|
70
|
+
// after the tag is not seen, so this undercounts and only ratchets what it can see.
|
|
71
|
+
control_override: String.raw`<(?:Button|Input|Textarea|SelectTrigger)\b[^>]*?\bclassName=[^>]*?(?<![\w-])(?:h-|min-h-|size-|px-|py-|text-(?:xs|sm|base|lg|\[)|rounded-|font-(?:normal|medium|semibold|bold)\b)`,
|
|
58
72
|
};
|
|
59
73
|
const LOCAL_PRIM_NAME = new RegExp(String.raw`(?:function|const)\s+(${PRIMS})\b`);
|
|
60
74
|
const SHARED = ["@tangle-network/brand", "@tangle-network/ui", "@tangle-network/sandbox-ui", "@tangle-network/agent-app"];
|
|
@@ -70,6 +84,10 @@ function extensions(surface, metric) {
|
|
|
70
84
|
case "arbitrary_color": return ["*.tsx", "*.jsx", "*.astro", "*.ts"];
|
|
71
85
|
case "css_var_defs": return ["*.css"];
|
|
72
86
|
case "local_primitive_count": return ["*.tsx", "*.jsx"];
|
|
87
|
+
case "font_size_literal": return ["*.tsx", "*.jsx", "*.astro", "*.ts", "*.css", ...extra];
|
|
88
|
+
case "size_literal": return ["*.tsx", "*.jsx", "*.astro", "*.ts", ...extra];
|
|
89
|
+
case "native_control": return ["*.tsx", "*.jsx", "*.astro"];
|
|
90
|
+
case "control_override": return ["*.tsx", "*.jsx"];
|
|
73
91
|
case "imports": return ["*.tsx", "*.ts", "*.astro", "*.jsx"];
|
|
74
92
|
default: return SRC_EXT;
|
|
75
93
|
}
|
|
@@ -207,17 +225,23 @@ export function measure(surface, source) {
|
|
|
207
225
|
return { row, matches };
|
|
208
226
|
}
|
|
209
227
|
|
|
210
|
-
/**
|
|
228
|
+
/**
|
|
229
|
+
* Compare a measured row with its baseline row. A metric the baseline row has not recorded
|
|
230
|
+
* (one added after the row was written) is `unrecorded`, not a rise: it gates from the first
|
|
231
|
+
* baseline that records it, so a consumer's own baseline file keeps passing until refreshed.
|
|
232
|
+
*/
|
|
211
233
|
export function compare(baseline, current) {
|
|
212
234
|
const rises = [];
|
|
213
235
|
const falls = [];
|
|
236
|
+
const unrecorded = [];
|
|
214
237
|
for (const metric of GATED) {
|
|
215
|
-
const base = baseline?.[metric] ?? 0;
|
|
216
238
|
const now = current[metric];
|
|
239
|
+
if (baseline && typeof baseline[metric] !== "number") { unrecorded.push({ metric, now }); continue; }
|
|
240
|
+
const base = baseline?.[metric] ?? 0;
|
|
217
241
|
if (now > base) rises.push({ metric, base, now });
|
|
218
242
|
else if (now < base) falls.push({ metric, base, now });
|
|
219
243
|
}
|
|
220
|
-
return { rises, falls };
|
|
244
|
+
return { rises, falls, unrecorded };
|
|
221
245
|
}
|
|
222
246
|
|
|
223
247
|
/** Files whose count for `metric` rose above their baseline count, worst first. */
|
|
@@ -231,14 +255,23 @@ export function risenFiles(baseline, current, metric) {
|
|
|
231
255
|
|
|
232
256
|
/**
|
|
233
257
|
* Apply `rows` to `baseline`. Without allowRise, a row with any gated rise is refused and the
|
|
234
|
-
* baseline keeps its old row
|
|
258
|
+
* baseline keeps its old counts, but still records metrics that row had not recorded yet.
|
|
259
|
+
* Returns { next, refused }.
|
|
235
260
|
*/
|
|
236
261
|
export function updateBaseline(baseline, rows, { allowRise = false } = {}) {
|
|
237
262
|
const next = { ...baseline };
|
|
238
263
|
const refused = [];
|
|
239
264
|
for (const [surface, row] of Object.entries(rows)) {
|
|
240
|
-
const { rises } = compare(baseline[surface], row);
|
|
241
|
-
if (rises.length && !allowRise && baseline[surface]) {
|
|
265
|
+
const { rises, unrecorded } = compare(baseline[surface], row);
|
|
266
|
+
if (rises.length && !allowRise && baseline[surface]) {
|
|
267
|
+
refused.push({ surface, rises });
|
|
268
|
+
if (unrecorded.length) {
|
|
269
|
+
const kept = { ...baseline[surface], by_file: { ...baseline[surface].by_file } };
|
|
270
|
+
for (const { metric } of unrecorded) { kept[metric] = row[metric]; kept.by_file[metric] = row.by_file?.[metric] ?? {}; }
|
|
271
|
+
next[surface] = kept;
|
|
272
|
+
}
|
|
273
|
+
continue;
|
|
274
|
+
}
|
|
242
275
|
next[surface] = row;
|
|
243
276
|
}
|
|
244
277
|
return { next: sortedBySurface(next), refused };
|
|
@@ -407,7 +440,11 @@ export function main(argv = process.argv.slice(2), log = (s) => process.stdout.w
|
|
|
407
440
|
if (!opts.update) failed = true;
|
|
408
441
|
continue;
|
|
409
442
|
}
|
|
410
|
-
const { rises, falls } = compare(base, row);
|
|
443
|
+
const { rises, falls, unrecorded } = compare(base, row);
|
|
444
|
+
if (unrecorded.length) {
|
|
445
|
+
log(` not yet in this baseline (not gated): ${unrecorded.map((u) => `${u.metric}=${u.now}`).join(", ")}`);
|
|
446
|
+
log(" record them with --update-baseline (the shipped baseline is refreshed in tangle-network/brand)");
|
|
447
|
+
}
|
|
411
448
|
for (const { metric, base: b, now } of rises) {
|
|
412
449
|
log(` FAIL ${metric}: ${now} > baseline ${b} (+${now - b}; baseline read ${base.head})`);
|
|
413
450
|
const files = risenFiles(base, row, metric);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/* GENERATED by scripts/gen-compat.mjs. Do not edit.
|
|
2
2
|
* Source: tokens.css + named-themes.css
|
|
3
|
-
* SHA-256:
|
|
3
|
+
* SHA-256: 524da3dda08c6fb6130bde802f96d6b961561a900caa939c1eea43ec4495b48d
|
|
4
4
|
* Light-default compatibility; all explicit modes remain canonical.
|
|
5
5
|
*/
|
|
6
6
|
:where(:root),
|
|
@@ -288,6 +288,13 @@
|
|
|
288
288
|
--radius-cover: 20px;
|
|
289
289
|
--radius-sheet: 24px;
|
|
290
290
|
--control-height: 2.25rem;
|
|
291
|
+
--control-height-sm: 2rem;
|
|
292
|
+
--control-height-lg: 2.75rem;
|
|
293
|
+
--control-text-sm: 0.75rem;
|
|
294
|
+
--control-text-md: 0.875rem;
|
|
295
|
+
--control-text-lg: 1rem;
|
|
296
|
+
--font-size-label: 0.875rem;
|
|
297
|
+
--font-size-help: 0.75rem;
|
|
291
298
|
--panel-padding: 1rem;
|
|
292
299
|
--content-max-width: 80rem;
|
|
293
300
|
--font-size-xs: 0.6875rem;
|
|
@@ -772,6 +779,14 @@
|
|
|
772
779
|
--text-page: var(--font-size-3xl);
|
|
773
780
|
--text-section: var(--font-size-xl);
|
|
774
781
|
--text-eyebrow: var(--font-size-sm);
|
|
782
|
+
--text-control-sm: var(--control-text-sm);
|
|
783
|
+
--text-control-md: var(--control-text-md);
|
|
784
|
+
--text-control-lg: var(--control-text-lg);
|
|
785
|
+
--text-label: var(--font-size-label);
|
|
786
|
+
--text-help: var(--font-size-help);
|
|
787
|
+
--spacing-control-sm: var(--control-height-sm);
|
|
788
|
+
--spacing-control-md: var(--control-height);
|
|
789
|
+
--spacing-control-lg: var(--control-height-lg);
|
|
775
790
|
--color-scrim: var(--scrim);
|
|
776
791
|
--color-scrim-strong: var(--scrim-strong);
|
|
777
792
|
--color-on-media: var(--on-media);
|
|
@@ -19,6 +19,14 @@
|
|
|
19
19
|
--text-page: var(--font-size-3xl);
|
|
20
20
|
--text-section: var(--font-size-xl);
|
|
21
21
|
--text-eyebrow: var(--font-size-sm);
|
|
22
|
+
--text-control-sm: var(--control-text-sm);
|
|
23
|
+
--text-control-md: var(--control-text-md);
|
|
24
|
+
--text-control-lg: var(--control-text-lg);
|
|
25
|
+
--text-label: var(--font-size-label);
|
|
26
|
+
--text-help: var(--font-size-help);
|
|
27
|
+
--spacing-control-sm: var(--control-height-sm);
|
|
28
|
+
--spacing-control-md: var(--control-height);
|
|
29
|
+
--spacing-control-lg: var(--control-height-lg);
|
|
22
30
|
--color-scrim: var(--scrim);
|
|
23
31
|
--color-scrim-strong: var(--scrim-strong);
|
|
24
32
|
--color-on-media: var(--on-media);
|
package/src/styles/theme.css
CHANGED
|
@@ -24,6 +24,11 @@
|
|
|
24
24
|
--text-eyebrow--line-height: 1;
|
|
25
25
|
--text-eyebrow--letter-spacing: 0.08em;
|
|
26
26
|
--text-eyebrow--font-weight: 600;
|
|
27
|
+
--text-control-sm--line-height: 1rem;
|
|
28
|
+
--text-control-md--line-height: 1.25rem;
|
|
29
|
+
--text-control-lg--line-height: 1.5rem;
|
|
30
|
+
--text-label--line-height: 1.25rem;
|
|
31
|
+
--text-help--line-height: 1rem;
|
|
27
32
|
|
|
28
33
|
--radius-sm: 6px;
|
|
29
34
|
--radius-md: 8px;
|
|
@@ -61,6 +66,18 @@
|
|
|
61
66
|
--text-section: var(--font-size-xl);
|
|
62
67
|
--text-eyebrow: var(--font-size-sm);
|
|
63
68
|
|
|
69
|
+
/* Control and form type: text-control-sm/md/lg, text-label, text-help. */
|
|
70
|
+
--text-control-sm: var(--control-text-sm);
|
|
71
|
+
--text-control-md: var(--control-text-md);
|
|
72
|
+
--text-control-lg: var(--control-text-lg);
|
|
73
|
+
--text-label: var(--font-size-label);
|
|
74
|
+
--text-help: var(--font-size-help);
|
|
75
|
+
|
|
76
|
+
/* Control heights as spacing: h-control-md, size-control-sm, min-h-control-lg. */
|
|
77
|
+
--spacing-control-sm: var(--control-height-sm);
|
|
78
|
+
--spacing-control-md: var(--control-height);
|
|
79
|
+
--spacing-control-lg: var(--control-height-lg);
|
|
80
|
+
|
|
64
81
|
--color-scrim: var(--scrim);
|
|
65
82
|
--color-scrim-strong: var(--scrim-strong);
|
|
66
83
|
--color-on-media: var(--on-media);
|
package/src/styles/tokens.css
CHANGED
|
@@ -442,6 +442,17 @@
|
|
|
442
442
|
|
|
443
443
|
/* Density */
|
|
444
444
|
--control-height: 2.25rem;
|
|
445
|
+
/* Control scale: Button, IconButton sizes, Input, Textarea and SelectTrigger
|
|
446
|
+
share one height and text size per size, so a row of mixed controls aligns.
|
|
447
|
+
md is --control-height, the density knob a subtree may override. */
|
|
448
|
+
--control-height-sm: 2rem; /* 32px */
|
|
449
|
+
--control-height-lg: 2.75rem; /* 44px — touch target */
|
|
450
|
+
--control-text-sm: 0.75rem; /* 12px */
|
|
451
|
+
--control-text-md: 0.875rem; /* 14px */
|
|
452
|
+
--control-text-lg: 1rem; /* 16px — fields never zoom on iOS */
|
|
453
|
+
/* Form text: labels name a field; help text describes or reports on it. */
|
|
454
|
+
--font-size-label: 0.875rem; /* 14px */
|
|
455
|
+
--font-size-help: 0.75rem; /* 12px */
|
|
445
456
|
--panel-padding: 1rem;
|
|
446
457
|
--content-max-width: 80rem;
|
|
447
458
|
|