@motion-proto/live-tokens 0.74.0 → 0.76.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.
Files changed (171) hide show
  1. package/.claude/skills/live-tokens-check-compliance/SKILL.md +33 -38
  2. package/.claude/skills/live-tokens-create-component/SKILL copy.md +196 -0
  3. package/.claude/skills/live-tokens-create-component/SKILL.md +198 -146
  4. package/.claude/skills/live-tokens-create-component/references/contract-tests.md +95 -52
  5. package/.claude/skills/live-tokens-create-component/references/intrinsics.md +2 -2
  6. package/.claude/skills/live-tokens-create-component/references/linked-siblings.md +2 -2
  7. package/.claude/skills/live-tokens-create-component/references/sketch-mode.md +12 -12
  8. package/.claude/skills/live-tokens-create-component/references/token-naming.md +2 -1
  9. package/.claude/skills/live-tokens-create-page/SKILL.md +187 -0
  10. package/.claude/skills/live-tokens-create-page/references/interaction-sources.md +66 -0
  11. package/.claude/skills/live-tokens-create-page/references/layout-sources.md +87 -0
  12. package/.claude/skills/live-tokens-create-theme/SKILL.md +52 -48
  13. package/.claude/skills/live-tokens-create-theme/references/design-directions.md +1 -1
  14. package/.claude/skills/live-tokens-fix-findings/SKILL.md +69 -60
  15. package/.claude/skills/live-tokens-pick-component/SKILL.md +64 -79
  16. package/.claude/skills/live-tokens-set-colors/SKILL.md +47 -38
  17. package/.claude/skills/live-tokens-set-geometry/SKILL.md +58 -35
  18. package/.claude/skills/live-tokens-set-geometry/references/geometry-anchors.md +5 -3
  19. package/.claude/skills/live-tokens-set-type/SKILL.md +30 -30
  20. package/CHANGELOG.md +138 -0
  21. package/README.md +17 -7
  22. package/bin/check-component.mjs +91 -19
  23. package/bin/check-page.mjs +102 -21
  24. package/bin/cli.mjs +87 -113
  25. package/bin/contractRunner.mjs +945 -0
  26. package/bin/create.mjs +1 -1
  27. package/bin/lib/catalogue.mjs +37 -30
  28. package/bin/lib/findings.mjs +46 -15
  29. package/bin/lib/report.mjs +3 -3
  30. package/bin/lib/tokenVocabulary.mjs +4 -4
  31. package/bin/migrate-routes.mjs +5 -5
  32. package/bin/migrate.mjs +4 -4
  33. package/bin/save-theme.mjs +8 -9
  34. package/bin/set-colors.mjs +9 -11
  35. package/bin/set-geometry.mjs +7 -7
  36. package/bin/set-type.mjs +5 -7
  37. package/bin/setup-claude.mjs +110 -0
  38. package/dist-plugin/{chunk-W6Y4BWFB.js → chunk-6WGFOJXO.js} +22 -0
  39. package/dist-plugin/{chunk-7VRTBGJT.js → chunk-PDNL4NC5.js} +9 -2
  40. package/dist-plugin/{chunk-V3YF6CGT.js → chunk-SWXRVZKT.js} +43 -1
  41. package/dist-plugin/{dataPaths-BhWzd5cL.d.cts → dataPaths-BpIK_Obx.d.cts} +1 -0
  42. package/dist-plugin/{dataPaths-BhWzd5cL.d.ts → dataPaths-BpIK_Obx.d.ts} +1 -0
  43. package/dist-plugin/index.cjs +310 -147
  44. package/dist-plugin/index.d.cts +1 -1
  45. package/dist-plugin/index.d.ts +1 -1
  46. package/dist-plugin/index.js +218 -122
  47. package/dist-plugin/migrateData/index.cjs +66 -2
  48. package/dist-plugin/migrateData/index.d.cts +1 -1
  49. package/dist-plugin/migrateData/index.d.ts +1 -1
  50. package/dist-plugin/migrateData/index.js +3 -3
  51. package/dist-plugin/setColors/index.cjs +61 -4
  52. package/dist-plugin/setColors/index.d.cts +1 -1
  53. package/dist-plugin/setColors/index.d.ts +1 -1
  54. package/dist-plugin/setColors/index.js +5 -5
  55. package/dist-plugin/setGeometry/index.cjs +107 -44
  56. package/dist-plugin/setGeometry/index.d.cts +5 -5
  57. package/dist-plugin/setGeometry/index.d.ts +5 -5
  58. package/dist-plugin/setGeometry/index.js +51 -45
  59. package/dist-plugin/setType/index.cjs +15 -0
  60. package/dist-plugin/setType/index.d.cts +1 -1
  61. package/dist-plugin/setType/index.d.ts +1 -1
  62. package/dist-plugin/setType/index.js +1 -1
  63. package/dist-plugin/tokensCssMigrations/index.d.cts +1 -1
  64. package/dist-plugin/tokensCssMigrations/index.d.ts +1 -1
  65. package/dist-plugin/tokensCssMigrations/index.js +1 -1
  66. package/package.json +35 -8
  67. package/src/app/site.css +19 -9
  68. package/src/editor/bootstrap.ts +2 -0
  69. package/src/editor/component-editor/CollapsibleSectionEditor.svelte +4 -4
  70. package/src/editor/component-editor/DialogEditor.svelte +4 -4
  71. package/src/editor/component-editor/MenuSelectEditor.svelte +4 -1
  72. package/src/editor/component-editor/SegmentedControlEditor.svelte +6 -1
  73. package/src/editor/component-editor/TabBarEditor.svelte +1 -1
  74. package/src/editor/component-editor/TableEditor.svelte +2 -2
  75. package/src/editor/component-editor/scaffolding/TokenLayout.svelte +6 -3
  76. package/src/editor/component-editor/scaffolding/VariantGroup.svelte +41 -20
  77. package/src/editor/core/components/adjustAliases.ts +59 -45
  78. package/src/editor/core/components/aliasKinds.ts +9 -5
  79. package/src/editor/core/preview/themePreview.ts +9 -2
  80. package/src/editor/core/sketch/sketchLayer.ts +22 -0
  81. package/src/editor/core/store/editorStore.ts +10 -1
  82. package/src/editor/core/themes/buildColors.ts +3 -3
  83. package/src/editor/core/themes/liveStateStream.ts +26 -0
  84. package/src/editor/core/themes/migrations/2026-09-07-stroke-role-renames.ts +52 -0
  85. package/src/editor/core/themes/migrations/index.ts +2 -0
  86. package/src/editor/core/themes/themeDocumentSync.ts +22 -11
  87. package/src/editor/core/themes/themeService.ts +9 -2
  88. package/src/editor/pages/ComponentEditorPage.svelte +17 -1
  89. package/src/editor/pages/liveTokensEditorHandle.ts +23 -0
  90. package/src/editor/skill-atlas/SkillAtlas.svelte +75 -573
  91. package/src/editor/skill-atlas/TreeCanvas.svelte +263 -0
  92. package/src/editor/skill-atlas/TreeNodeCard.svelte +136 -53
  93. package/src/editor/skill-atlas/edges.ts +31 -0
  94. package/src/editor/skill-atlas/skillSources.generated.ts +20 -19
  95. package/src/editor/skill-atlas/skillTrees.ts +19 -3840
  96. package/src/editor/skill-atlas/trees/check-compliance.ts +183 -0
  97. package/src/editor/skill-atlas/trees/create-component.ts +275 -0
  98. package/src/editor/skill-atlas/trees/create-page.ts +320 -0
  99. package/src/editor/skill-atlas/trees/create-theme.ts +255 -0
  100. package/src/editor/skill-atlas/trees/fix-findings.ts +469 -0
  101. package/src/editor/skill-atlas/trees/pick-component.ts +300 -0
  102. package/src/editor/skill-atlas/trees/set-colors.ts +148 -0
  103. package/src/editor/skill-atlas/trees/set-geometry.ts +136 -0
  104. package/src/editor/skill-atlas/trees/set-type.ts +142 -0
  105. package/src/editor/skill-atlas/types.ts +3 -4
  106. package/src/editor/skill-atlas/wireLayout.ts +287 -0
  107. package/src/live-tokens/data/themes/autumn.json +15 -15
  108. package/src/live-tokens/data/themes/halloween.json +15 -15
  109. package/src/live-tokens/data/themes/midnight-study.json +15 -15
  110. package/src/live-tokens/data/themes/ocean.json +15 -15
  111. package/src/live-tokens/data/themes/royal-velvet.json +15 -15
  112. package/src/live-tokens/data/themes/sketchy.json +15 -15
  113. package/src/live-tokens/data/themes/spring-meadow.json +15 -15
  114. package/src/live-tokens/data/themes/sunset.json +15 -15
  115. package/src/system/components/Badge.svelte +7 -0
  116. package/src/system/components/Button.svelte +7 -0
  117. package/src/system/components/Callout.svelte +10 -6
  118. package/src/system/components/Card.svelte +23 -4
  119. package/src/system/components/CodeSnippet.svelte +4 -3
  120. package/src/system/components/CollapsibleSection.svelte +23 -8
  121. package/src/system/components/CornerBadge.svelte +6 -0
  122. package/src/system/components/Dialog.svelte +13 -6
  123. package/src/system/components/IconButton.svelte +9 -0
  124. package/src/system/components/Image.svelte +8 -0
  125. package/src/system/components/ImageLightbox.svelte +6 -0
  126. package/src/system/components/InlineEditActions.svelte +7 -0
  127. package/src/system/components/Input.svelte +7 -0
  128. package/src/system/components/MenuSelect.svelte +7 -0
  129. package/src/system/components/Notification.svelte +7 -0
  130. package/src/system/components/Panel.svelte +6 -0
  131. package/src/system/components/ProgressBar.svelte +5 -0
  132. package/src/system/components/RadioButton.svelte +11 -5
  133. package/src/system/components/SectionDivider.svelte +8 -0
  134. package/src/system/components/SegmentedControl.svelte +6 -0
  135. package/src/system/components/SideNavigation.svelte +6 -0
  136. package/src/system/components/Slider.svelte +7 -4
  137. package/src/system/components/TabBar.svelte +15 -9
  138. package/src/system/components/Table.svelte +8 -3
  139. package/src/system/components/Toggle.svelte +4 -4
  140. package/src/system/components/Tooltip.svelte +6 -0
  141. package/src/testing-js/chunk-AO7EZHYV.js +776 -0
  142. package/src/testing-js/chunk-AO7EZHYV.js.map +1 -0
  143. package/src/testing-js/chunk-FAFOAWYL.js +39 -0
  144. package/src/testing-js/chunk-FAFOAWYL.js.map +1 -0
  145. package/src/testing-js/chunk-L73N4NSO.js +23 -0
  146. package/src/testing-js/chunk-L73N4NSO.js.map +1 -0
  147. package/src/testing-js/chunk-LXR3MN6N.js +3063 -0
  148. package/src/testing-js/chunk-LXR3MN6N.js.map +1 -0
  149. package/src/testing-js/chunk-ZMSX6CXR.js +53 -0
  150. package/src/testing-js/chunk-ZMSX6CXR.js.map +1 -0
  151. package/src/testing-js/component-alias.contract.js +81 -0
  152. package/src/testing-js/component-alias.contract.js.map +1 -0
  153. package/src/testing-js/component-editor.contract.js +62 -0
  154. package/src/testing-js/component-editor.contract.js.map +1 -0
  155. package/src/testing-js/component-render.contract.js +568 -0
  156. package/src/testing-js/component-render.contract.js.map +1 -0
  157. package/src/testing-js/index.d.ts +293 -0
  158. package/src/testing-js/index.js +222 -0
  159. package/src/testing-js/index.js.map +1 -0
  160. package/src/testing-js/registry.contract.js +39 -0
  161. package/src/testing-js/registry.contract.js.map +1 -0
  162. package/src/testing-js/vitest-BE6uGF31.d.ts +73 -0
  163. package/src/testing-js/vitest.d.ts +3 -0
  164. package/src/testing-js/vitest.js +13 -0
  165. package/src/testing-js/vitest.js.map +1 -0
  166. package/template/README.md +13 -0
  167. package/template/_gitignore +6 -0
  168. package/template/package.json +3 -1
  169. package/template/src/pages/Home.svelte +4 -18
  170. package/.claude/skills/live-tokens-build-page/SKILL.md +0 -103
  171. package/.claude/skills/live-tokens-build-page/references/layout-sources.md +0 -48
