@tangle-network/brand 1.9.1 → 1.10.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 +6 -0
- package/README.md +77 -94
- package/package.json +6 -3
- package/src/styles/compatibility.test.ts +11 -0
- package/src/styles/focus.test.ts +1 -1
- package/src/styles/legacy-light.css +873 -0
- package/src/styles/named-themes.css +38 -62
- package/src/styles/theme.css +30 -48
- package/src/styles/tokens.css +85 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.10.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 48b9dcf: Keep Brand dark-default while exporting a checked, generated light-default compatibility stylesheet. Re-resolve shared aliases and syntax colors at nested mode boundaries, retain named identities, and make ThemeToggle SSR-, storage- and system-change-safe. CodeBlock now uses scoped semantic CSS variables with immediate browser recoloring.
|
|
8
|
+
|
|
3
9
|
## 1.9.1
|
|
4
10
|
|
|
5
11
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -1,44 +1,79 @@
|
|
|
1
1
|
# @tangle-network/brand
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
**Scope:** tokens, not components. Shared components live in `@tangle-network/ui`; product-specific components stay in their product repos.
|
|
6
|
-
|
|
7
|
-
For the broader visual, copy, graphic, and component standards, see the repo-level [brand guidelines](../../docs/brand-guidelines.md).
|
|
3
|
+
The authoring owner of shared Tangle color, typography, radius, motion, shadow and status decisions. Generic components live in `@tangle-network/ui`; product composition stays in its product repository. See the [brand guidelines](../../docs/brand-guidelines.md) for visual, copy and component standards.
|
|
8
4
|
|
|
9
5
|
## Install
|
|
10
6
|
|
|
11
|
-
```
|
|
7
|
+
```sh
|
|
12
8
|
pnpm add @tangle-network/brand
|
|
13
9
|
```
|
|
14
10
|
|
|
15
11
|
## Use
|
|
16
12
|
|
|
17
|
-
### Tailwind v4
|
|
13
|
+
### Tailwind v4
|
|
18
14
|
|
|
19
15
|
```css
|
|
20
|
-
|
|
16
|
+
@import "tailwindcss";
|
|
21
17
|
@import "@tangle-network/brand/styles";
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
The standard import supplies canonical tokens, the Tailwind theme, named themes and globals. **Brand remains dark-default.** It does not import the optional ladder/system styles or load fonts.
|
|
21
|
+
|
|
22
|
+
For finer control, use the exported filenames:
|
|
23
|
+
|
|
24
|
+
```css
|
|
22
25
|
@import "tailwindcss";
|
|
26
|
+
@import "@tangle-network/brand/styles/tokens.css";
|
|
27
|
+
@import "@tangle-network/brand/styles/theme.css";
|
|
28
|
+
@import "@tangle-network/brand/styles/named-themes.css";
|
|
29
|
+
@import "@tangle-network/brand/styles/globals.css";
|
|
23
30
|
```
|
|
24
31
|
|
|
25
|
-
|
|
32
|
+
### Mode contract
|
|
33
|
+
|
|
34
|
+
| Scope | Behavior |
|
|
35
|
+
| --- | --- |
|
|
36
|
+
| Unpinned root; `[data-sandbox-ui]` | Canonical dark baseline |
|
|
37
|
+
| `.dark` or `data-theme="dark"` | Explicit dark, including a nested dark island |
|
|
38
|
+
| `.light` or `data-theme="light"` | Explicit light, including a nested light island |
|
|
39
|
+
| `data-sandbox-theme="vault"` or `"dawn"` | Existing legacy light names; an explicit `.dark` on that same element takes precedence |
|
|
40
|
+
| `aubergine` / `aubergine-light`, `arena` / `arena-light`, `tangle-dark` / `tangle-light` | Existing named dark/light pairs, applied with `data-theme` |
|
|
41
|
+
| `.dark[data-theme="intelligence"]` | Existing dark-only product surface; `.light` keeps the identity and uses canonical light |
|
|
26
42
|
|
|
27
|
-
|
|
28
|
-
- Tailwind v4 `@theme` block so `bg-brand`, `text-fg-muted`, `rounded-lg`, `font-display`, etc. just work
|
|
29
|
-
- Base styles and a handful of utility classes (`.text-gradient-brand`, `.depth-1..4`, `.status-dot-*`)
|
|
43
|
+
Set one coherent mode on each boundary; do not author contradictory `.light`/`.dark`/named-mode markers. Named light variants keep their existing palettes; `tangle-light` is not silently retuned to the neutral base light palette. Each known boundary receives a complete baseline before named overrides, so nested scopes do not inherit opposite-mode syntax, status or already-resolved HSL aliases.
|
|
30
44
|
|
|
31
|
-
|
|
45
|
+
`--background`, `--card`, `--input`, and the other shadcn aliases remain **HSL channel triples**: consume them as `hsl(var(--card))`. Full-color aliases such as `--bg-card`, `--text-primary` and `--syntax-keyword` remain full CSS colors. `--input` is the border/off-track, not the recessed field fill (`--bg-input`). Tailwind's variable-dependent registrations use `@theme inline`; the named-theme bridge keeps precompiled `var(--color-*)` consumers scoped too.
|
|
32
46
|
|
|
33
|
-
|
|
47
|
+
`@tangle-network/ui`'s existing `useTheme()` / `ThemeToggle` control the document root, not individual islands. `useTheme()` retains `theme` and `setTheme` and also exposes `resolvedTheme`. With no valid stored preference or explicit initial mode, it follows the system. The hook validates the existing `theme` storage key, tolerates denied storage, synchronizes mounted controls and cross-tab events, and updates its resolved state on system changes. It sets both mode classes, updates plain mode attributes, switches known named pairs within the same family, and never replaces an unpaired product identity with `light` or `dark`.
|
|
48
|
+
|
|
49
|
+
SSR uses a deterministic dark placeholder for the control without accessing browser APIs; hydration then resolves the client preference. This is not an app-level pre-paint theme script. Apps needing a preference-correct first paint still own their server/cookie or pre-paint initialization. Custom, unpaired themes retain ownership of how their CSS responds to the mode classes.
|
|
50
|
+
|
|
51
|
+
`CodeBlock` leaves `--syntax-*` references in the rendered styles, so the browser resolves them where the code lives and recolors on class, attribute, stylesheet or inline-variable changes without a React rerender. An omitted `light` prop inherits; `light={true}` / `light={false}` explicitly pins a local light/dark scope. Line numbers use the syntax-comment role, not a second palette.
|
|
52
|
+
|
|
53
|
+
### Existing light-default consumers
|
|
54
|
+
|
|
55
|
+
The Agent App light-default contract is an explicit exception to Brand's default, not a second palette authoring source. Use the generated, opt-in shared-token projection:
|
|
34
56
|
|
|
35
57
|
```css
|
|
36
|
-
@import "@tangle-network/brand/styles/tokens";
|
|
37
58
|
@import "tailwindcss";
|
|
38
|
-
@import "@tangle-network/brand/styles/
|
|
39
|
-
@import "@tangle-network/brand/styles/
|
|
59
|
+
@import "@tangle-network/brand/styles/legacy-light.css";
|
|
60
|
+
@import "@tangle-network/brand/styles/theme.css";
|
|
61
|
+
/* Import globals.css separately only when the host already intends those globals. */
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Choose this **instead of** `styles`, `tokens.css` and `named-themes.css`; do not stack the two default contracts. The projection contains canonical declarations with a zero-specificity light root default, keeps all explicit mode selectors, and omits `--radius`, which the legacy consumer deliberately leaves to its host. It includes no global component rules, keyframes or automatic ladder/system imports.
|
|
65
|
+
|
|
66
|
+
This export is the shared migration boundary, **not a claim that Agent App has already migrated** and not a replacement for that package's entire stylesheet. Its component-specific variables, neutral-ramp API, keyframes and structural rules remain consumer-owned until a separate, explicit migration reconciles them. Remove duplicate shared palette declarations in that migration rather than placing competing global token systems on the same page.
|
|
67
|
+
|
|
68
|
+
The only authoring inputs are `src/styles/tokens.css` and `named-themes.css`. Never edit `legacy-light.css` by hand:
|
|
69
|
+
|
|
70
|
+
```sh
|
|
71
|
+
pnpm --filter @tangle-network/brand gen:compat
|
|
72
|
+
pnpm --filter @tangle-network/brand check:compat
|
|
40
73
|
```
|
|
41
74
|
|
|
75
|
+
Brand's build rejects a stale artifact. The main test suite also runs the generation contract: every projected declaration is compared with the canonical source, and color, named-color, typography, radius and motion mutations must change the artifact. The package export is checked too.
|
|
76
|
+
|
|
42
77
|
### Colour ladders (opt in)
|
|
43
78
|
|
|
44
79
|
```css
|
|
@@ -47,18 +82,9 @@ Fonts are **not** bundled — see [Fonts](#fonts) below.
|
|
|
47
82
|
@import "@tangle-network/brand/styles/system.css";
|
|
48
83
|
```
|
|
49
84
|
|
|
50
|
-
`ladders.css` defines
|
|
51
|
-
Each step has one job: 1–2 backgrounds, 3–5 control fills, 6–8 borders, 9–10 solid fills, 11 secondary text and 12 primary text.
|
|
52
|
-
Role aliases (`--gray-*`, `--accent-*`, `--success-*`, `--warning-*`, `--danger-*`, `--info-*`) point at the ramps.
|
|
53
|
-
Setting `data-domain="tax"` (or another domain key) on an element gives its subtree `--domain-1..12`.
|
|
54
|
-
|
|
55
|
-
`system.css` maps every token family in `tokens.css` onto a ladder step and adds semantic tokens (`--bg-page`, `--line`, `--fg-muted`, `--accent-text`, `--ink`), role radii, type roles, motion and three shadow levels.
|
|
56
|
-
Light pages become white, and dark becomes a neutral mauve ladder.
|
|
57
|
-
Import it after `tokens.css`; it is opt in because it changes every surface of the app that loads it.
|
|
85
|
+
These existing optional styles remain separate. `ladders.css` defines twelve twelve-step Radix color ramps and role/domain aliases. `system.css` maps the token families to those ladders and adds semantic roles. It changes every surface of an app that loads it; neither normal Brand nor the compatibility export opts a consumer in.
|
|
58
86
|
|
|
59
|
-
Both files are generated.
|
|
60
|
-
Change a value in `scripts/gen-ladders.mjs` or `scripts/radix-ramps.json`, then run `pnpm --filter @tangle-network/brand gen:ladders`.
|
|
61
|
-
An app that ships ahead of a release copies the two generated files verbatim, so its copy stays identical.
|
|
87
|
+
Both files are generated by `scripts/gen-ladders.mjs` from its declarations and `scripts/radix-ramps.json`. Run `pnpm --filter @tangle-network/brand gen:ladders` after changing those sources. A consumer temporarily copying them ahead of a release copies the generated files verbatim.
|
|
62
88
|
|
|
63
89
|
### Logo
|
|
64
90
|
|
|
@@ -70,90 +96,47 @@ import { Logo, TangleKnot } from "@tangle-network/brand";
|
|
|
70
96
|
<TangleKnot size={48} />
|
|
71
97
|
```
|
|
72
98
|
|
|
73
|
-
##
|
|
74
|
-
|
|
75
|
-
- **Accent** — indigo `#6366F1` / `#818CF8` / `#A5AAFC`
|
|
76
|
-
- **Depth stack** — `#0C0B1D` → `#262448` (dark); `#f4f4f9` → `#dcdbe8` (light)
|
|
77
|
-
- **Status** — emerald / amber / coral for running / stopped / error
|
|
78
|
-
- **Radii** — 6/8/12/16px (dark); 2/4/6/8px (light/"vault")
|
|
79
|
-
- **Focus** — `--focus-border` and `--focus-halo` (plus `-danger` variants) for a focused control, and `--border-strong` for a hovered field. They derive from the ring and border tokens, so every theme gets its own. `@tangle-network/ui` applies them through `focusField` and `focusRing`. `globals.css` gives a control or native text field that styles no focus of its own a fallback: a control gets the 1px line and halo on keyboard focus, and a text field gets the 1px line over its border.
|
|
99
|
+
## Palette and typography
|
|
80
100
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
## Fonts
|
|
101
|
+
The base dark surface ladder is neutral `#161616` / `#303030` / `#353535` / `#3b3b3b`. Light uses a neutral `#eaeaea` canvas, white paper cards and overlays, and `#f2f2f2` nested wells. Indigo is the interaction accent, not the base field. Existing named products may supply deliberate surface overrides.
|
|
84
102
|
|
|
85
|
-
|
|
103
|
+
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.
|
|
86
104
|
|
|
87
|
-
|
|
88
|
-
- (b) Shipping a third-party Google Fonts request from a library is privacy-hostile.
|
|
89
|
-
- (c) Consumers cannot fall back when the network fails.
|
|
105
|
+
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.
|
|
90
106
|
|
|
91
|
-
|
|
92
|
-
| ------------ | ----------------------------------- | -------------------- |
|
|
93
|
-
| Geist | UI body text (dark) | `--font-sans` |
|
|
94
|
-
| Geist Mono | Code, terminal | `--font-mono` |
|
|
95
|
-
| Outfit | Display / headings (dark) | `--font-display` |
|
|
96
|
-
| Manrope | Display / headings (vault) | `--font-display` |
|
|
97
|
-
| Inter | UI body text (vault) | `--font-sans` |
|
|
107
|
+
Focus uses `--focus-border`, `--focus-halo`, their danger variants, and `--border-strong`, derived from the local ring/border palette. UI's `focusField` / `focusRing` consume them. `globals.css` supplies the existing fallback for controls without their own focus treatment.
|
|
98
108
|
|
|
99
|
-
|
|
109
|
+
## Fonts
|
|
100
110
|
|
|
101
|
-
**
|
|
111
|
+
Fonts are referenced, **not bundled or remotely fetched**. Consumers own loading, privacy and network fallbacks. For example:
|
|
102
112
|
|
|
103
|
-
```
|
|
104
|
-
pnpm add @fontsource/
|
|
113
|
+
```sh
|
|
114
|
+
pnpm add @fontsource/inter @fontsource/geist-mono
|
|
105
115
|
```
|
|
106
116
|
|
|
107
117
|
```tsx
|
|
108
|
-
// app entry
|
|
109
|
-
import "@fontsource/geist-sans/400.css";
|
|
110
|
-
import "@fontsource/geist-sans/500.css";
|
|
111
|
-
import "@fontsource/geist-sans/600.css";
|
|
112
|
-
import "@fontsource/geist-sans/700.css";
|
|
113
|
-
import "@fontsource/geist-mono/400.css";
|
|
114
|
-
import "@fontsource/geist-mono/500.css";
|
|
115
|
-
import "@fontsource/outfit/500.css";
|
|
116
|
-
import "@fontsource/outfit/700.css";
|
|
117
|
-
import "@fontsource/manrope/500.css";
|
|
118
|
-
import "@fontsource/manrope/700.css";
|
|
119
118
|
import "@fontsource/inter/400.css";
|
|
119
|
+
import "@fontsource/inter/500.css";
|
|
120
120
|
import "@fontsource/inter/600.css";
|
|
121
|
+
import "@fontsource/inter/700.css";
|
|
122
|
+
import "@fontsource/geist-mono/400.css";
|
|
123
|
+
import "@fontsource/geist-mono/500.css";
|
|
121
124
|
```
|
|
122
125
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
```html
|
|
126
|
-
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
127
|
-
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
128
|
-
<link
|
|
129
|
-
rel="stylesheet"
|
|
130
|
-
href="https://fonts.googleapis.com/css2?family=Geist:wght@300;400;500;600;700&family=Geist+Mono:wght@400;500;600&family=Outfit:wght@400;500;600;700&family=Manrope:wght@400;500;600;700;800&family=Inter:wght@400;500;600;700&display=swap"
|
|
131
|
-
/>
|
|
132
|
-
```
|
|
133
|
-
|
|
134
|
-
Any family you omit falls back per the `--font-*` token chain.
|
|
126
|
+
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.
|
|
135
127
|
|
|
136
|
-
## Policy
|
|
128
|
+
## Policy and development
|
|
137
129
|
|
|
138
|
-
|
|
139
|
-
- **Version as a product.** Semver. Breaking token changes are major bumps.
|
|
140
|
-
- **Sandbox-ui as the stress-test surface.** Tokens ship here only after sandbox-ui has used them in anger.
|
|
130
|
+
Shared tokens land here, not in copied app palettes. Additions require a cross-app reason. Version the package with semver; breaking public token changes require a major release. Shared UI and actual consuming apps are the stress-test surfaces.
|
|
141
131
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
pnpm
|
|
132
|
+
```sh
|
|
133
|
+
pnpm install --frozen-lockfile
|
|
134
|
+
pnpm --filter @tangle-network/brand check:compat
|
|
135
|
+
pnpm typecheck
|
|
146
136
|
pnpm build
|
|
137
|
+
pnpm test
|
|
147
138
|
```
|
|
148
139
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
```bash
|
|
152
|
-
# in ~/webb/brand
|
|
153
|
-
pnpm link --global
|
|
154
|
-
|
|
155
|
-
# in the consumer
|
|
156
|
-
pnpm link --global @tangle-network/brand
|
|
157
|
-
```
|
|
140
|
+
For actual browser cascade checks, serve the repository with any local static server and open `packages/brand/scripts/theme-browser.html`. Repeat with light/dark color preferences and reduced motion. The page reports assertions for both default contracts, all named modes, nested scopes, aliases and immediate code-color changes. This CSS fixture does not substitute for the React SSR/hydration tests or a consuming app build.
|
|
158
141
|
|
|
159
|
-
|
|
142
|
+
For local consumer iteration, use workspace linking or a `file:` dependency, then run that consumer's real build and browser path before release.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tangle-network/brand",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.10.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",
|
|
@@ -38,7 +38,8 @@
|
|
|
38
38
|
"./styles/theme.css": "./src/styles/theme.css",
|
|
39
39
|
"./styles/named-themes.css": "./src/styles/named-themes.css",
|
|
40
40
|
"./styles/ladders.css": "./src/styles/ladders.css",
|
|
41
|
-
"./styles/system.css": "./src/styles/system.css"
|
|
41
|
+
"./styles/system.css": "./src/styles/system.css",
|
|
42
|
+
"./styles/legacy-light.css": "./src/styles/legacy-light.css"
|
|
42
43
|
},
|
|
43
44
|
"peerDependencies": {
|
|
44
45
|
"react": ">=18"
|
|
@@ -63,8 +64,10 @@
|
|
|
63
64
|
"tailwind"
|
|
64
65
|
],
|
|
65
66
|
"scripts": {
|
|
66
|
-
"build": "tsdown",
|
|
67
|
+
"build": "node scripts/gen-compat.mjs --check && tsdown",
|
|
67
68
|
"gen:ladders": "node scripts/gen-ladders.mjs",
|
|
69
|
+
"gen:compat": "node scripts/gen-compat.mjs",
|
|
70
|
+
"check:compat": "node scripts/gen-compat.mjs --check && node --test scripts/gen-compat.test.mjs",
|
|
68
71
|
"dev": "tsdown --watch"
|
|
69
72
|
}
|
|
70
73
|
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { execFileSync } from 'node:child_process'
|
|
2
|
+
import { resolve } from 'node:path'
|
|
3
|
+
import { expect, it } from 'vitest'
|
|
4
|
+
|
|
5
|
+
it('keeps the generated light-default contract and all canonical mutations checked', () => {
|
|
6
|
+
const output = execFileSync(process.execPath, [
|
|
7
|
+
'--test',
|
|
8
|
+
resolve(process.cwd(), 'packages/brand/scripts/gen-compat.test.mjs'),
|
|
9
|
+
], { encoding: 'utf8' })
|
|
10
|
+
expect(output).toContain('fail 0')
|
|
11
|
+
})
|
package/src/styles/focus.test.ts
CHANGED
|
@@ -65,7 +65,7 @@ const scopes: Array<{ name: string; sources: string[] }> = [
|
|
|
65
65
|
})),
|
|
66
66
|
...["aubergine-light", "arena-light", "tangle-light"].map((name) => ({
|
|
67
67
|
name,
|
|
68
|
-
sources: [...blocksIn(themes, `[data-theme="${name}"]`), lightStatus, DARK],
|
|
68
|
+
sources: [...blocksIn(themes, `[data-theme="${name}"]`), lightStatus, LIGHT, DARK],
|
|
69
69
|
})),
|
|
70
70
|
{
|
|
71
71
|
name: "intelligence",
|