@tangle-network/brand 1.17.0 → 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.
@@ -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 and locally defined primitives in a surface's
7
- * source, reading a git tree with `git grep` (no checkout of the tree needed).
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
@@ -25,13 +27,14 @@ export const DEFAULT_BASELINE = fileURLToPath(new URL("./drift-baseline.json", i
25
27
 
26
28
  /** surface → GitHub repository and web roots. The default branch is resolved from the remote. */
27
29
  export const SURFACES = {
28
- website: { repo: "tangle-network/tangle-website", roots: [""] },
30
+ website: { repo: "tangle-network/tangle-website", roots: ["src"] },
29
31
  sandbox: { repo: "tangle-network/agent-dev-container", roots: ["products/sandbox/web"] },
30
32
  platform: { repo: "tangle-network/agent-dev-container", roots: ["products/platform/web"] },
31
33
  intelligence: { repo: "tangle-network/agent-dev-container", roots: ["products/intelligence/web"] },
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
- /** Compare a measured row with its baseline row. Missing baseline metrics count as 0. */
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. Returns { next, refused }.
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]) { refused.push({ surface, rises }); continue; }
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
  {
2
2
  "name": "@tangle-network/brand",
3
- "version": "1.17.0",
3
+ "version": "1.18.0",
4
4
  "description": "Tangle brand system — design tokens, logo, Tailwind v4 theme. Single source of truth for every Tangle app.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -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: 6376234ba229d9bd8fc0be766a9998b61146de56ee8dc9d5bb5a0ba2206951f0
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);
@@ -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);
@@ -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