@@ -0,0 +1,142 @@
1
+ import type { SkillTree } from '../types';
2
+
3
+ export const setType: SkillTree = {
4
+ "id": "live-tokens-set-type",
5
+ "digest": "sha256:0c8d26a16313f9ee",
6
+ "title": "set-type",
7
+ "tagline": "Set Typefaces from Google Fonts",
8
+ "nodes": [
9
+ {
10
+ "id": "st-trig",
11
+ "row": 0,
12
+ "kind": "trigger",
13
+ "title": "Choose or pair font families",
14
+ "desc": "Sets a theme's fonts: a Google Fonts pairing for the display and body stacks, verified for the weights the text styles need.",
15
+ "lines": [3, 3],
16
+ "anchor": "description: Set a live-tokens theme's type: a Google Fonts "
17
+ },
18
+ {
19
+ "id": "st-anchor",
20
+ "row": 1,
21
+ "kind": "ref",
22
+ "title": "Read the type anchor",
23
+ "desc": "An anchor's entry overrides the Voice table.",
24
+ "lines": [14, 14],
25
+ "anchor": "Read the type intent and any anchor live-tokens-create-theme",
26
+ "reference": "references/type-anchors.md"
27
+ },
28
+ {
29
+ "id": "st-input",
30
+ "row": 2,
31
+ "kind": "step",
32
+ "title": "Write the font pairing",
33
+ "desc": "A display family and a body family in one file. The body face comes first, and the display face contrasts with it and matches the voice.",
34
+ "lines": [15, 15],
35
+ "anchor": "Choose the pairing and write it to `scratch/font-pairing.jso",
36
+ "chips": [
37
+ {
38
+ "label": "Choose the body face first",
39
+ "lines": [30, 34],
40
+ "anchor": "## Choose the body face first",
41
+ "anchorEnd": "The shipped text styles ask the display face for 600 and the"
42
+ },
43
+ {
44
+ "label": "The font matrix",
45
+ "lines": [36, 50],
46
+ "anchor": "## The font matrix",
47
+ "anchorEnd": "Many faces sit between columns. When one straddles, say so a"
48
+ },
49
+ {
50
+ "label": "Voice",
51
+ "lines": [52, 66],
52
+ "anchor": "## Voice",
53
+ "anchorEnd": "Match the type to the design direction the color came from. "
54
+ },
55
+ {
56
+ "label": "Shortcuts",
57
+ "lines": [68, 75],
58
+ "anchor": "## Shortcuts",
59
+ "anchorEnd": "**Serif display over sans body** when nothing else decides i"
60
+ },
61
+ {
62
+ "label": "Watch for",
63
+ "lines": [77, 82],
64
+ "anchor": "## Watch for",
65
+ "anchorEnd": "**Sets of themes.** No two share a display face or a body fa"
66
+ }
67
+ ]
68
+ },
69
+ {
70
+ "id": "st-cli",
71
+ "row": 3,
72
+ "kind": "cli",
73
+ "title": "Run set-type",
74
+ "desc": "The command verifies each family on Google Fonts and reports missing weights.",
75
+ "lines": [16, 16],
76
+ "anchor": "Run `npx live-tokens set-type scratch/font-pairing.json`. It"
77
+ },
78
+ {
79
+ "id": "st-fail",
80
+ "row": 4,
81
+ "kind": "gate",
82
+ "title": "Correct the font pairing",
83
+ "desc": "A family not on Google Fonts fails the run. Fix the file and run again.",
84
+ "lines": [17, 17],
85
+ "anchor": "Read the report. Name a missing weight and offer an alternat"
86
+ },
87
+ {
88
+ "id": "st-reply",
89
+ "row": 5,
90
+ "kind": "step",
91
+ "title": "Reply with the result",
92
+ "desc": "Report the two families, their construction, and any missing weight.",
93
+ "lines": [17, 18],
94
+ "anchor": "Read the report. Name a missing weight and offer an alternat",
95
+ "anchorEnd": "Reply with the two families, the form model behind each, the"
96
+ },
97
+ {
98
+ "id": "st-verify",
99
+ "row": 6,
100
+ "kind": "done",
101
+ "title": "Verify the type",
102
+ "desc": "The CLI passes, each URL matches the family's weights, and the app shows the new type.",
103
+ "lines": [88, 93],
104
+ "anchor": "## Verify",
105
+ "anchorEnd": "To revert, run the previous pairing file, or load the open t"
106
+ }
107
+ ],
108
+ "edges": [
109
+ {
110
+ "to": "st-anchor",
111
+ "from": "st-trig"
112
+ },
113
+ {
114
+ "to": "st-fail",
115
+ "from": "st-cli",
116
+ "label": "exit 1"
117
+ },
118
+ {
119
+ "to": "st-cli",
120
+ "from": "st-fail",
121
+ "label": "rerun",
122
+ "back": true
123
+ },
124
+ {
125
+ "to": "st-input",
126
+ "from": "st-anchor"
127
+ },
128
+ {
129
+ "to": "st-cli",
130
+ "from": "st-input"
131
+ },
132
+ {
133
+ "to": "st-reply",
134
+ "from": "st-cli",
135
+ "label": "exit 0"
136
+ },
137
+ {
138
+ "to": "st-verify",
139
+ "from": "st-reply"
140
+ }
141
+ ]
142
+ };
@@ -1,6 +1,6 @@
1
1
  export type NodeKind =
