@starci/grammar 0.12.14 → 0.14.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 (214) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +34 -31
  3. package/dist/.build-stamp.json +4 -4
  4. package/dist/common/components-forms.css +28 -44
  5. package/dist/common/components-list-panel.css +8 -0
  6. package/dist/common/components-navigation.css +163 -6
  7. package/dist/common/components-overlays.css +41 -2
  8. package/dist/common/conformance.d.ts +3 -8
  9. package/dist/common/conformance.d.ts.map +1 -1
  10. package/dist/common/conformance.js +4 -9
  11. package/dist/common/conformance.js.map +1 -1
  12. package/dist/common/registry.d.ts +24 -21
  13. package/dist/common/registry.d.ts.map +1 -1
  14. package/dist/common/registry.js +4 -4
  15. package/dist/common/registry.js.map +1 -1
  16. package/dist/common/renderers-forms.d.ts +11 -11
  17. package/dist/common/renderers-navigation.d.ts +5 -5
  18. package/dist/common/renderers-navigation.d.ts.map +1 -1
  19. package/dist/common/renderers-navigation.js +1 -1
  20. package/dist/common/renderers-navigation.js.map +1 -1
  21. package/dist/common/renderers-overlays.d.ts +2 -2
  22. package/dist/common/renderers-overlays.d.ts.map +1 -1
  23. package/dist/common/renderers-overlays.js.map +1 -1
  24. package/dist/common/renderers.d.ts +4 -1
  25. package/dist/common/renderers.d.ts.map +1 -1
  26. package/dist/common/renderers.js +3 -0
  27. package/dist/common/renderers.js.map +1 -1
  28. package/dist/common/{rule-catalog.generated.d.ts → rule-catalog.d.ts} +1 -1
  29. package/dist/common/rule-catalog.d.ts.map +1 -0
  30. package/dist/common/{rule-catalog.generated.js → rule-catalog.js} +2 -4
  31. package/dist/common/rule-catalog.js.map +1 -0
  32. package/dist/common/styles.css +236 -48
  33. package/dist/core/branch/ComboBox/index.d.ts +5 -5
  34. package/dist/core/branch/ComboBox/index.d.ts.map +1 -1
  35. package/dist/core/branch/ComboBox/index.js +9 -2
  36. package/dist/core/branch/ComboBox/index.js.map +1 -1
  37. package/dist/core/branch/DataTable/index.d.ts +20 -3
  38. package/dist/core/branch/DataTable/index.d.ts.map +1 -1
  39. package/dist/core/branch/DataTable/index.js +45 -8
  40. package/dist/core/branch/DataTable/index.js.map +1 -1
  41. package/dist/core/branch/FlowGraph/index.d.ts +26 -7
  42. package/dist/core/branch/FlowGraph/index.d.ts.map +1 -1
  43. package/dist/core/branch/FlowGraph/index.js +132 -30
  44. package/dist/core/branch/FlowGraph/index.js.map +1 -1
  45. package/dist/core/branch/FlowGraph/layout.d.ts +34 -1
  46. package/dist/core/branch/FlowGraph/layout.d.ts.map +1 -1
  47. package/dist/core/branch/FlowGraph/layout.js +82 -0
  48. package/dist/core/branch/FlowGraph/layout.js.map +1 -1
  49. package/dist/core/branch/FlowGraph/types.d.ts +13 -1
  50. package/dist/core/branch/FlowGraph/types.d.ts.map +1 -1
  51. package/dist/core/branch/ListBox/index.d.ts +4 -2
  52. package/dist/core/branch/ListBox/index.d.ts.map +1 -1
  53. package/dist/core/branch/ListBox/index.js +2 -2
  54. package/dist/core/branch/ListBox/index.js.map +1 -1
  55. package/dist/core/branch/MarkdownArticle/index.d.ts +7 -1
  56. package/dist/core/branch/MarkdownArticle/index.d.ts.map +1 -1
  57. package/dist/core/branch/MarkdownArticle/index.js +5 -2
  58. package/dist/core/branch/MarkdownArticle/index.js.map +1 -1
  59. package/dist/core/branch/Select/index.d.ts +6 -8
  60. package/dist/core/branch/Select/index.d.ts.map +1 -1
  61. package/dist/core/branch/Select/index.js +9 -5
  62. package/dist/core/branch/Select/index.js.map +1 -1
  63. package/dist/core/branch/SurfaceCard/index.d.ts +9 -0
  64. package/dist/core/branch/SurfaceCard/index.d.ts.map +1 -1
  65. package/dist/core/branch/SurfaceCard/index.js +3 -2
  66. package/dist/core/branch/SurfaceCard/index.js.map +1 -1
  67. package/dist/core/branch/Tabs/index.d.ts.map +1 -1
  68. package/dist/core/branch/Tabs/index.js +17 -0
  69. package/dist/core/branch/Tabs/index.js.map +1 -1
  70. package/dist/core/branch/Tooltip/index.d.ts +9 -1
  71. package/dist/core/branch/Tooltip/index.d.ts.map +1 -1
  72. package/dist/core/branch/Tooltip/index.js +2 -2
  73. package/dist/core/branch/Tooltip/index.js.map +1 -1
  74. package/dist/core/buttonIntent.d.ts.map +1 -1
  75. package/dist/core/buttonIntent.js +1 -2
  76. package/dist/core/buttonIntent.js.map +1 -1
  77. package/dist/core/collectionItem.d.ts +3 -3
  78. package/dist/core/collectionItem.d.ts.map +1 -1
  79. package/dist/core/collectionItem.js +1 -2
  80. package/dist/core/collectionItem.js.map +1 -1
  81. package/dist/core/composite/Alert/index.d.ts +5 -0
  82. package/dist/core/composite/Alert/index.d.ts.map +1 -1
  83. package/dist/core/composite/Alert/index.js +7 -2
  84. package/dist/core/composite/Alert/index.js.map +1 -1
  85. package/dist/core/composite/CheckboxGroup/index.d.ts +6 -5
  86. package/dist/core/composite/CheckboxGroup/index.d.ts.map +1 -1
  87. package/dist/core/composite/CheckboxGroup/index.js +10 -3
  88. package/dist/core/composite/CheckboxGroup/index.js.map +1 -1
  89. package/dist/core/composite/DescriptionList/index.d.ts +3 -2
  90. package/dist/core/composite/DescriptionList/index.d.ts.map +1 -1
  91. package/dist/core/composite/DescriptionList/index.js +5 -4
  92. package/dist/core/composite/DescriptionList/index.js.map +1 -1
  93. package/dist/core/composite/RadioGroup/index.d.ts +5 -3
  94. package/dist/core/composite/RadioGroup/index.d.ts.map +1 -1
  95. package/dist/core/composite/RadioGroup/index.js +9 -2
  96. package/dist/core/composite/RadioGroup/index.js.map +1 -1
  97. package/dist/core/composite/SegmentedControl/index.d.ts +6 -6
  98. package/dist/core/composite/SegmentedControl/index.d.ts.map +1 -1
  99. package/dist/core/composite/SegmentedControl/index.js +9 -5
  100. package/dist/core/composite/SegmentedControl/index.js.map +1 -1
  101. package/dist/core/composite/TagGroup/index.d.ts +27 -4
  102. package/dist/core/composite/TagGroup/index.d.ts.map +1 -1
  103. package/dist/core/composite/TagGroup/index.js +132 -2
  104. package/dist/core/composite/TagGroup/index.js.map +1 -1
  105. package/dist/core/composite/Timeline/index.d.ts +5 -0
  106. package/dist/core/composite/Timeline/index.d.ts.map +1 -1
  107. package/dist/core/composite/Timeline/index.js +1 -1
  108. package/dist/core/composite/Timeline/index.js.map +1 -1
  109. package/dist/core/composition/Sidebar/index.d.ts +4 -0
  110. package/dist/core/composition/Sidebar/index.d.ts.map +1 -1
  111. package/dist/core/composition/Sidebar/index.js +4 -2
  112. package/dist/core/composition/Sidebar/index.js.map +1 -1
  113. package/dist/core/dna.d.ts +1 -1
  114. package/dist/core/dna.js +1 -1
  115. package/dist/core/dna.js.map +1 -1
  116. package/dist/core/index.d.ts +1 -1
  117. package/dist/core/index.d.ts.map +1 -1
  118. package/dist/core/primitive/Avatar/index.d.ts +1 -1
  119. package/dist/core/primitive/Badge/index.d.ts +8 -5
  120. package/dist/core/primitive/Badge/index.d.ts.map +1 -1
  121. package/dist/core/primitive/Badge/index.js +14 -5
  122. package/dist/core/primitive/Badge/index.js.map +1 -1
  123. package/dist/core/primitive/Button/index.d.ts +10 -2
  124. package/dist/core/primitive/Button/index.d.ts.map +1 -1
  125. package/dist/core/primitive/Button/index.js +15 -4
  126. package/dist/core/primitive/Button/index.js.map +1 -1
  127. package/dist/core/primitive/Checkbox/index.d.ts +5 -3
  128. package/dist/core/primitive/Checkbox/index.d.ts.map +1 -1
  129. package/dist/core/primitive/Checkbox/index.js +9 -2
  130. package/dist/core/primitive/Checkbox/index.js.map +1 -1
  131. package/dist/core/primitive/Code/index.d.ts +10 -0
  132. package/dist/core/primitive/Code/index.d.ts.map +1 -0
  133. package/dist/core/primitive/Code/index.js +5 -0
  134. package/dist/core/primitive/Code/index.js.map +1 -0
  135. package/dist/core/primitive/Field/index.d.ts +12 -25
  136. package/dist/core/primitive/Field/index.d.ts.map +1 -1
  137. package/dist/core/primitive/Field/index.js +13 -14
  138. package/dist/core/primitive/Field/index.js.map +1 -1
  139. package/dist/core/primitive/GrammarRoot/index.d.ts +8 -1
  140. package/dist/core/primitive/GrammarRoot/index.d.ts.map +1 -1
  141. package/dist/core/primitive/GrammarRoot/index.js +5 -1
  142. package/dist/core/primitive/GrammarRoot/index.js.map +1 -1
  143. package/dist/core/primitive/Icon/index.d.ts.map +1 -1
  144. package/dist/core/primitive/Icon/index.js +3 -2
  145. package/dist/core/primitive/Icon/index.js.map +1 -1
  146. package/dist/core/primitive/Input/index.d.ts +9 -4
  147. package/dist/core/primitive/Input/index.d.ts.map +1 -1
  148. package/dist/core/primitive/Input/index.js +13 -8
  149. package/dist/core/primitive/Input/index.js.map +1 -1
  150. package/dist/core/primitive/Meter/index.d.ts +4 -1
  151. package/dist/core/primitive/Meter/index.d.ts.map +1 -1
  152. package/dist/core/primitive/Meter/index.js +3 -3
  153. package/dist/core/primitive/Meter/index.js.map +1 -1
  154. package/dist/core/primitive/NumberField/index.d.ts +6 -5
  155. package/dist/core/primitive/NumberField/index.d.ts.map +1 -1
  156. package/dist/core/primitive/NumberField/index.js +10 -3
  157. package/dist/core/primitive/NumberField/index.js.map +1 -1
  158. package/dist/core/primitive/PinnedActionBar/index.d.ts +25 -6
  159. package/dist/core/primitive/PinnedActionBar/index.d.ts.map +1 -1
  160. package/dist/core/primitive/PinnedActionBar/index.js +9 -4
  161. package/dist/core/primitive/PinnedActionBar/index.js.map +1 -1
  162. package/dist/core/primitive/SearchField/index.d.ts +6 -5
  163. package/dist/core/primitive/SearchField/index.d.ts.map +1 -1
  164. package/dist/core/primitive/SearchField/index.js +10 -3
  165. package/dist/core/primitive/SearchField/index.js.map +1 -1
  166. package/dist/core/primitive/SectionHeader/index.d.ts +3 -3
  167. package/dist/core/primitive/SectionHeader/index.js +2 -2
  168. package/dist/core/primitive/SectionHeader/index.js.map +1 -1
  169. package/dist/core/primitive/Skeleton/index.d.ts +11 -0
  170. package/dist/core/primitive/Skeleton/index.d.ts.map +1 -1
  171. package/dist/core/primitive/Skeleton/index.js +12 -1
  172. package/dist/core/primitive/Skeleton/index.js.map +1 -1
  173. package/dist/core/primitive/StatusDot/index.d.ts +61 -0
  174. package/dist/core/primitive/StatusDot/index.d.ts.map +1 -0
  175. package/dist/core/primitive/StatusDot/index.js +55 -0
  176. package/dist/core/primitive/StatusDot/index.js.map +1 -0
  177. package/dist/core/primitive/Switch/index.d.ts +4 -3
  178. package/dist/core/primitive/Switch/index.d.ts.map +1 -1
  179. package/dist/core/primitive/Switch/index.js +5 -2
  180. package/dist/core/primitive/Switch/index.js.map +1 -1
  181. package/dist/core/primitive/Terminal/index.d.ts +20 -0
  182. package/dist/core/primitive/Terminal/index.d.ts.map +1 -0
  183. package/dist/core/primitive/Terminal/index.js +29 -0
  184. package/dist/core/primitive/Terminal/index.js.map +1 -0
  185. package/dist/core/primitive/Text/index.d.ts +5 -1
  186. package/dist/core/primitive/Text/index.d.ts.map +1 -1
  187. package/dist/core/primitive/Text/index.js +12 -7
  188. package/dist/core/primitive/Text/index.js.map +1 -1
  189. package/dist/core/primitive/TextAction/index.d.ts.map +1 -1
  190. package/dist/core/primitive/TextAction/index.js +9 -4
  191. package/dist/core/primitive/TextAction/index.js.map +1 -1
  192. package/dist/core/primitive/Textarea/index.d.ts +8 -4
  193. package/dist/core/primitive/Textarea/index.d.ts.map +1 -1
  194. package/dist/core/primitive/Textarea/index.js +9 -5
  195. package/dist/core/primitive/Textarea/index.js.map +1 -1
  196. package/dist/core/styles.css +18 -6
  197. package/dist/offset-pop/components-forms.css +0 -17
  198. package/dist/offset-pop/components-navigation.css +30 -4
  199. package/dist/offset-pop/components-overlays.css +5 -0
  200. package/dist/offset-pop/index.d.ts +1 -1
  201. package/dist/offset-pop/index.d.ts.map +1 -1
  202. package/dist/offset-pop/styles.css +44 -0
  203. package/package.json +7 -12
  204. package/dist/common/rule-catalog.generated.d.ts.map +0 -1
  205. package/dist/common/rule-catalog.generated.js.map +0 -1
  206. package/dist/heritage/dna.d.ts +0 -63
  207. package/dist/heritage/dna.d.ts.map +0 -1
  208. package/dist/heritage/dna.js +0 -37
  209. package/dist/heritage/dna.js.map +0 -1
  210. package/dist/heritage/index.d.ts +0 -27
  211. package/dist/heritage/index.d.ts.map +0 -1
  212. package/dist/heritage/index.js +0 -46
  213. package/dist/heritage/index.js.map +0 -1
  214. package/dist/heritage/styles.css +0 -187
