@tangle-network/brand 1.21.1 → 1.22.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/CHANGELOG.md CHANGED
@@ -1,5 +1,21 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.22.1
4
+
5
+ ### Patch Changes
6
+
7
+ - 07e7a78: tangle-drift ignores CSS comments when counting header_height_literal and transcript_type_override, and no longer counts a step-progress bar (`.steps-bar`) as a header row. UI's RunGroup sets its Markdown with the shared TRANSCRIPT_TEXT instead of a fixed 15px / leading-7, so a run reads at the transcript scale.
8
+
9
+ ## 1.22.0
10
+
11
+ ### Minor Changes
12
+
13
+ - c94bde7: Fix shared defects from the Hospitality design audit.
14
+
15
+ Brand: dark `--destructive` is now a light red carrying dark ink (`0 90% 77%` on `0 60% 10%`), so `text-destructive` clears AA (at least 4.5:1) on the canvas, card, popover and muted planes of every dark theme; it measured 2.5:1 on Hospitality's dark card. Destructive fills keep their foreground at 8:1. The `--font-sans` and `--font-display` stacks name `"Inter Variable"` ahead of `"Inter"`, and the README now recommends `@fontsource-variable/inter` so apps render every weight the scale uses. Brand still loads no fonts itself.
16
+
17
+ UI: the `tabs` SegmentedControl scrolls the selected tab into view inside the strip (never the page) and fades the edge that has more tabs past it; tab items take a 44px hit height on coarse pointers; labels keep one weight in both variants, so changing the selection no longer shifts the row. SelectTrigger values end in an ellipsis with a gap before the chevron instead of clipping mid-glyph. Plain-text code fences (`text`, `txt`, `plain`, `plaintext`) show no language label.
18
+
3
19
  ## 1.21.1
4
20
 
5
21
  ### Patch Changes