2
2
  | 'trigger' | 'step' | 'decide' | 'cli' | 'hand'
3
- | 'gate' | 'ok' | 'ref' | 'ask' | 'chipset' | 'done';
3
+ | 'gate' | 'ok' | 'ref' | 'chipset' | 'done';
4
4
 
5
5
  /** Inclusive 1-based line range into the skill's SKILL.md. */
6
6
  export type LineRange = [number, number];
@@ -27,14 +27,11 @@ export interface TreeNode extends Anchored {
27
27
  title: string;
28
28
  /** Overrides the kind's default eyebrow. */
29
29
  tag?: string;
30
- /** The skill's own step number, where the skill numbers its workflow. */
31
- n?: string;
32
30
  desc?: string;
33
31
  /** A `references/*.md` the node stands for, opened as a tab in the source pane. */
34
32
  reference?: string;
35
33
  lines?: LineRange;
36
34
  chips?: Chip[];
37
- command?: string;
38
35
  }
39
36
 
40
37
  export interface Edge {
@@ -42,6 +39,8 @@ export interface Edge {
42
39
  to: string;
43
40
  /** The answer that selects this branch, drawn on the wire. */
44
41
  label?: string;
42
+ /** Answers the card already shows as badges. They light the wire when a badge is selected and are not drawn. */
43
+ answers?: string[];
45
44
  /** A re-run loop, routed up the gutter lane. */
46
45
  back?: boolean;
47
46
  }
@@ -0,0 +1,287 @@
1
+ import type { Edge } from './types';
2
+
3
+ /** A card's laid-out rectangle, relative to the canvas. */
4
+ export interface Box {
5
+ left: number;
6
+ right: number;
7
+ top: number;
8
+ bottom: number;
9
+ midX: number;
10
+ midY: number;
11
+ }
12
+
13
+ export interface Wire {
14
+ d: string;
15
+ back: boolean;
16
+ lit: boolean;
17
+ /** Trunks and buses carry no arrowhead; only the segment that arrives does. */
18
+ arrow: boolean;
19
+ }
20
+
21
+ export interface Label {
22
+ x: number;
23
+ y: number;
24
+ /** One entry per answer sharing the wire. */
25
+ lines: string[];
26
+ lit: boolean;
27
+ /** Lane labels run along the lane; branch labels sit across the drop. */
28
+ rotate: boolean;
29
+ }
30
+
31
+ export interface Drawing {
32
+ wires: Wire[];
33
+ labels: Label[];
34
+ }
35
+
36
+ /** Clearance between a card border and the wire, so the arrowhead reads as
37
+ * an arrival rather than as part of the card outline. */
38
+ const STANDOFF = 6;
39
+ const CORNER = 12;
40
+ /** How far outside the cards a lane sits, and how far one lane nests
41
+ * inside the next when two share a stretch of gutter. */
42
+ const LANE_INSET = 40;
43
+ const LANE_STEP = 16;
44
+
45
+ /** Cards laid out in one row share a top to within a pixel; cards pushed onto
46
+ * a second wrapped line do not, and a single bus cannot serve both. */
47
+ function aligned(values: number[]): boolean {
48
+ return Math.max(...values) - Math.min(...values) < 4;
49
+ }
50
+
51
+ function push<T>(map: Map<string, T[]>, key: string, value: T) {
52
+ const list = map.get(key);
53
+ if (list) list.push(value);
54
+ else map.set(key, [value]);
55
+ }
56
+
57
+ function label(out: Drawing, text: string | undefined, x: number, y: number, lit: boolean, rotate = false) {
58
+ if (text) out.labels.push({ x, y, lines: text.split('\n'), lit, rotate });
59
+ }
60
+
61
+ /** One trunk down from the parent, one bus across, one drop into each child —
62
+ * so a split reads as a single decision rather than as N crossing curves.
63
+ * Each answer sits on its own drop. */
64
+ function fanOut(out: Drawing, a: Box, kids: Box[], edges: Edge[], lit: (i: number) => boolean) {
65
+ const top = kids[0].top;
66
+ const busY = (a.bottom + top) / 2;
67
+ const xs = kids.map((k) => k.midX);
68
+ const r = Math.max(
69
+ 0,
70
+ Math.min(CORNER, (Math.max(...xs) - Math.min(...xs)) / 2, (busY - a.bottom) / 2, (top - busY) / 2),
71
+ );
72
+ const turns = xs.map((x) => (x < a.midX ? x + r : x > a.midX ? x - r : x));
73
+ const busLeft = Math.min(...turns, a.midX);
74
+ const busRight = Math.max(...turns, a.midX);
75
+ const anyLit = kids.some((_, i) => lit(i));
76
+
77
+ out.wires.push({
78
+ back: false,
79
+ arrow: false,
80
+ lit: anyLit,
81
+ d: `M ${a.midX} ${a.bottom + STANDOFF} L ${a.midX} ${busY} M ${busLeft} ${busY} L ${busRight} ${busY}`,
82
+ });
83
+ kids.forEach((k, i) => {
84
+ const x = k.midX;
85
+ const turn = turns[i];
86
+ out.wires.push({
87
+ back: false,
88
+ arrow: true,
89
+ lit: lit(i),
90
+ d:
91
+ turn === x
92
+ ? `M ${x} ${busY} L ${x} ${top - STANDOFF}`
93
+ : `M ${turn} ${busY} Q ${x} ${busY}, ${x} ${busY + r} L ${x} ${top - STANDOFF}`,
94
+ });
95
+ label(out, edges[i].label, x, (busY + r + top - STANDOFF) / 2, lit(i));
96
+ });
97
+ }
98
+
99
+ /** The mirror of `fanOut`: risers up to a shared bus, one trunk into the child,
100
+ * so several outcomes converging land as one arrow instead of a pile. */
101
+ function fanIn(out: Drawing, parents: Box[], b: Box, edges: Edge[], lit: (i: number) => boolean) {
102
+ const bottom = parents[0].bottom;
103
+ const busY = (bottom + b.top) / 2;
104
+ const xs = parents.map((p) => p.midX);
105
+ const r = Math.max(
106
+ 0,
107
+ Math.min(CORNER, (Math.max(...xs) - Math.min(...xs)) / 2, (busY - bottom) / 2, (b.top - busY) / 2),
108
+ );
109
+ const turns = xs.map((x) => (x < b.midX ? x + r : x > b.midX ? x - r : x));
110
+ const anyLit = parents.some((_, i) => lit(i));
111
+
112
+ parents.forEach((p, i) => {
113
+ const x = p.midX;
114
+ const turn = turns[i];
115
+ out.wires.push({
116
+ back: false,
117
+ arrow: false,
118
+ lit: lit(i),
119
+ d:
120
+ turn === x
121
+ ? `M ${x} ${bottom + STANDOFF} L ${x} ${busY}`
122
+ : `M ${x} ${bottom + STANDOFF} L ${x} ${busY - r} Q ${x} ${busY}, ${turn} ${busY}`,
123
+ });
124
+ label(out, edges[i].label, x, (bottom + STANDOFF + busY - r) / 2, lit(i));
125
+ });
126
+ out.wires.push({
127
+ back: false,
128
+ arrow: true,
129
+ lit: anyLit,
130
+ d:
131
+ `M ${Math.min(...turns, b.midX)} ${busY} L ${Math.max(...turns, b.midX)} ${busY}` +
132
+ ` M ${b.midX} ${busY} L ${b.midX} ${b.top - STANDOFF}`,
133
+ });
134
+ }
135
+
136
+ /** A plain parent-to-child curve, and the fallback wherever a bus would have
137
+ * to cross a card: children wrapped onto a second line, or parents staggered
138
+ * across rows. */
139
+ function curve(out: Drawing, a: Box, b: Box, edge: Edge, lit: boolean) {
140
+ const y1 = a.bottom + STANDOFF;
141
+ const y2 = b.top - STANDOFF;
142
+ const bend = Math.max(16, (y2 - y1) * 0.55);
143
+ out.wires.push({
144
+ back: false,
145
+ arrow: true,
146
+ lit,
147
+ d: `M ${a.midX} ${y1} C ${a.midX} ${y1 + bend}, ${b.midX} ${y2 - bend}, ${b.midX} ${y2}`,
148
+ });
149
+ label(out, edge.label, (a.midX + b.midX) / 2, (y1 + y2) / 2, lit);
150
+ }
151
+
152
+ /** Every card the span passes between, not just the two it joins. */
153
+ function crossed(a: Box, b: Box, all: Box[]): Box[] {
154
+ const top = Math.min(a.midY, b.midY);
155
+ const bottom = Math.max(a.midY, b.midY);
156
+ return all.filter((x) => x !== a && x !== b && x.bottom > top && x.top < bottom);
157
+ }
158
+
159
+ /** On the spine: no other card shares its row. A wrapped branch row also
160
+ * "crosses" cards on its way down, and those stay curves. */
161
+ function alone(box: Box, all: Box[]): boolean {
162
+ return all.every((x) => x === box || !aligned([x.top, box.top]));
163
+ }
164
+
165
+ /** Re-run loops ride a lane in the left gutter the centred spine opens up,
166
+ * rather than bulging around the card they leave. A card with siblings to
167
+ * its left leaves from its bottom and runs under the row, so the wire never
168
+ * passes through the cards beside it. */
169
+ function backEdge(out: Drawing, a: Box, b: Box, lane: number, edge: Edge, lit: boolean, under?: number) {
170
+ const dir = Math.sign(b.midY - a.midY) || -1;
171
+ const r = Math.max(0, Math.min(CORNER, (a.left - STANDOFF - lane) / 2, Math.abs(a.midY - b.midY) / 2));
172
+ const d =
173
+ under === undefined
174
+ ? `M ${a.left - STANDOFF} ${a.midY} L ${lane + r} ${a.midY}` +
175
+ ` Q ${lane} ${a.midY}, ${lane} ${a.midY + dir * r}`
176
+ : `M ${a.midX} ${a.bottom + STANDOFF} L ${a.midX} ${under - r}` +
177
+ ` Q ${a.midX} ${under}, ${a.midX - r} ${under} L ${lane + r} ${under}` +
178
+ ` Q ${lane} ${under}, ${lane} ${under - r}`;
179
+ out.wires.push({
180
+ back: true,
181
+ arrow: true,
182
+ lit,
183
+ d:
184
+ d +
185
+ ` L ${lane} ${b.midY - dir * r} Q ${lane} ${b.midY}, ${lane + r} ${b.midY}` +
186
+ ` L ${b.left - STANDOFF} ${b.midY}`,
187
+ });
188
+ label(out, edge.label, lane, (a.midY + b.midY) / 2, lit, true);
189
+ }
190
+
191
+ /** A branch that skips a row rides the right gutter, the mirror of a loop,
192
+ * so a "no" never has to be drawn through the card the "yes" leads to. */
193
+ function skipEdge(out: Drawing, a: Box, b: Box, lane: number, edge: Edge, lit: boolean) {
194
+ const r = Math.max(0, Math.min(CORNER, (lane - a.right - STANDOFF) / 2, Math.abs(a.midY - b.midY) / 2));
195
+ out.wires.push({
196
+ back: false,
197
+ arrow: true,
198
+ lit,
199
+ d:
200
+ `M ${a.right + STANDOFF} ${a.midY} L ${lane - r} ${a.midY}` +
201
+ ` Q ${lane} ${a.midY}, ${lane} ${a.midY + r}` +
202
+ ` L ${lane} ${b.midY - r} Q ${lane} ${b.midY}, ${lane - r} ${b.midY}` +
203
+ ` L ${b.right + STANDOFF} ${b.midY}`,
204
+ });
205
+ label(out, edge.label, lane, (a.midY + b.midY) / 2, lit, true);
206
+ }
207
+
208
+ /**
209
+ * Routes every edge between the measured cards. `lit` answers whether a wire
210
+ * joining the given node ids belongs to the current selection.
211
+ */
212
+ export function routeWires(boxes: Map<string, Box>, edges: Edge[], lit: (edge: Edge) => boolean): Drawing {
213
+ const all = [...boxes.values()];
214
+ const children = new Map<string, Edge[]>();
215
+ const parents = new Map<string, Edge[]>();
216
+ for (const edge of edges) {
217
+ if (edge.back) continue;
218
+ push(children, edge.from, edge);
219
+ push(parents, edge.to, edge);
220
+ }
221
+
222
+ const out: Drawing = { wires: [], labels: [] };
223
+ const claimed = new Set<Edge>();
224
+
225
+ for (const [from, kidEdges] of children) {
226
+ const a = boxes.get(from);
227
+ const kids = kidEdges.map((e) => boxes.get(e.to));
228
+ if (!a || kidEdges.length < 2 || kids.some((k) => !k)) continue;
229
+ const kept = kids as Box[];
230
+ if (!aligned(kept.map((k) => k.top))) continue;
231
+ kidEdges.forEach((e) => claimed.add(e));
232
+ fanOut(out, a, kept, kidEdges, (i) => lit(kidEdges[i]));
233
+ }
234
+
235
+ for (const [to, parentEdges] of parents) {
236
+ const open = parentEdges.filter((e) => !claimed.has(e));
237
+ const b = boxes.get(to);
238
+ const ups = open.map((e) => boxes.get(e.from));
239
+ if (!b || open.length < 2 || ups.some((u) => !u)) continue;
240
+ const kept = ups as Box[];
241
+ if (!aligned(kept.map((u) => u.bottom))) continue;
242
+ open.forEach((e) => claimed.add(e));
243
+ fanIn(out, kept, b, open, (i) => lit(open[i]));
244
+ }
245
+
246
+ // Longest skip first, so a long one always rides outside the skips it spans.
247
+ const skips: { edge: Edge; a: Box; b: Box; over: Box[] }[] = [];
248
+ for (const edge of edges) {
249
+ if (edge.back || claimed.has(edge)) continue;
250
+ const a = boxes.get(edge.from);
251
+ const b = boxes.get(edge.to);
252
+ if (!a || !b) continue;
253
+ const over = alone(a, all) && alone(b, all) ? crossed(a, b, all) : [];
254
+ if (over.length > 0) skips.push({ edge, a, b, over });
255
+ else curve(out, a, b, edge, lit(edge));
256
+ }
257
+ skips.sort((x, y) => Math.abs(y.a.midY - y.b.midY) - Math.abs(x.a.midY - x.b.midY));
258
+ let outer = -Infinity;
259
+ for (const { edge, a, b, over } of skips) {
260
+ const lane = Math.max(...over.map((x) => x.right), a.right, b.right) + LANE_INSET;
261
+ const placed = Math.max(lane, outer + LANE_STEP);
262
+ outer = placed;
263
+ skipEdge(out, a, b, placed, edge, lit(edge));
264
+ }
265
+
266
+ // Shortest loop first, so a long one always nests outside the loops it spans.
267
+ const loops = edges
268
+ .filter((e) => e.back)
269
+ .map((edge) => ({ edge, a: boxes.get(edge.from), b: boxes.get(edge.to) }))
270
+ .filter((e): e is { edge: Edge; a: Box; b: Box } => !!e.a && !!e.b)
271
+ .sort((x, y) => Math.abs(x.a.midY - x.b.midY) - Math.abs(y.a.midY - y.b.midY));
272
+
273
+ let inner = Infinity;
274
+ let unders = 0;
275
+ for (const { edge, a, b } of loops) {
276
+ const beside = all.filter((x) => x !== a && x.right <= a.left && x.bottom > a.midY && x.top < a.midY);
277
+ const clear = Math.min(...crossed(a, b, all).map((x) => x.left), ...beside.map((x) => x.left), a.left, b.left) - LANE_INSET;
278
+ const lane = Math.max(4, Math.min(clear, inner - LANE_STEP));
279
+ inner = lane;
280
+ // Each wire under the row takes its own height, in step with its lane, so
281
+ // the loops nest instead of sharing one line.
282
+ const under = beside.length ? a.bottom + STANDOFF + CORNER + LANE_STEP * unders++ : undefined;
283
+ backEdge(out, a, b, lane, edge, lit(edge), under);
284
+ }
285
+
286
+ return out;
287
+ }
@@ -2895,8 +2895,8 @@
2895
2895
  "--collapsiblesection-chromeless-hover-icon-size": "--icon-size-xs",
2896
2896
  "--collapsiblesection-chromeless-expanded-padding": "--space-8",
2897
2897
  "--collapsiblesection-divider-default-surface": "--color-transparent",
2898
- "--collapsiblesection-divider-default-border": "--border-brand",
2899
- "--collapsiblesection-divider-default-border-width": "--border-width-1",
2898
+ "--collapsiblesection-divider-default-hairline-color": "--border-brand",
2899
+ "--collapsiblesection-divider-default-hairline-thickness": "--border-width-1",
2900
2900
  "--collapsiblesection-divider-default-padding": "--space-8",
2901
2901
  "--collapsiblesection-divider-default-label": "--text-primary",
2902
2902
  "--collapsiblesection-divider-default-label-font-family": "--font-sans",
@@ -2906,8 +2906,8 @@
2906
2906
  "--collapsiblesection-divider-default-icon": "--text-primary",
2907
2907
  "--collapsiblesection-divider-default-icon-size": "--icon-size-xs",
2908
2908
  "--collapsiblesection-divider-hover-surface": "--color-transparent",
2909
- "--collapsiblesection-divider-hover-border": "--border-neutral",
2910
- "--collapsiblesection-divider-hover-border-width": "--border-width-1",
2909
+ "--collapsiblesection-divider-hover-hairline-color": "--border-neutral",
2910
+ "--collapsiblesection-divider-hover-hairline-thickness": "--border-width-1",
2911
2911
  "--collapsiblesection-divider-hover-padding": "--space-8",
2912
2912
  "--collapsiblesection-divider-hover-label": "--text-primary",
2913
2913
  "--collapsiblesection-divider-hover-label-font-family": "--font-sans",
@@ -3004,8 +3004,8 @@
3004
3004
  "--dialog-shadow": "--shadow-2xl",
3005
3005
  "--dialog-blur": "--blur-none",
3006
3006
  "--dialog-header-surface": "--surface-neutral-lower",
3007
- "--dialog-header-border": "--border-neutral-subtle",
3008
- "--dialog-header-border-width": "--border-width-1",
3007
+ "--dialog-header-divider": "--border-neutral-subtle",
3008
+ "--dialog-header-divider-width": "--border-width-1",
3009
3009
  "--dialog-header-padding": "--space-12",
3010
3010
  "--dialog-title": "--text-primary",
3011
3011
  "--dialog-title-font-family": "--font-sans",
@@ -3020,8 +3020,8 @@
3020
3020
  "--dialog-body-font-size": "--font-size-md",
3021
3021
  "--dialog-body-font-weight": "--font-weight-normal",
3022
3022
  "--dialog-body-line-height": "--line-height-normal",
3023
- "--dialog-footer-border": "--border-neutral-subtle",
3024
- "--dialog-footer-border-width": "--border-width-1",
3023
+ "--dialog-footer-divider": "--border-neutral-subtle",
3024
+ "--dialog-footer-divider-width": "--border-width-1",
3025
3025
  "--dialog-footer-padding": "--space-24"
3026
3026
  }