package/LICENSE CHANGED
@@ -1,21 +1,21 @@
1
- MIT License
2
-
3
- Copyright (c) 2026 StarCi
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
1
+ MIT License
2
+
3
+ Copyright (c) 2026 StarCi
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  `@starci/grammar` splits a UI kit into one shared contract and several visual families. The
4
4
  contract has no business logic. Common owns the typed props, semantics, renderer anatomy, universal
5
- states and scales, and the family registry. Core, Heritage and Offset Pop are sibling families.
5
+ states and scales, and the family registry. Core and Offset Pop are sibling families.
6
6
  Each one implements the same contract with its own scoped visual DNA. The package contains no
7
7
  routes, domain entities, product decisions or feature-named shells.
8
8
 
@@ -13,8 +13,6 @@ only: props, DOM, ARIA and keyboard behaviour stay the same.
13
13
 
14
14
  ```sh
15
15
  npm install @starci/grammar @heroui/react react react-dom
16
- # while developing inside this repository:
17
- npm install ./.claude/packages/grammar
18
16
  ```
19
17
 
20
18
  The package needs Node.js 22 or newer. Its peer dependencies are React 18+, `@xyflow/react` 12 (for `FlowGraph`) and `@heroui/react`
@@ -25,14 +23,10 @@ exactly one Grammar family stylesheet. Each family sheet already imports Common
25
23
  | --- | --- | --- |