package/README.md CHANGED
@@ -105,7 +105,7 @@ Renderers that cannot read CSS variables (PDF, canvas, WebGL, email, the `theme-
105
105
 
106
106
  The base surface ladder is the one GTM proved out: dark is indigo-lifted `#0a0a14` / `#191826` / `#221f33` / `#2c2942`; light is a cool `#eceef3` canvas, white paper cards and overlays, and `#f1f2f7` nested wells. The cast stays below 0.1 chroma and ink stays achromatic, so indigo still reads as the interaction accent, not the base field. Existing named products may supply deliberate surface overrides.
107
107
 
108
- Chrome radii are 6/8/10/12px in both base modes; the composer has its own 26px role. Inter leads body and display stacks, with Geist fallbacks; Geist Mono leads code with JetBrains Mono and system fallbacks. Runtime font/radius registrations retain their public override names; tests keep Tailwind registration defaults aligned with the canonical declarations.
108
+ Chrome radii are 6/8/10/12px in both base modes; the composer has its own 26px role. Inter (variable, then static) leads body and display stacks, with Geist fallbacks; Geist Mono leads code with JetBrains Mono and system fallbacks. Runtime font/radius registrations retain their public override names; tests keep Tailwind registration defaults aligned with the canonical declarations.
109
109
 
110
110
  Existing `--transition-*` values are preserved. The shared `--duration-*`, `--ease-*` and `--motion-*` vocabulary comes from the existing Agent App contract. It does not apply animations automatically. Reduced motion zeroes those durations and re-resolves composites at theme boundaries.
111
111
 
@@ -113,22 +113,19 @@ Focus uses `--focus-border`, `--focus-halo`, their danger variants, and `--borde
113
113
 
114
114
  ## Fonts
115
115
 
116
- Fonts are referenced, **not bundled or remotely fetched**. Consumers own loading, privacy and network fallbacks. For example:
116
+ Fonts are referenced, **not bundled or remotely fetched**. Consumers own loading, privacy and network fallbacks, and an app that loads nothing renders in the OS fallback (DejaVu, Segoe, SF), whose widths and weights the type scale was not tuned for. Load Inter in the app entry. The variable build is one file that renders every weight the scale uses, including in-between weights such as 650 that static files round to the nearest cut:
117
117
 
118
118
  ```sh
119
- pnpm add @fontsource/inter @fontsource/geist-mono
119
+ pnpm add @fontsource-variable/inter @fontsource/geist-mono
120
120
  ```
121
121
 
122
122
  ```tsx
123
- import "@fontsource/inter/400.css";
124
- import "@fontsource/inter/500.css";
125
- import "@fontsource/inter/600.css";
126
- import "@fontsource/inter/700.css";
123
+ import "@fontsource-variable/inter";
127
124
  import "@fontsource/geist-mono/400.css";
128
125
  import "@fontsource/geist-mono/500.css";
129
126
  ```
130
127
 
131
- Omitted families fall back through the `--font-*` stacks. Keep external font imports out of library CSS: they can break reordered CSS imports and should not impose a third-party request on every consumer.
128
+ The `--font-sans` and `--font-display` stacks name `"Inter Variable"` (the family that package registers) ahead of `"Inter"`, so static `@fontsource/inter` imports keep working. Omitted families fall back through the `--font-*` stacks. Keep external font imports out of library CSS: they can break reordered CSS imports and should not impose a font download on every consumer.
132
129
 
133
130
  ## Policy and development
134
131
 
@@ -83,7 +83,7 @@ export const PATTERNS = {
83
83
  // the selector's last compound (pseudo-classes and attributes aside), so `.top-header .avatar`
84
84
  // and `.top-header-avatar` size something inside a header, not the header row.
85
85
  // A `-bar` row counts too (`.files-bar`), but not a progress, scroll, side or meter bar.
86
- const HEADER_WORD = String.raw`(?:header|head|topbar|toolbar|titlebar|appbar|navbar|(?<![\w-]*(?:progress|scroll|side|meter|loading|track|fill|status)-?)bar)`;
86
+ const HEADER_WORD = String.raw`(?:header|head|topbar|toolbar|titlebar|appbar|navbar|(?<![\w-]*(?:progress|scroll|side|meter|loading|track|fill|status|steps?)-?)bar)`;
87
87
  const HEADER_COMPOUND = String.raw`(?:[\w#.:-]*[.#-]${HEADER_WORD}|(?<![\w.#-])header)(?![\w-])(?::{1,2}[\w-]+(?:\([^)]*\))?|\[[^\]]*\])*`;
88
88
  const LITERAL_HEIGHT_UTILITY = String.raw`(?<![\w-])(?:h|min-h|max-h)-(?:[0-9]|\[(?!var\())`;
89
89
 
@@ -199,7 +199,9 @@ export function ruleMatches(source, roots, metric, exts) {
199
199
  .map((f) => (f.startsWith(prefix) ? f.slice(prefix.length) : f).trim()).filter(Boolean);
200
200
  const rows = [];
201
201
  for (const path of files) {
202
- const text = readFile(source, path);
202
+ // Comments name things without styling them (a CSS note that mentions "transcript" or
203
+ // "header"); blank them, keeping offsets and newlines, so only real rules count.
204
+ const text = readFile(source, path).replace(/\/\*[\s\S]*?\*\//g, (comment) => comment.replace(/[^\n]/g, " "));
203
205
  const found = new Map();
204
206
  for (const pattern of patterns) {
205
207
  for (const m of text.matchAll(new RegExp(pattern, "g"))) found.set(m.index, m[0]);
package/dist/index.d.ts CHANGED
@@ -97,7 +97,7 @@ export declare const palettes: {
97
97
  readonly accentHover: "#5a50d6";
98
98
  readonly onAccent: "#ffffff";
99
99
  readonly accentText: "#a5b4fc";
100
- readonly danger: "#d52044";
100
+ readonly danger: "#f99090";
101
101
  readonly successBg: "#2f5146";
102
102
  readonly successBorder: "#3c7760";
103
103
  readonly successText: "#5ed7a4";
@@ -163,7 +163,7 @@ export declare const palettes: {
163
163
  readonly accentHover: "#5a50d6";
164
164
  readonly onAccent: "#ffffff";
165
165
  readonly accentText: "#a5b4fc";
166
- readonly danger: "#d52044";
166
+ readonly danger: "#f99090";
167
167
  readonly successBg: "#2f5146";
168
168
  readonly successBorder: "#3c7760";
169
169
  readonly successText: "#5ed7a4";
package/dist/index.js CHANGED
@@ -156,7 +156,7 @@ const palettes = {
156
156
  accentHover: "#5a50d6",
157
157
  onAccent: "#ffffff",
158
158
  accentText: "#a5b4fc",
159
- danger: "#d52044",
159
+ danger: "#f99090",
160
160
  successBg: "#2f5146",
161
161
  successBorder: "#3c7760",
162
162
  successText: "#5ed7a4",
@@ -222,7 +222,7 @@ const palettes = {
222
222
  accentHover: "#5a50d6",
223
223
  onAccent: "#ffffff",
224
224
  accentText: "#a5b4fc",
225
- danger: "#d52044",
225
+ danger: "#f99090",
226
226
  successBg: "#2f5146",
227
227
  successBorder: "#3c7760",
228
228
  successText: "#5ed7a4",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tangle-network/brand",
3
- "version": "1.21.1",
3
+ "version": "1.22.1",
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",
@@ -87,7 +87,7 @@ export const palettes = {
87
87
  accentHover: "#5a50d6",
88
88
  onAccent: "#ffffff",
89
89
  accentText: "#a5b4fc",
90
- danger: "#d52044",
90
+ danger: "#f99090",
91
91
  successBg: "#2f5146",
92
92
  successBorder: "#3c7760",
93
93
  successText: "#5ed7a4",
@@ -153,7 +153,7 @@ export const palettes = {
153
153
  accentHover: "#5a50d6",
154
154
  onAccent: "#ffffff",
155
155
  accentText: "#a5b4fc",
156
- danger: "#d52044",
156
+ danger: "#f99090",
157
157
  successBg: "#2f5146",
158
158
  successBorder: "#3c7760",
159
159
  successText: "#5ed7a4",
@@ -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: 939e3e76ae042294bf30fd3c9083242457c387a107693bb8c329e8e5ab22b92d
3
+ * SHA-256: cf009734f12a6dcb94d31f9ee6c21583942f1b2b2be38454344d6c3403cd26b5
4
4
  * Light-default compatibility; all explicit modes remain canonical.
5
5
  */
6
6
  :where(:root),
@@ -74,8 +74,8 @@
74
74
  --hsl-muted-foreground: 240 5% 68%;
75
75
  --hsl-accent: 249 24% 16%;
76
76
  --hsl-accent-foreground: 0 0% 90.1961%;
77
- --hsl-destructive: 348 74% 48%;
78
- --hsl-destructive-foreground: 0 0% 100%;
77
+ --hsl-destructive: 0 90% 77%;
78
+ --hsl-destructive-foreground: 0 60% 10%;
79
79
  --hsl-border: 243 20% 20%;
80
80
  --hsl-input: 244 29% 9%;
81
81
  --hsl-ring: 233 100% 87%;
@@ -365,8 +365,8 @@
365
365
  --transition-fast: 0.1s ease;
366
366
  --transition-default: 0.16s ease;
367
367
  --transition-medium: 0.24s ease;
368
- --font-sans: "Inter", "Geist", ui-sans-serif, system-ui, -apple-system, sans-serif;
369
- --font-display: "Inter", "Geist", sans-serif;
368
+ --font-sans: "Inter Variable", "Inter", "Geist", ui-sans-serif, system-ui, -apple-system, sans-serif;
369
+ --font-display: "Inter Variable", "Inter", "Geist", sans-serif;
370
370
  --font-mono: "Geist Mono", "JetBrains Mono", ui-monospace, monospace;
371
371
  }
372
372
  [data-sandbox-theme="vault"]:where(:not(.dark)),
@@ -610,8 +610,8 @@
610
610
  --accent-surface-soft: rgba(76, 63, 198, 0.05);
611
611
  --accent-surface-strong: rgba(76, 63, 198, 0.13);
612
612
  --accent-text: #3c32e9;
613
- --font-sans: "Inter", "Geist", ui-sans-serif, system-ui, -apple-system, sans-serif;
614
- --font-display: "Inter", "Geist", sans-serif;
613
+ --font-sans: "Inter Variable", "Inter", "Geist", ui-sans-serif, system-ui, -apple-system, sans-serif;
614
+ --font-display: "Inter Variable", "Inter", "Geist", sans-serif;
615
615
  --font-mono: "Geist Mono", "JetBrains Mono", ui-monospace, monospace;
616
616
  --radius-sm: 6px;
617
617
  --radius-md: 8px;
@@ -1152,8 +1152,8 @@
1152
1152
  --hsl-muted-foreground: 240 5% 68%;
1153
1153
  --hsl-accent: 249 24% 16%;
1154
1154
  --hsl-accent-foreground: 0 0% 90.1961%;
1155
- --hsl-destructive: 348 74% 48%;
1156
- --hsl-destructive-foreground: 0 0% 100%;
1155
+ --hsl-destructive: 0 90% 77%;
1156
+ --hsl-destructive-foreground: 0 60% 10%;
1157
1157
  --hsl-border: 243 20% 20%;
1158
1158
  --hsl-input: 244 29% 9%;
1159
1159
  --hsl-ring: 233 100% 87%;
@@ -243,7 +243,7 @@
243
243
  --hsl-secondary: 249 24% 16%; --hsl-secondary-foreground: 0 0% 90.1961%;
244
244
  --hsl-muted: 249 24% 16%; --hsl-muted-foreground: 240 5% 68%;
245
245
  --hsl-accent: 249 24% 16%; --hsl-accent-foreground: 0 0% 90.1961%;
246
- --hsl-destructive: 348 74% 48%; --hsl-destructive-foreground: 0 0% 100%;
246
+ --hsl-destructive: 0 90% 77%; --hsl-destructive-foreground: 0 60% 10%;
247
247
  --hsl-border: 243 20% 20%; --hsl-input: 244 29% 9%;
248
248
  --hsl-ring: 233 100% 87%;
249
249
  --depth-1: #0a0a14; --depth-2: #191826; --depth-3: #221f33; --depth-4: #2c2942;
@@ -4,6 +4,7 @@ import { describe, expect, it } from "vitest";
4
4
  import {
5
5
  blockIn,
6
6
  blocksIn,
7
+ compositeOver,
7
8
  contrastRatio,
8
9
  declIn,
9
10
  hexIn,
@@ -653,3 +654,49 @@ describe('named theme: [data-theme="website"]', () => {
653
654
  }
654
655
  });
655
656
  });
657
+
658
+ describe("dark themes: destructive reads as text", () => {
659
+ // `text-destructive` is the error ink on cards, popovers and wells, and
660
+ // `bg-destructive text-destructive-foreground` is the destructive fill. In
661
+ // dark mode one token must serve both, so it is a light red carrying dark
662
+ // ink, as the dark primary does: 2.5:1 error text on a sage card shipped
663
+ // before every dark scope was checked here.
664
+ const scopes: Array<[string, string]> = [
665
+ [".dark", CANON],
666
+ ...[
667
+ '[data-theme="tangle-dark"]',
668
+ '[data-theme="aubergine"]',
669
+ '[data-theme="arena"]',
670
+ '[data-theme="super"]',
671
+ '.dark[data-theme="intelligence"]',
672
+ '.dark[data-theme="hospitality"]',
673
+ '[data-theme="agents"]',
674
+ '.dark[data-theme="website"]',
675
+ ].map((selector): [string, string] => {
676
+ const css = blocksIn(themes, selector).find((b) => b.includes("--hsl-background"));
677
+ if (!css) throw new Error(`missing dark theme block: ${selector}`);
678
+ return [selector, css];
679
+ }),
680
+ ];
681
+ it.each(scopes)("%s holds AA for destructive text and its fill", (_selector, css) => {
682
+ const effective = (token: string) => {
683
+ try {
684
+ return hslToRgb(hslIn(css, token));
685
+ } catch {
686
+ return hslToRgb(hslIn(CANON, token));
687
+ }
688
+ };
689
+ const destructive = effective("hsl-destructive");
690
+ for (const plane of ["hsl-background", "hsl-card", "hsl-popover", "hsl-muted"]) {
691
+ const bg = effective(plane);
692
+ expect(contrastRatio(destructive, bg), `destructive text on ${plane}`).toBeGreaterThanOrEqual(4.5);
693
+ // Error rows commonly sit on a 10% destructive tint (bg-destructive/10).
694
+ const tint = compositeOver(destructive, bg, 0.1);
695
+ expect(contrastRatio(destructive, tint), `destructive text on its tint over ${plane}`).toBeGreaterThanOrEqual(4.5);
696
+ }
697
+ expect(
698
+ contrastRatio(effective("hsl-destructive-foreground"), destructive),
699
+ "destructive-foreground on the destructive fill",
700
+ ).toBeGreaterThanOrEqual(4.5);
701
+ });
702
+ });
@@ -5,8 +5,8 @@
5
5
  * override --font-* / --radius-* on a subtree.
6
6
  */
7
7
  @theme {
8
- --font-sans: "Inter", "Geist", ui-sans-serif, system-ui, -apple-system, sans-serif;
9
- --font-display: "Inter", "Geist", sans-serif;
8
+ --font-sans: "Inter Variable", "Inter", "Geist", ui-sans-serif, system-ui, -apple-system, sans-serif;
9
+ --font-display: "Inter Variable", "Inter", "Geist", sans-serif;
10
10
  --font-mono: "Geist Mono", "JetBrains Mono", ui-monospace, monospace;
11
11
 
12
12
  --text-display--line-height: 1.05;
@@ -129,8 +129,10 @@
129
129
  contrast floor against the L2 card. */
130
130
  --hsl-accent: 249 24% 16%; /* neutral hover (indigo lives in accent-surface-strong) */
131
131
  --hsl-accent-foreground: 0 0% 90.1961%;
132
- --hsl-destructive: 348 74% 48%;
133
- --hsl-destructive-foreground: 0 0% 100%;
132
+ /* Light red carrying dark ink, as the dark primary does: it must read as
133
+ * error TEXT on every dark plane (AA) and still carry its foreground as a fill. */
134
+ --hsl-destructive: 0 90% 77%;
135
+ --hsl-destructive-foreground: 0 60% 10%;
134
136
  /* Quiet on purpose: 1.5:1 against the resting card. Shadow does no work on a
135
137
  * dark canvas, so the hairline is what states where a plane ends. */
136
138
  --hsl-border: 243 20% 20%; /* #2a293d = --md3-outline-variant */
@@ -548,8 +550,8 @@
548
550
  --transition-medium: 0.24s ease;
549
551
 
550
552
  /* Fonts — unified on Inter (dark + light) */
551
- --font-sans: "Inter", "Geist", ui-sans-serif, system-ui, -apple-system, sans-serif;
552
- --font-display: "Inter", "Geist", sans-serif;
553
+ --font-sans: "Inter Variable", "Inter", "Geist", ui-sans-serif, system-ui, -apple-system, sans-serif;
554
+ --font-display: "Inter Variable", "Inter", "Geist", sans-serif;
553
555
  --font-mono: "Geist Mono", "JetBrains Mono", ui-monospace, monospace;
554
556
  }
555
557
 
@@ -877,8 +879,8 @@
877
879
  --accent-text: #3c32e9;
878
880
 
879
881
  /* Typography — unified on Inter */
880
- --font-sans: "Inter", "Geist", ui-sans-serif, system-ui, -apple-system, sans-serif;
881
- --font-display: "Inter", "Geist", sans-serif;
882
+ --font-sans: "Inter Variable", "Inter", "Geist", ui-sans-serif, system-ui, -apple-system, sans-serif;
883
+ --font-display: "Inter Variable", "Inter", "Geist", sans-serif;
882
884
  --font-mono: "Geist Mono", "JetBrains Mono", ui-monospace, monospace;
883
885
 
884
886
  /* Radius — unified chrome scale (matches dark; 8px md) */