3027
3027
  },
@@ -3808,7 +3808,7 @@
3808
3808
  "--tabbar-default-tab-border-width": "--border-width-0",
3809
3809
  "--tabbar-default-tab-top-radius": "--radius-md",
3810
3810
  "--tabbar-default-tab-bottom-radius": "--radius-md",
3811
- "--tabbar-default-indicator-border-width": "--border-width-2",
3811
+ "--tabbar-default-indicator-width": "--border-width-2",
3812
3812
  "--tabbar-hover-text": "--text-secondary",
3813
3813
  "--tabbar-hover-surface": "--surface-neutral-lower",
3814
3814
  "--tabbar-hover-text-font-family": "--font-sans",
@@ -3822,7 +3822,7 @@
3822
3822
  "--tabbar-hover-tab-border-width": "--border-width-0",
3823
3823
  "--tabbar-hover-tab-top-radius": "--radius-md",
3824
3824
  "--tabbar-hover-tab-bottom-radius": "--radius-md",
3825
- "--tabbar-hover-indicator-border-width": "--border-width-2",
3825
+ "--tabbar-hover-indicator-width": "--border-width-2",
3826
3826
  "--tabbar-active-text": "--text-primary",
3827
3827
  "--tabbar-active-border": "--color-brand-500",
3828
3828
  "--tabbar-active-surface": "--tint-low",