26
24
  | `@starci/grammar/common` | `GrammarRoot` (neutral) | `@starci/grammar/common/styles.css` (`common.css`) |
27
25
  | `@starci/grammar/core` | `CoreGrammarRoot` | `@starci/grammar/core/styles.css` (`core.css`) |
28
- | `@starci/grammar/heritage` | `HeritageGrammarRoot` | `@starci/grammar/heritage/styles.css` (`heritage.css`) |
29
26
  | `@starci/grammar/offset-pop` | `OffsetPopGrammarRoot` | `@starci/grammar/offset-pop/styles.css` (`offset-pop.css`) |
30
27
 
31
28
  The Core and Offset Pop entry points re-export all of Common, so an app imports its components and
32
- its root from one place. Heritage does not re-export Common. It exports `heritageGrammar`,
33
- `HeritageGrammarRoot`, its `Brand` extension (`HeritageBrand`), the `HeritageButton` and
34
- `HeritageHeading` aliases and its conformance, so import the other renderers from
35
- `@starci/grammar/common`.
29
+ its root from one place.
36
30
 
37
31
  ## Usage
38
32
 
@@ -57,7 +51,7 @@ the complete placement mapping in light and dark, including destination, pending
57
51
 
58
52
  ### App shell
59
53
 
60
- The [app-shell canon](../../knowledge/ui/composition/app-shell.yaml) owns `FE-APP-SHELL-1` through `FE-APP-SHELL-7`.
54
+ The [app-shell canon](../../contexts/ui/composition.md) owns `FE-APP-SHELL-1` through `FE-APP-SHELL-7`.
61
55
  The `Compositions/App shell` stories show the fixed shell, grouped sidebar, icon rail and mobile drawer.
62
56
 
63
57
  - `size="control"` is available on `Button`, `IconButton`, `TextAction`, `PlainPressable`, `IdentityLockup`, `Input` and `SearchField`, alongside `Switch`, `Avatar` and `AvatarButton`.