@@ -3836,7 +3836,7 @@
3836
3836
  "--tabbar-active-tab-border-width": "--border-width-0",
3837
3837
  "--tabbar-active-tab-top-radius": "--radius-md",
3838
3838
  "--tabbar-active-tab-bottom-radius": "--radius-md",
3839
- "--tabbar-active-indicator-border-width": "--border-width-2",
3839
+ "--tabbar-active-indicator-width": "--border-width-2",
3840
3840
  "--tabbar-disabled-text": "--text-disabled",
3841
3841
  "--tabbar-disabled-text-font-family": "--font-sans",
3842
3842
  "--tabbar-disabled-text-font-size": "--font-size-md",
@@ -3850,7 +3850,7 @@
3850
3850
  "--tabbar-disabled-tab-border-width": "--border-width-0",
3851
3851
  "--tabbar-disabled-tab-top-radius": "--radius-md",
3852
3852
  "--tabbar-disabled-tab-bottom-radius": "--radius-md",
3853
- "--tabbar-disabled-indicator-border-width": "--border-width-2"
3853
+ "--tabbar-disabled-indicator-width": "--border-width-2"
3854
3854
  }
3855
3855
  },
3856
3856
  "table": {
@@ -3871,8 +3871,8 @@
3871
3871
  "--table-default-header-font-weight": "--font-weight-semibold",
3872
3872
  "--table-default-header-line-height": "--line-height-none",
3873
3873
  "--table-default-header-padding": "--space-20",
3874
- "--table-default-header-border": "--border-neutral",
3875
- "--table-default-header-border-width": "--border-width-1",
3874
+ "--table-default-header-divider": "--border-neutral",
3875
+ "--table-default-header-divider-width": "--border-width-1",
3876
3876
  "--table-default-cell-text": "--text-secondary",
3877
3877
  "--table-default-cell-font-family": "--font-sans",
3878
3878
  "--table-default-cell-font-size": "--font-size-md",
@@ -3994,5 +3994,5 @@
3994
3994
  }
3995
3995
  }
3996
3996
  },
3997
- "componentSchemaVersion": 26
3997
+ "componentSchemaVersion": 27
3998
3998
  }
@@ -2934,8 +2934,8 @@
2934
2934
  "--collapsiblesection-chromeless-hover-icon-size": "--icon-size-xs",
2935
2935
  "--collapsiblesection-chromeless-expanded-padding": "--space-4",
2936
2936
  "--collapsiblesection-divider-default-surface": "--color-transparent",
2937
- "--collapsiblesection-divider-default-border": "--border-brand",
2938
- "--collapsiblesection-divider-default-border-width": "--border-width-3",
2937
+ "--collapsiblesection-divider-default-hairline-color": "--border-brand",
2938
+ "--collapsiblesection-divider-default-hairline-thickness": "--border-width-3",
2939
2939
  "--collapsiblesection-divider-default-padding": "--space-4",
2940
2940
  "--collapsiblesection-divider-default-label": "--text-primary",
2941
2941
  "--collapsiblesection-divider-default-label-font-family": "--font-sans",
@@ -2945,8 +2945,8 @@
2945
2945
  "--collapsiblesection-divider-default-icon": "--text-primary",
2946
2946
  "--collapsiblesection-divider-default-icon-size": "--icon-size-xs",