@@ -127,23 +121,6 @@ export const ProductSurface = () => (
127
121
  Core's DNA declares a shadow without a border at top surface depth, and a border without a shadow
128
122
  at nested depth.
129
123
 
130
- ### Heritage
131
-
132
- ```tsx
133
- import { TopBar } from "@starci/grammar/common"
134
- import { HeritageBrand, HeritageGrammarRoot } from "@starci/grammar/heritage"
135
- import "@starci/grammar/heritage/styles.css"
136
-
137
- export const Masthead = () => (
138
- <HeritageGrammarRoot>
139
- <TopBar brand={<a href="/"><HeritageBrand name="…" descriptor="…" logo={{ src: "/logo.svg", width: 40, height: 40 }} /></a>} />
140
- </HeritageGrammarRoot>
141
- )
142
- ```
143
-
144
- Heritage replaces only the root. It adds one extension, `Brand`, which is non-interactive brand
145
- content for a destination the consumer owns.
146
-
147
124
  ### Offset Pop
148
125
 
149
126
  ```tsx
@@ -167,11 +144,37 @@ offset shadow and a pink decision accent. Text that uses the accent is drawn in
167
144
  `OFFSET_POP_TOKEN_DEFAULTS`, `OFFSET_POP_DARK_TOKEN_DEFAULTS`, `OFFSET_POP_BAND_TOKEN_NAMES`,
168
145
  `offsetPopGrammar` and `offsetPopRuleConformance`.
169
146
 
147
+ ## Client-side navigation
148
+
149
+ Every Grammar root (`GrammarRoot`, `CoreGrammarRoot`, `OffsetPopGrammarRoot`) accepts `navigate(path)` and `useHref(href)` and
150
+ mounts react-aria's `RouterProvider`. Pass your router's functions once at the root and every Grammar `Link`, `Button href`,
151
+ `Breadcrumbs` item, `ListBox`/`Menu` item `href` and `FlowGraph` node `href` navigates inside the app without a reload.
152
+
153
+ ```tsx
154
+ import { useHref, useNavigate } from "react-router-dom"
155
+ import { CoreGrammarRoot } from "@starci/grammar/core"
156
+
157
+ export const Root = ({ children }: { children: React.ReactNode }) => {
158
+ const navigate = useNavigate()
159
+ return <CoreGrammarRoot navigate={(path) => navigate(path)} useHref={useHref}>{children}</CoreGrammarRoot>
160
+ }
161
+ ```
162
+
163
+ Mount the root inside the router. Without `navigate` the links behave as plain anchors.
164
+
165
+ ### FlowGraph layouts
166
+
167
+ `FlowGraph` places nodes with `layout`: `radial` (the default), `layered` (a DAG in layers) or `manual` (each node's `position`).
168
+ `direction="TB" | "LR"` puts the target handle on the entering side and the source handle on the leaving side so edges run along the
169
+ layout, and selects `layered` when no layout is given. `headers={[{ label, at }]}` draws a column header above each layer for `LR` and
170
+ a lane header beside it for `TB` (`at` is the layer index in `layered`, graph units in `manual`). `height` takes `sm | md | lg`, a number
171
+ (pixels) or any CSS length. A node with `href` is a link through the root's router and one keyboard stop.
172
+
170
173
  ## Family scope and the overlay portal rule
171
174
 
172
175
  A family paints only inside its root. Each family stylesheet is scoped to
173
176
  `.grammar-common-root[data-grammar-family="<id>"]` inside its own cascade layer:
174
- `starci-grammar-common` comes first, then `-core`, `-heritage` and `-offset-pop`. No family sheet
177
+ `starci-grammar-common` comes first, then `-core` and `-offset-pop`. No family sheet
175
178
  imports a sibling family's sheet. So two families can share a page, each inside its own root, and
176
179
  nested roots resolve to the innermost one.
177
180
 
@@ -246,7 +249,7 @@ className and no size. An item takes `id`, `label`, `description`, `descriptionL
246
249
  `size-4`, which is example content and not a component default, so 20px is StarCi's icon rule and not a vendor override.
247
250
  - `mark`: a decorative node instead of `iconSource`, e.g. an identity mark (`<Avatar size="sm" shape="rounded" isDecorative />`). The public type rejects both props together.
248
251
  - `tone: "danger"`: HeroUI's `danger` variant, which colours the label and the glyph. Sign out, delete, remove, leave workspace
249
- and revoke are destructive; the lint rule `starci-grammar/destructive-menu-entry-needs-danger-tone` of `@starci/eslint-grammar-starci` refuses such an entry without it.
252
+ and revoke are destructive; the lint rule `starci-grammar/destructive-menu-entry-needs-danger-tone` of `@starci/eslint-grammar-core` refuses such an entry without it.
250
253
 
251
254
  A section takes `label` and optionally `selection` (`mode: "single" | "multiple"`, `selectedIds`, `onChange`). Between two
252
255
  adjacent entries, when either is a section, Grammar always inserts HeroUI's own `Separator`: the app cannot forget one. The selection
@@ -267,7 +270,7 @@ an item with a submenu).
267
270
  ```
268
271
 
269
272
  **HeroUI parity.** Grammar is a wrapper of HeroUI, so a wrapper keeps HeroUI's anatomy and defaults. The vendor reference
270
- (`knowledge/vendor/heroui/<component>.yaml` in the StarCi runtime) is measured from the installed `@heroui/react` and
273
+ (`contexts/vendor/` of the StarCi harness) is measured from the installed `@heroui/react` and
271
274
  `@heroui/styles`; the gate `GRAMMAR_VENDOR_PARITY` fails a wrapper that renders a part HeroUI does not have, omits one it
272
275
  requires, restyles what the vendor decides, or replaces a vendor default, unless an owner-pinned override declares it. `ListBox`
273
276
  and `Select` are HeroUI's own list-box items (a `Label`, a `Description`, the item indicator at the end of the row).
@@ -430,8 +433,8 @@ person uses Avatar. The default `layout="row"` keeps the overlapping member row
430
433
 
431
434
  ## Theming
432
435
 
433
- - **Tokens.** Each family writes its DNA as CSS custom properties on its root: `--starci-core-*`,
434
- `--heritage-*` or `--offset-pop-*`. It feeds those into the Common semantic variables that
436
+ - **Tokens.** Each family writes its DNA as CSS custom properties on its root: `--starci-core-*`
437
+ or `--offset-pop-*`. It feeds those into the Common semantic variables that
435
438
  renderers read: `--accent`, `--accent-foreground`, `--focus`, `--background`, `--surface`,
436
439
  `--surface-secondary`, `--foreground`, `--muted`, `--border`, `--separator`, the status
437
440
  colours and their foregrounds, radii and shadow. Core also publishes a tertiary face under its own
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "schema": "starci/grammar-build-stamp@1",
3
3
  "package": "@starci/grammar",
4
- "version": "0.12.14",
4
+ "version": "0.14.0",
5
5
  "algorithm": "starci/grammar-source-digest@1",
6
- "sourceDigest": "sha256:d0f747302686e5391934ba97b7fb91f582e15260a9b8e0eb4a25f365163bfdde",
7
- "distDigest": "sha256:291edd4443f66c843492626af0a8d95c55549f58db0520c2d4d16273fb0b85d6",
8
- "builtAt": "2026-10-26T21:41:52.449Z"
6
+ "sourceDigest": "sha256:abe48ef178e3f654d64eda3138e1bbc98b0d48d7f8fd27c09432d9966a6a99ec",
7
+ "distDigest": "sha256:d0879914392be475eacac5872c11615e3528982f7bae31376fc96801d23c97c9",
8
+ "builtAt": "2026-10-27T19:22:38.977Z"
9
9
  }
@@ -6,7 +6,7 @@
6
6
  *
7
7
  * Only structure, rhythm and the touch floor live here. Paint resolves through the semantic
8
8
  * variables every family binds (`--field-*`, `--accent`, `--danger`, `--muted`, `--focus`,
9
- * `--border`), so Core, Heritage and Offset Pop each get their own look without a Common colour.
9
+ * `--border`), so Core and Offset Pop each get their own look without a Common colour.
10
10
  * Spacing uses the Common 4px scale (0.25 / 0.5 / 0.75 / 1 / 1.5rem).
11
11
  *
12
12
  * `!important` appears only on the few declarations the vendor's field roots also set (display and
@@ -124,58 +124,42 @@
124
124
  }
125
125
 
126
126
  /*
127
- * Loading field (knowledge STATE-8, Select and Textarea `isSkeleton`): the label stays real, the control is one
128
- * Skeleton block of the loaded control's height (the select trigger 2.5rem, with the touch floor below; the textarea
129
- * `rows` at 1.25rem a line plus its padding, never under the 5rem field minimum). Not focusable: no control exists.
127
+ * Loading field (knowledge STATE-8) with skeleton parity: the control renders its ready anatomy inside
128
+ * `.starci-core-field-skeleton-scope` (layout-neutral, inert), so label, description and control box keep the ready
129
+ * geometry by construction. Every control box in the scope is painted as a Skeleton (the vendor skeleton tint and
130
+ * shimmer); what it holds - a value, a placeholder, an indicator, a thumb - is not drawn. Its border stays, so a
131
+ * family's outline keeps the field box.
130
132
  */
131
- /* The loading root (Field, Select, NumberField, SearchField, SegmentedControl, CheckboxGroup, Switch, Textarea): inert geometry that can shrink inside its row or column. */
132
- .starci-core-field-skeleton {
133
- min-width: 0;
134
- pointer-events: none;
135
- user-select: none;
136
- }
137
-
138
- .starci-core-field-skeleton-control { display: block; min-width: 0; }
133
+ .starci-core-field-skeleton-scope { display: contents; }
139
134
 
140
- .starci-core-field-skeleton-control > .starci-core-skeleton {
141
- block-size: 100%;
142
- aspect-ratio: auto;
135
+ .starci-core-field-skeleton-scope :is([data-grammar-field-control="true"]:not(.starci-core-choice-group-options), .starci-core-input-field, [data-grammar-choice-control], [data-grammar-tag-id]) {
136
+ position: relative;
137
+ overflow: hidden;
138
+ background: color-mix(in oklab, var(--surface-tertiary, #e4e4e7) 70%, transparent) !important;
139
+ color: transparent !important;
140
+ user-select: none;
143
141
  }
144
142
 
145
- .starci-core-field-skeleton-control:is([data-grammar-field-skeleton="input"]) { block-size: 2.5rem; }
146
-
147
- /* The select trigger is HeroUI's own (min-h-9). */
148
- .starci-core-field-skeleton-control[data-grammar-field-skeleton="select"] { block-size: var(--starci-core-control-min-size, 2.5rem); }
149
-
150
- /* Joined segments: one block, a segment-width (5rem) per option, never wider than the field; `fill` takes the whole row. */
151
- .starci-core-field-skeleton-control[data-grammar-field-skeleton="segmented"] {
152
- block-size: 2.5rem;
153
- inline-size: min(100%, calc(var(--starci-core-skeleton-segments, 2) * 5rem));
143
+ /* A control box whose rounded part is inside it (a ComboBox or SegmentedControl group, a Field control slot) takes the field radius. */
144
+ .starci-core-field-skeleton-scope [data-grammar-field-control="true"]:is(.starci-core-combo-box-group, .starci-core-segmented-control-group, .starci-core-field-control) {
145
+ border-radius: var(--field-radius, calc(var(--radius, 0.5rem) * 1.5));
154
146
  }
155
147
 
156
- .starci-core-field-skeleton-control[data-grammar-field-skeleton="segmented"][data-width="fill"] { inline-size: 100%; }
157
- .starci-core-field-skeleton-control[data-grammar-field-skeleton="segmented"][data-grammar-segmented-size="control"] { block-size: var(--starci-core-control-min-size, 2.75rem); }
158
-
159
- /* A checkbox box and a switch track beside their real labels. */
160
- .starci-core-field-skeleton-control[data-grammar-field-skeleton="choice"] { flex: none; block-size: 1rem; inline-size: 1rem; }
161
- .starci-core-field-skeleton-control[data-grammar-field-skeleton="switch"] { flex: none; block-size: 1.25rem; inline-size: 2rem; }
162
- .starci-core-field-skeleton-control[data-grammar-field-skeleton="switch"][data-grammar-switch-size="lg"] { block-size: 1.5rem; inline-size: 3rem; }
163
- .starci-core-field-skeleton-control[data-grammar-field-skeleton="switch"][data-grammar-switch-size="control"] { block-size: var(--starci-core-control-min-size, 2.5rem); inline-size: 4.5rem; }
148
+ .starci-core-field-skeleton-scope :is([data-grammar-field-control="true"]:not(.starci-core-choice-group-options), [data-grammar-choice-control], [data-grammar-tag-id]) > * { visibility: hidden; }
149
+ .starci-core-field-skeleton-scope :is(input, textarea)::placeholder { color: transparent; }
164
150
 
165
- .starci-core-field-skeleton-choice-label {
166
- color: var(--foreground, CanvasText);
167
- font-size: 0.875rem;
168
- line-height: 1.25;
169
- }
170
-
171
- .starci-core-field-skeleton-control[data-grammar-field-skeleton="textarea"] {
172
- block-size: max(5rem, calc(var(--starci-core-skeleton-rows, 4) * 1.25rem + 1rem));
151
+ .starci-core-field-skeleton-scope :is([data-grammar-field-control="true"]:not(.starci-core-choice-group-options, textarea), [data-grammar-choice-control], [data-grammar-tag-id])::after {
152
+ content: "";
153
+ position: absolute;
154
+ inset: 0;
155
+ visibility: visible;
156
+ background-image: linear-gradient(to right, transparent, var(--surface-tertiary, #e4e4e7), transparent);
157
+ translate: -100%;
158
+ animation: skeleton 2s linear infinite;
173
159
  }
174
160
 
175
- @media (pointer: coarse), (max-width: 40rem) {
176
- .starci-core-field-skeleton-control:is([data-grammar-field-skeleton="select"], [data-grammar-field-skeleton="input"], [data-grammar-field-skeleton="segmented"]) {
177
- min-block-size: var(--starci-core-control-min-size, 2.5rem);
178
- }
161
+ @media (prefers-reduced-motion: reduce) {
162
+ .starci-core-field-skeleton-scope :is([data-grammar-field-control="true"], [data-grammar-choice-control])::after { animation: none; content: none; }
179
163
  }
180
164
 
181
165
  .starci-core-combo-box-group,
@@ -21,6 +21,14 @@
21
21
  line-clamp: 2;
22
22
  }
23
23
 
24
+ .grammar-common-root [data-slot="description"][data-grammar-description-lines="wrap"] {
25
+ display: block;
26
+ -webkit-line-clamp: none;
27
+ line-clamp: none;
28
+ overflow: visible;
29
+ overflow-wrap: anywhere;
30
+ }
31
+
24
32
  .grammar-common-root [data-slot="description"][data-grammar-description-lines="3"] {
25
33
  -webkit-line-clamp: 3;
26
34
  line-clamp: 3;
@@ -112,7 +112,7 @@
112
112
  /* A crumb whose label is not known yet (knowledge STATE-8): a short Skeleton text line of the label's size. */
113
113
  .starci-core-breadcrumb-skeleton { display: inline-block; inline-size: 6rem; }
114
114
  .starci-core-breadcrumb-skeleton > .starci-core-skeleton-group { gap: 0; }
115
- .starci-core-breadcrumb-skeleton .starci-core-skeleton { block-size: 0.875rem; }
115
+ .starci-core-breadcrumb-skeleton .starci-core-skeleton { --starci-core-skeleton-line: 0.875rem; }
116
116
 
117
117
  @media (max-width: 30rem) {
118
118
  .starci-core-breadcrumb-label {
@@ -269,7 +269,7 @@
269
269
  .starci-core-step-skeleton-marker { border: 0; background: transparent; }
270
270
  .starci-core-step-skeleton-label { display: block; inline-size: 5rem; }
271
271
  .starci-core-step-skeleton-label > .starci-core-skeleton-group { gap: 0; }
272
- .starci-core-step-skeleton-label .starci-core-skeleton { block-size: 0.875rem; }
272
+ .starci-core-step-skeleton-label .starci-core-skeleton { --starci-core-skeleton-line: 0.875rem; }
273
273
 
274
274
  .starci-core-step-copy {
275
275
  display: flex;
@@ -779,6 +779,26 @@
779
779
  white-space: nowrap;
780
780
  }
781
781
 
782
+ /* The reorder layer's event wrapper takes no box: the TagGroup lays out as if it were not there. */
783
+ .starci-core-tag-group-reorder {
784
+ display: contents;
785
+ }
786
+
787
+ /* Reorderable tags: the grab cursor, the lifted tag and the tag a drop would replace. */
788
+ .starci-core-tag-group[data-grammar-tag-reorderable="true"] .starci-core-tag[draggable="true"] {
789
+ cursor: grab;
790
+ }
791
+
792
+ .starci-core-tag-group[data-grammar-tag-reorderable="true"] .starci-core-tag[data-grammar-tag-dragging="true"] {
793
+ opacity: 0.5;
794
+ cursor: grabbing;
795
+ }
796
+
797
+ .starci-core-tag-group[data-grammar-tag-reorderable="true"] .starci-core-tag[data-grammar-tag-drop-target="true"] {
798
+ outline: 2px solid var(--accent, Highlight);
799
+ outline-offset: 2px;
800
+ }
801
+
782
802
  /*
783
803
  * Selectable chips (0.9.6, selectionMode single or multiple): a group of choices carries a 1px inset ring,
784
804
  * --border when unselected and --accent when selected. The chip face, hover and selected paint are HeroUI's own.
@@ -850,6 +870,15 @@
850
870
  width: max-content;
851
871
  }
852
872
 
873
+ /*
874
+ * A table that hosts editors, or has folded its low columns for a narrow container, fits the container instead:
875
+ * at max-content an editor's TagGroup never wraps its chips and the folded text never wraps, so the table ran
876
+ * past its container. It still scrolls sideways once its columns cannot shrink further.
877
+ */
878
+ .starci-core-data-table:is([data-grammar-table-editors="true"], [data-grammar-table-narrow="true"]) .starci-core-data-table-content {
879
+ width: 100%;
880
+ }
881
+
853
882
  .starci-core-data-table[data-grammar-table-sticky="true"] .starci-core-data-table-header {
854
883
  position: sticky;
855
884
  z-index: 1;
@@ -892,6 +921,65 @@
892
921
  box-sizing: border-box;
893
922
  }
894
923
 
924
+ /* A whole-row link target reads as one pressable row. */
925
+ .starci-core-data-table-row[data-grammar-table-row-link="true"] {
926
+ cursor: pointer;
927
+ }
928
+
929
+ /* An editor cell hosts a field; the field keeps its own control height and fills the column, never wider. */
930
+ .starci-core-data-table-editor {
931
+ display: flex;
932
+ min-width: 0;
933
+ max-inline-size: 100%;
934
+ align-items: center;
935
+ white-space: normal;
936
+ }
937
+
938
+ .starci-core-data-table-editor > * {
939
+ flex: 1 1 auto;
940
+ min-width: 0;
941
+ }
942
+
943
+ /*
944
+ * A text input's intrinsic width is its `size` (20 characters, about 11rem), which an auto-layout table takes as the
945
+ * column's minimum: a NumberField editor alone pushed the table past its container, even in narrow mode. In an editor
946
+ * cell the input asks for no width of its own and fills its track through its minimum (a percentage minimum does not
947
+ * count toward the column's intrinsic width), so the column can shrink to the room the table has and the input still
948
+ * fills what it gets, never less than three characters beside its own inline padding.
949
+ */
950
+ .starci-core-data-table-editor input {
951
+ inline-size: 0;
952
+ min-inline-size: max(calc(3ch + 1.5rem), 100%);
953
+ }
954
+
955
+ /* Narrow mode: low-priority columns stack under the first column's content as secondary text. */
956
+ .starci-core-data-table-secondary {
957
+ display: flex;
958
+ flex-direction: column;
959
+ gap: 0.125rem;
960
+ margin-block-start: 0.25rem;
961
+ color: var(--muted, GrayText);
962
+ font-size: 0.875em;
963
+ white-space: normal;
964
+ }
965
+
966
+ /* A long value (an email, an id) may break anywhere, so the folded text never sets the first column's minimum. */
967
+ .starci-core-data-table-secondary-item {
968
+ display: flex;
969
+ min-width: 0;
970
+ flex-wrap: wrap;
971
+ gap: 0.25rem;
972
+ overflow-wrap: anywhere;
973
+ }
974
+
975
+ .starci-core-data-table-secondary-value {
976
+ min-width: 0;
977
+ }
978
+
979
+ .starci-core-data-table-secondary-label::after {
980
+ content: ":";
981
+ }
982
+
895
983
  .starci-core-drag-item {
896
984
  display: flex;
897
985
  min-width: 0;
@@ -1021,6 +1109,9 @@
1021
1109
  line-height: 1.25rem;
1022
1110
  }
1023
1111
 
1112
+ /* A loading stat keeps its unit's place, unseen (skeleton parity). */
1113
+ .starci-core-description-list[data-loading="true"] .starci-core-description-unit { visibility: hidden; }
1114
+
1024
1115
  .starci-core-description-meta {
1025
1116
  min-width: 0;
1026
1117
  margin: 0;
@@ -1112,12 +1203,12 @@
1112
1203
  display: flex;
1113
1204
  min-width: 0;
1114
1205
  flex-direction: column;
1206
+ gap: 0.25rem;
1115
1207
  }
1116
1208
 
1117
1209
  /* Loading options (knowledge STATE-8): a label line and a description line, at the loaded option's height. */
1118
1210
  .starci-core-list-box-skeleton-option {
1119
1211
  display: flex;
1120
- min-block-size: 2.25rem;
1121
1212
  align-items: center;
1122
1213
  padding-inline: 0.5rem;
1123
1214
  padding-block: 0.375rem;
@@ -1128,8 +1219,10 @@
1128
1219
  .starci-core-list-box-skeleton-option .starci-core-list-box-description { display: block; }
1129
1220
  .starci-core-list-box-skeleton-option .starci-core-list-box-label > .starci-core-skeleton-group { inline-size: 55%; }
1130
1221
  .starci-core-list-box-skeleton-option .starci-core-list-box-description > .starci-core-skeleton-group { inline-size: 80%; }
1131
- .starci-core-list-box-skeleton-option .starci-core-skeleton { block-size: 0.75rem; }
1132
- .starci-core-list-box-skeleton-option .starci-core-skeleton-group { gap: 0; margin-block: 0.125rem; }
1222
+ /* Skeleton parity: each line takes the line box of the vendor label (0.875rem / 1.25rem) and description (0.75rem / 1rem). */
1223
+ .starci-core-list-box-skeleton-option .starci-core-list-box-label { font-size: 0.875rem; line-height: 1.25rem; }
1224
+ .starci-core-list-box-skeleton-option .starci-core-list-box-description { font-size: 0.75rem; line-height: 1rem; }
1225
+ .starci-core-list-box-skeleton-option .starci-core-skeleton-group { gap: 0; }
1133
1226
 
1134
1227
  .starci-core-list-box-empty {
1135
1228
  padding: var(--grammar-section-gap, 1rem);
@@ -1379,6 +1472,34 @@
1379
1472
  background: currentColor;
1380
1473
  }
1381
1474
 
1475
+ /*
1476
+ * Attention entry: the whole row takes a tone background (the marker alone is too small to catch the eye). The
1477
+ * row grows 0.5rem each side and gives the same back as negative margin, so the markers and the copy keep their
1478
+ * column; the connector moves with the padding and reaches the next entry.
1479
+ */
1480
+ .starci-core-timeline-item[data-grammar-attention="true"] {
1481
+ --starci-core-timeline-attention-tone: var(--warning, CanvasText);
1482
+ margin-inline: -0.5rem;
1483
+ margin-block-end: 0.5rem;
1484
+ padding: 0.5rem;
1485
+ border-radius: 0.75rem;
1486
+ background: color-mix(in oklab, var(--starci-core-timeline-attention-tone) 14%, transparent);
1487
+ }
1488
+
1489
+ .starci-core-timeline-item[data-grammar-attention="true"][data-grammar-timeline-state="negative"] { --starci-core-timeline-attention-tone: var(--danger, CanvasText); }
1490
+ .starci-core-timeline-item[data-grammar-attention="true"][data-grammar-timeline-state="affirmative"] { --starci-core-timeline-attention-tone: var(--success, CanvasText); }
1491
+ .starci-core-timeline-item[data-grammar-attention="true"][data-grammar-timeline-state="informative"] { --starci-core-timeline-attention-tone: var(--info, CanvasText); }
1492
+
1493
+ .starci-core-timeline-item[data-grammar-attention="true"]:not(:last-child)::before {
1494
+ top: 2rem;
1495
+ bottom: -0.5rem;
1496
+ inset-inline-start: calc(1.25rem - 1px);
1497
+ }
1498
+
1499
+ .starci-core-timeline-item[data-grammar-attention="true"] .starci-core-timeline-marker {
1500
+ background: transparent;
1501
+ }
1502
+
1382
1503
  /* Loading timeline (knowledge STATE-8): static mark and connector tint, three Skeleton lines of the entry's rhythm. */
1383
1504
  .starci-core-timeline-skeleton-marker > .starci-core-skeleton { inline-size: 1rem; block-size: 1rem; }
1384
1505
  .starci-core-timeline[data-loading="true"] .starci-core-timeline-item:not(:last-child)::before { background: color-mix(in srgb, var(--foreground, CanvasText) 9%, transparent); }
@@ -1386,7 +1507,7 @@
1386
1507
  .starci-core-timeline-item[data-grammar-skeleton="true"] .starci-core-timeline-time > .starci-core-skeleton-group { inline-size: 25%; }
1387
1508
  .starci-core-timeline-item[data-grammar-skeleton="true"] .starci-core-timeline-description > .starci-core-skeleton-group { inline-size: 80%; }
1388
1509
  .starci-core-timeline-item[data-grammar-skeleton="true"] .starci-core-skeleton-group { gap: 0; }
1389
- .starci-core-timeline-item[data-grammar-skeleton="true"] .starci-core-skeleton { block-size: 0.8125rem; }
1510
+ .starci-core-timeline-item[data-grammar-skeleton="true"] .starci-core-skeleton { --starci-core-skeleton-line: 0.8125rem; }
1390
1511
 
1391
1512
  .starci-core-timeline-copy {
1392
1513
  display: flex;
@@ -1395,10 +1516,13 @@
1395
1516
  gap: 0.125rem;
1396
1517
  }
1397
1518
 
1519
+ /* The list title scale (the ListBox and panel row label, text-sm), not whatever size the page body inherits. */
1398
1520
  .starci-core-timeline-title {
1399
1521
  margin: 0;
1400
1522
  overflow-wrap: anywhere;
1523
+ font-size: 0.875rem;
1401
1524
  font-weight: 600;
1525
+ line-height: 1.25rem;
1402
1526
  }
1403
1527
 
1404
1528
  .starci-core-timeline-time,
@@ -1479,6 +1603,9 @@
1479
1603
 
1480
1604
  .starci-core-flow-graph[data-grammar-flow-height="sm"] .starci-core-flow-graph-canvas { block-size: 16rem; }
1481
1605
  .starci-core-flow-graph[data-grammar-flow-height="lg"] .starci-core-flow-graph-canvas { block-size: 32rem; }
1606
+ .starci-core-flow-graph[data-grammar-flow-height="custom"] .starci-core-flow-graph-canvas { block-size: var(--starci-flow-height, 24rem); }
1607
+ /* The readable fit grew the canvas to the graph's aspect at its width rather than shrink the text below 12px. */
1608
+ .starci-core-flow-graph[data-grammar-flow-grown="true"] .starci-core-flow-graph-canvas { block-size: var(--starci-flow-grown-height); }
1482
1609
 
1483
1610
  .starci-core-flow-graph-loading > .starci-core-skeleton { block-size: 100%; aspect-ratio: auto; }
1484
1611
 
@@ -1568,6 +1695,36 @@
1568
1695
  line-height: 1.25;
1569
1696
  }
1570
1697
 
1698
+ .starci-core-flow-graph-link {
1699
+ display: inline-flex;
1700
+ align-items: center;
1701
+ gap: 0.5rem;
1702
+ min-inline-size: 0;
1703
+ border-radius: inherit;
1704
+ color: inherit;
1705
+ text-decoration: none;
1706
+ outline: none;
1707
+ cursor: pointer;
1708
+ }
1709
+
1710
+ .starci-core-flow-graph-node:has(.starci-core-flow-graph-link:focus-visible) {
1711
+ outline: 2px solid var(--focus, var(--starci-core-focus, Highlight));
1712
+ outline-offset: 2px;
1713
+ }
1714
+
1715
+ .starci-core-flow-graph-header {
1716
+ padding: 0.125rem 0.625rem;
1717
+ color: var(--muted, GrayText);
1718
+ font-size: 0.6875rem;
1719
+ font-weight: 600;
1720
+ letter-spacing: 0.04em;
1721
+ text-transform: uppercase;
1722
+ white-space: nowrap;
1723
+ pointer-events: none;
1724
+ }
1725
+
1726
+ .starci-core-flow-graph-header[data-direction="TB"] { text-align: end; }
1727
+
1571
1728
  .starci-core-flow-graph-node[data-size="sm"] { padding: 0.25rem 0.625rem; font-size: 0.75rem; }
1572
1729
  .starci-core-flow-graph-node[data-size="lg"] { padding: 0.625rem 1.25rem; font-size: 1rem; font-weight: 600; }
1573
1730
  .starci-core-flow-graph-node[data-tone="accent"] { --starci-flow-tint: var(--accent-soft, transparent); --starci-flow-ink: var(--accent-soft-foreground, CanvasText); }
@@ -547,6 +547,14 @@
547
547
  flex: none;
548
548
  }
549
549
 
550
+ /* A loading Alert (skeleton parity): the glyph keeps its place as a round Skeleton tint. */
551
+ .starci-core-alert[data-loading="true"] .starci-core-alert-indicator {
552
+ border-radius: 999px;
553
+ background: color-mix(in oklab, var(--surface-tertiary, #e4e4e7) 70%, transparent);
554
+ }
555
+
556
+ .starci-core-alert[data-loading="true"] .starci-core-alert-indicator > * { visibility: hidden; }
557
+
550
558
  .starci-core-alert-title {
551
559
  margin: 0;
552
560
  }
@@ -663,11 +671,16 @@
663
671
  }
664
672
 
665
673
  /* ---------------------------------------------------------------- skeleton */
674
+ /*
675
+ * Skeleton parity: a text skeleton takes the line box of the copy it stands for. Each line occupies exactly `1lh` of
676
+ * the inherited type (the bar, `--starci-core-skeleton-line` tall, is centred in it by its margins), so `lines={n}`
677
+ * is as tall as n lines of that copy. A context sizes the bar through the variable, never the block size.
678
+ */
666
679
  .starci-core-skeleton-group {
667
680
  display: flex;
668
681
  width: 100%;
669
682
  flex-direction: column;
670
- gap: 0.5rem;
683
+ gap: 0;
671
684
  }
672
685
 
673
686
  .starci-core-skeleton {
@@ -678,9 +691,32 @@
678
691
 
679
692
  .starci-core-skeleton-group > .starci-core-skeleton {
680
693
  width: 100%;
681
- height: 0.875em;
694
+ height: var(--starci-core-skeleton-line, 0.875em);
695
+ margin-block: calc((1lh - var(--starci-core-skeleton-line, 0.875em)) / 2);
682
696
  }
683
697
 
698
+ /*
699
+ * The measured placeholder (internal `SkeletonPlaceholder`): the ready content is laid out unseen and the shimmer
700
+ * paints its box. A text run is an inline block one line box per line (`vertical-align: top` keeps it from growing
701
+ * the line box); a block or circle sizes to its control or mark.
702
+ */
703
+ .starci-core-skeleton-placeholder {
704
+ display: inline-block;
705
+ max-inline-size: 100%;
706
+ vertical-align: top;
707
+ color: transparent;
708
+ user-select: none;
709
+ }
710
+
711
+ .starci-core-skeleton-placeholder > * { visibility: hidden; }
712
+ .starci-core-skeleton-placeholder[data-shape="block"] { display: inline-flex; }
713
+ .starci-core-skeleton-placeholder[data-shape="circle"] { display: inline-flex; border-radius: 999px; }
714
+ .starci-core-skeleton-placeholder[data-shape="region"] { display: block; }
715
+ .starci-core-skeleton-placeholder[data-shape="text"]:empty { inline-size: min(100%, 8rem); block-size: 1lh; }
716
+
717
+ /* A skeleton root that keeps its content as a measured placeholder (Button, Badge, StatusDot) paints none of it. */
718
+ :is(.starci-core-button, [data-component="Badge"], [data-component="StatusDot"])[data-loading="true"] > * { visibility: hidden; }
719
+
684
720
  .starci-core-skeleton[data-grammar-skeleton-line="last"] {
685
721
  width: 60%;
686
722
  }
@@ -793,6 +829,9 @@
793
829
  /* Loading meter (knowledge STATE-8, `isSkeleton`): the label stays real, the bar is a static Skeleton bar of the track height. */
794
830
  .starci-core-meter-skeleton-bar { display: block; block-size: 0.5rem; overflow: hidden; border-radius: 999px; }
795
831
 
832
+ /* `size="sm"`: the thin track is HeroUI's own `.meter--sm` (h-1); only the loading bar, which is ours, follows it. */
833
+ .starci-core-meter-skeleton[data-size="sm"] .starci-core-meter-skeleton-bar { block-size: 0.25rem; }
834
+
796
835
  .starci-core-meter-skeleton-bar > .starci-core-skeleton {
797
836
  block-size: 100%;
798
837
  aspect-ratio: auto;