2947
2947
  "--collapsiblesection-divider-hover-surface": "--color-transparent",
2948
- "--collapsiblesection-divider-hover-border": "--border-neutral",
2949
- "--collapsiblesection-divider-hover-border-width": "--border-width-3",
2948
+ "--collapsiblesection-divider-hover-hairline-color": "--border-neutral",
2949
+ "--collapsiblesection-divider-hover-hairline-thickness": "--border-width-3",
2950
2950
  "--collapsiblesection-divider-hover-padding": "--space-4",
2951
2951
  "--collapsiblesection-divider-hover-label": "--text-primary",
2952
2952
  "--collapsiblesection-divider-hover-label-font-family": "--font-sans",
@@ -3043,8 +3043,8 @@
3043
3043
  "--dialog-shadow": "--shadow-2xl",
3044
3044
  "--dialog-blur": "--blur-none",
3045
3045
  "--dialog-header-surface": "--surface-neutral-lower",
3046
- "--dialog-header-border": "--border-neutral-subtle",
3047
- "--dialog-header-border-width": "--border-width-3",
3046
+ "--dialog-header-divider": "--border-neutral-subtle",
3047
+ "--dialog-header-divider-width": "--border-width-3",
3048
3048
  "--dialog-header-padding": "--space-6",
3049
3049
  "--dialog-title": "--text-primary",
3050
3050
  "--dialog-title-font-family": "--font-sans",
@@ -3059,8 +3059,8 @@
3059
3059
  "--dialog-body-font-size": "--font-size-md",
3060
3060
  "--dialog-body-font-weight": "--font-weight-normal",
3061
3061
  "--dialog-body-line-height": "--line-height-normal",
3062
- "--dialog-footer-border": "--border-neutral-subtle",
3063
- "--dialog-footer-border-width": "--border-width-3",
3062
+ "--dialog-footer-divider": "--border-neutral-subtle",
3063
+ "--dialog-footer-divider-width": "--border-width-3",
3064
3064
  "--dialog-footer-padding": "--space-12"
3065
3065
  }
3066
3066
  },
@@ -3847,7 +3847,7 @@
3847
3847
  "--tabbar-default-tab-border-width": "--border-width-2",
3848
3848
  "--tabbar-default-tab-top-radius": "--radius-none",
3849
3849
  "--tabbar-default-tab-bottom-radius": "--radius-none",
3850
- "--tabbar-default-indicator-border-width": "--border-width-4",
3850
+ "--tabbar-default-indicator-width": "--border-width-4",
3851
3851
  "--tabbar-hover-text": "--text-secondary",
3852
3852
  "--tabbar-hover-surface": "--surface-neutral-lower",
3853
3853
  "--tabbar-hover-text-font-family": "--font-sans",
@@ -3861,7 +3861,7 @@
3861
3861
  "--tabbar-hover-tab-border-width": "--border-width-2",
3862
3862
  "--tabbar-hover-tab-top-radius": "--radius-none",
3863
3863
  "--tabbar-hover-tab-bottom-radius": "--radius-none",
3864
- "--tabbar-hover-indicator-border-width": "--border-width-4",
3864
+ "--tabbar-hover-indicator-width": "--border-width-4",
3865
3865
  "--tabbar-active-text": "--text-primary",
3866
3866
  "--tabbar-active-border": "--color-brand-500",
3867
3867
  "--tabbar-active-surface": "--tint-low",
@@ -3875,7 +3875,7 @@
3875
3875
  "--tabbar-active-tab-border-width": "--border-width-2",
3876
3876
  "--tabbar-active-tab-top-radius": "--radius-none",
3877
3877
  "--tabbar-active-tab-bottom-radius": "--radius-none",
3878
- "--tabbar-active-indicator-border-width": "--border-width-4",
3878
+ "--tabbar-active-indicator-width": "--border-width-4",
3879
3879
  "--tabbar-disabled-text": "--text-disabled",
3880
3880
  "--tabbar-disabled-text-font-family": "--font-sans",
3881
3881
  "--tabbar-disabled-text-font-size": "--font-size-md",
@@ -3889,7 +3889,7 @@
3889
3889
  "--tabbar-disabled-tab-border-width": "--border-width-2",
3890
3890
  "--tabbar-disabled-tab-top-radius": "--radius-none",
3891
3891
  "--tabbar-disabled-tab-bottom-radius": "--radius-none",
3892
- "--tabbar-disabled-indicator-border-width": "--border-width-4"
3892
+ "--tabbar-disabled-indicator-width": "--border-width-4"
3893
3893
  }
3894
3894
  },
3895
3895
  "table": {
@@ -3910,8 +3910,8 @@
3910
3910
  "--table-default-header-font-weight": "--font-weight-semibold",
3911
3911
  "--table-default-header-line-height": "--line-height-none",
3912
3912
  "--table-default-header-padding": "--space-10",
3913
- "--table-default-header-border": "--border-neutral",
3914
- "--table-default-header-border-width": "--border-width-3",
3913
+ "--table-default-header-divider": "--border-neutral",
3914
+ "--table-default-header-divider-width": "--border-width-3",
3915
3915
  "--table-default-cell-text": "--text-secondary",
3916
3916
  "--table-default-cell-font-family": "--font-sans",
3917
3917
  "--table-default-cell-font-size": "--font-size-md",
@@ -4033,5 +4033,5 @@
4033
4033
  }
4034
4034
  }
4035
4035
  },
4036
- "componentSchemaVersion": 26
4036
+ "componentSchemaVersion": 27
4037
4037
  }