themelia-ui 3.1.0 → 3.2.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 (178) hide show
  1. package/.agents/skills/themelia-ui/SKILL.md +38 -9
  2. package/.agents/skills/themelia-ui/manifest.json +23 -0
  3. package/.agents/skills/themelia-ui/references/api-compatibility.md +3 -2
  4. package/.agents/skills/themelia-ui/references/blocks.md +123 -0
  5. package/.agents/skills/themelia-ui/references/composition-ladder.md +8 -8
  6. package/.agents/skills/themelia-ui/references/composition.md +98 -1
  7. package/.agents/skills/themelia-ui/references/forms.md +23 -3
  8. package/.agents/skills/themelia-ui/references/framework-wiring.md +34 -6
  9. package/.agents/skills/themelia-ui/references/i18n.md +4 -2
  10. package/.agents/skills/themelia-ui/references/imports.md +1 -1
  11. package/.agents/skills/themelia-ui/references/installation.md +5 -4
  12. package/.agents/skills/themelia-ui/references/migration.md +22 -2
  13. package/.agents/skills/themelia-ui/references/provider-and-scoping.md +27 -9
  14. package/.agents/skills/themelia-ui/references/reference-workflows.md +43 -0
  15. package/.agents/skills/themelia-ui/references/structure-and-spacing.md +147 -0
  16. package/.agents/skills/themelia-ui/references/theming.md +6 -2
  17. package/.agents/skills/themelia-ui/references/troubleshooting.md +28 -5
  18. package/.agents/skills/themelia-ui/references/verification.md +58 -2
  19. package/CHANGELOG.md +25 -0
  20. package/README.md +3 -0
  21. package/dist/{alert-dialog-Bmo1YrTm.js → alert-dialog-Cg2HhuBe.js} +1 -1
  22. package/dist/base/alert-dialog.js +1 -1
  23. package/dist/base/cards.js +1 -1
  24. package/dist/base/choice-inputs.js +1 -1
  25. package/dist/base/combobox.js +1 -1
  26. package/dist/base/command.js +1 -1
  27. package/dist/base/date-pickers.js +1 -1
  28. package/dist/base/dialog.js +1 -1
  29. package/dist/base/display.js +1 -1
  30. package/dist/base/forms-numeric.js +1 -1
  31. package/dist/base/forms.js +1 -1
  32. package/dist/base/item.js +1 -1
  33. package/dist/base/navigation.js +1 -1
  34. package/dist/base/otp-input.js +1 -1
  35. package/dist/base/overlay.js +1 -1
  36. package/dist/base/popover-menu.js +1 -1
  37. package/dist/base/repeaters.js +1 -1
  38. package/dist/base/sheet.js +1 -1
  39. package/dist/base/sidebar.js +1 -1
  40. package/dist/base/skeleton.js +1 -1
  41. package/dist/base/structure.js +1 -1
  42. package/dist/base/timeline.js +1 -1
  43. package/dist/base/upload.js +1 -1
  44. package/dist/base/value-inputs.js +1 -1
  45. package/dist/blocks/admin/access.js +2 -2
  46. package/dist/blocks/admin/commerce.js +9 -9
  47. package/dist/blocks/analytics.js +3 -3
  48. package/dist/blocks/onboarding.js +1 -1
  49. package/dist/blocks/timelines.js +1 -1
  50. package/dist/{cards-DdiICdMM.js → cards-Ddhz43Bz.js} +2 -2
  51. package/dist/{choice-inputs-CCw-0gD6.js → choice-inputs-NwRxvmZN.js} +2 -2
  52. package/dist/combobox-519ubijc.js +292 -0
  53. package/dist/{command-CnB_IZSo.js → command-JjApYFds.js} +2 -2
  54. package/dist/{comments-C6Y_ddMt.js → comments-CCQ2QRaU.js} +4 -4
  55. package/dist/components/base/display/icon-badge.d.ts +2 -2
  56. package/dist/components/base/forms/form-field.d.ts +2 -2
  57. package/dist/components/base/structure/bleed.d.ts +1 -0
  58. package/dist/components/base/structure/grid.d.ts +8 -2
  59. package/dist/components/base/structure/split.d.ts +1 -0
  60. package/dist/components/features/combobox/fetching-combobox.d.ts +2 -1
  61. package/dist/components/features/combobox/resource-combobox.d.ts +3 -0
  62. package/dist/components/features/combobox/use-suggestions.d.ts +7 -0
  63. package/dist/css/structure.css +1 -1
  64. package/dist/{date-pickers-C2HgjpFU.js → date-pickers-BwCwKIVM.js} +1 -1
  65. package/dist/{dialog-0nRFg5Zs.js → dialog-CAnwRNrm.js} +1 -1
  66. package/dist/{display-BGN834xV.js → display-a7AoX_u7.js} +1 -1
  67. package/dist/features/actions.js +1 -1
  68. package/dist/features/activities.js +3 -3
  69. package/dist/features/ai-chat.js +2 -2
  70. package/dist/features/combobox.js +285 -273
  71. package/dist/features/comments.js +1 -1
  72. package/dist/features/data-view.js +5 -5
  73. package/dist/features/event-calendar.js +3 -3
  74. package/dist/features/filters.js +1 -1
  75. package/dist/features/global-search.js +4 -4
  76. package/dist/features/map.js +2 -2
  77. package/dist/features/media-library.js +8 -8
  78. package/dist/features/mentions.js +1 -1
  79. package/dist/features/overlays.js +1 -1
  80. package/dist/features/products.js +9 -9
  81. package/dist/features/resource-assignment.js +3 -3
  82. package/dist/features/resource.js +3 -3
  83. package/dist/features/schema-form.js +6 -6
  84. package/dist/features/sync.js +2 -2
  85. package/dist/features/table.js +1 -1
  86. package/dist/features/theme-tweaker.js +10 -10
  87. package/dist/{filters-geLuSlFL.js → filters-BhsTkWpZ.js} +9 -9
  88. package/dist/{forms-NxbJK4Mh.js → forms-C0WeNPFF.js} +1 -1
  89. package/dist/{forms-numeric-C6NMdj6G.js → forms-numeric-IwQ1ltNf.js} +1 -1
  90. package/dist/hooks/use-native-dialog.d.ts +1 -1
  91. package/dist/{item-CuUJyETl.js → item-M2pmpWXh.js} +1 -1
  92. package/dist/layout/app-shell.js +1 -1
  93. package/dist/layout/auth.js +1 -1
  94. package/dist/layout/header.js +3 -3
  95. package/dist/layout/navigation.js +1 -1
  96. package/dist/layout/page.js +2 -2
  97. package/dist/layout/settings.js +2 -2
  98. package/dist/layout/sidebar.js +10 -6
  99. package/dist/layout/workspace.js +3 -3
  100. package/dist/lib/ui-provider/ui-scope.d.ts +1 -1
  101. package/dist/{mentions-oeYNv_j1.js → mentions-BOxbigwp.js} +2 -2
  102. package/dist/{navigation-BTX5Ti0Z.js → navigation-Bgu7Vc0M.js} +2 -2
  103. package/dist/{navigation-WYKjDhZq.js → navigation-DiYACqpE.js} +2 -2
  104. package/dist/{overlay-C8RAgi2_.js → overlay-BuQOl8aP.js} +5 -5
  105. package/dist/{overlays-9BxwFTou.js → overlays-1sULyytH.js} +5 -5
  106. package/dist/{popover-menu-DcLeiw9r.js → popover-menu-QXgQeWeG.js} +1 -1
  107. package/dist/{repeaters-DfptvbGA.js → repeaters-CeH20dvr.js} +4 -4
  108. package/dist/{sheet-D-DVZH_b.js → sheet-CJ1-OAla.js} +1 -1
  109. package/dist/{sidebar-DXQOy_Cb.js → sidebar-D8uGqxio.js} +4 -4
  110. package/dist/{skeleton-CubvHel9.js → skeleton-BTKNKTdj.js} +1 -1
  111. package/dist/{structure-B9Y5NkP_.js → structure-CynEZ-OY.js} +1 -2
  112. package/dist/style.css +1 -1
  113. package/dist/styles/TOKENS.md +4 -3
  114. package/dist/{table-DT_20ity.js → table-B7dtIFUx.js} +4 -4
  115. package/dist/{timeline-BRZbSdhf.js → timeline-BR_jNgvz.js} +1 -1
  116. package/dist/{timelines-DczZKW5p.js → timelines-DXG1BfIN.js} +1 -1
  117. package/dist/{upload-C_6mlbfl.js → upload-CYuoJykZ.js} +1 -1
  118. package/dist/{value-inputs-CnO6RZeT.js → value-inputs-ng8aVJJZ.js} +4 -4
  119. package/docs/README.md +11 -1
  120. package/docs/build/browser-composition-fixture.ts +28 -0
  121. package/docs/build/recipes.md +398 -1
  122. package/docs/generated/component-index.json +1993 -862
  123. package/docs/generated/components/base--display.md +2 -2
  124. package/docs/generated/components/base--forms.md +72 -1
  125. package/docs/generated/components/base--structure.md +56 -5
  126. package/docs/generated/components/base--typography.md +38 -0
  127. package/docs/generated/components/blocks--admin--access.md +10 -0
  128. package/docs/generated/components/blocks--analytics.md +44 -0
  129. package/docs/generated/components/features--combobox.md +67 -0
  130. package/docs/generated/components/features--data-view.md +59 -3
  131. package/docs/generated/components/features--filters.md +10 -0
  132. package/docs/generated/components/features--media-library.md +68 -0
  133. package/docs/generated/components/features--overlays.md +54 -0
  134. package/docs/generated/components/features--rich-text-editor.md +38 -0
  135. package/docs/generated/components/layout--app-shell.md +31 -0
  136. package/docs/generated/components/layout--containers.md +40 -0
  137. package/docs/generated/components/layout--page.md +30 -1
  138. package/docs/generated/components/layout--sidebar.md +41 -0
  139. package/docs/generated/components/ui-provider.md +1 -1
  140. package/docs/generated/composition-ladder.md +8 -8
  141. package/docs/generated/consumer-contract.json +5736 -0
  142. package/docs/generated/imports.md +1 -1
  143. package/docs/generated/recipes.json +171 -1
  144. package/docs/generated/workflows/communications-overview/analytics/communications-reference.tsx +19 -0
  145. package/docs/generated/workflows/index.json +691 -0
  146. package/docs/generated/workflows/media-picking/media-library/media-picking-reference.tsx +63 -0
  147. package/docs/generated/workflows/media-picking/media-library/reference-data.ts +3 -0
  148. package/docs/generated/workflows/multi-section-form/form-workflow/multi-section-reference.tsx +46 -0
  149. package/docs/generated/workflows/nested-action-flow/action-overlays/nested-action-reference.tsx +39 -0
  150. package/docs/generated/workflows/record-detail/page-layout/record-detail-reference.tsx +24 -0
  151. package/docs/generated/workflows/resource-index/data-view/resource-index-reference.tsx +51 -0
  152. package/docs/generated/workflows/scoped-resource-selection/combobox/scoped-resource-reference.tsx +50 -0
  153. package/docs/generated/workflows/website-content-editor/media-library/reference-data.ts +3 -0
  154. package/docs/generated/workflows/website-content-editor/rich-text-editor/website-content-reference.tsx +33 -0
  155. package/docs/generated/workflows/workspace-navigation/app-shell/workspace-navigation-reference.tsx +26 -0
  156. package/docs/learn/api-compatibility.md +3 -2
  157. package/docs/learn/blocks.md +123 -0
  158. package/docs/learn/composition.md +98 -1
  159. package/docs/learn/forms.md +23 -3
  160. package/docs/learn/framework-wiring.md +34 -6
  161. package/docs/learn/i18n.md +4 -2
  162. package/docs/learn/installation.md +5 -4
  163. package/docs/learn/migration.md +22 -2
  164. package/docs/learn/provider-and-scoping.md +27 -9
  165. package/docs/learn/reference-workflows.md +43 -0
  166. package/docs/learn/structure-and-spacing.md +147 -0
  167. package/docs/learn/theming.md +6 -2
  168. package/docs/learn/troubleshooting.md +28 -5
  169. package/docs/learn/verification.md +58 -2
  170. package/package.json +4 -1
  171. package/scripts/consumer/codemod.mjs +3 -24
  172. package/scripts/consumer/contract-reader.mjs +42 -0
  173. package/scripts/consumer/css-scan.mjs +51 -0
  174. package/scripts/consumer/diagnose.mjs +136 -0
  175. package/scripts/consumer/find-component.mjs +28 -13
  176. package/scripts/consumer/install-skill.mjs +93 -11
  177. package/src/styles/TOKENS.md +4 -3
  178. package/dist/combobox-Byu8N-Oe.js +0 -274
@@ -69,9 +69,10 @@ consumer on plain state never resolves react-hook-form.
69
69
 
70
70
  ## Field chrome
71
71
 
72
- `FormField` renders exactly **one** supporting line, resolved as `error || helperText ||
73
- hint`. They replace each other rather than stacking, so a field never grows or shifts as
74
- validation state changes. It also owns the wiring: it associates the label, points
72
+ `FormField` renders at most **one** supporting message, resolved as `error || helperText ||
73
+ hint`. They replace each other rather than stacking. A message can wrap, and adding or
74
+ changing it can change field height; check long translated errors at narrow widths.
75
+ It also owns the wiring: it associates the label, points
75
76
  `aria-describedby` at the supporting line, and sets `aria-invalid` on the control when there
76
77
  is an error, without overriding any of these that the caller set explicitly. Errors are
77
78
  announced politely (`role="status"`), so validation firing on each keystroke does not
@@ -80,6 +81,25 @@ interrupt typing.
80
81
  `FieldGroup` covers several controls sharing one label and one supporting line — a date
81
82
  range, a name split in two.
82
83
 
84
+ ## Sections and completion
85
+
86
+ Use FormSection for an editable region inside a form, Card for an independent subject,
87
+ and ContentBlock for supporting read-only prose. Flat FormSections retain region padding;
88
+ an enclosing Stack can use `gap="none"` when that padding supplies the desired separation.
89
+ FieldGroup supplies fieldset/legend semantics. Each child FormField still needs a distinct
90
+ label. A section heading is not a field label.
91
+
92
+ A resolved promise is not evidence that validation succeeded. ActionDialog normally closes
93
+ after its confirmation callback resolves. Set `closeOnAsyncComplete={false}` when the app
94
+ resolves with errors, and close controlled `open` only after explicit success. For a native
95
+ form, use `formId` to associate the dialog's submit action with that form; avoid also invoking
96
+ the mutation in `onConfirm`. Native constraint validation, the submit handler and domain
97
+ validation then each have one owner. Prevent duplicate submissions while pending, retain
98
+ drafts after rejection and associate errors with the relevant field or root region.
99
+
100
+ See the complete [multi-section form and nested action](./reference-workflows.md) sources.
101
+ For the inset and gap contract, see [Structure and spacing](./structure-and-spacing.md#use-the-two-spacing-roles).
102
+
83
103
  ## Generated forms
84
104
 
85
105
  When the fields come from a schema at runtime, use `features/schema-form`. When they are
@@ -22,7 +22,9 @@ cells. Write it once:
22
22
  import type { LinkRenderer } from "themelia-ui/layout/sidebar"
23
23
 
24
24
  export const renderLink: LinkRenderer = ({ href, children, active, disabled, external, ...rest }) =>
25
- disabled || !href ? <span {...rest}>{children}</span> : <AppLink to={href} {...rest}>{children}</AppLink>
25
+ disabled || !href ? <span {...rest}>{children}</span>
26
+ : external ? <a href={href} {...rest}>{children}</a>
27
+ : <AppLink to={href} {...rest}>{children}</AppLink>
26
28
  ```
27
29
 
28
30
  Return one element and spread the rest of the props onto it: they carry the class, the
@@ -50,6 +52,20 @@ async lifecycle; the application decides cache invalidation, retries, and error
50
52
  </ResourceIndexShell>
51
53
  ```
52
54
 
55
+ ## Lookup and navigation lifetimes
56
+
57
+ Pass a stable `scopeKey` to ResourceCombobox, SuggestionsCombobox or useSuggestions when the lookup's workspace, locale or
58
+ permission context changes. Changing callback identity alone does not refetch. A scope
59
+ change aborts old work, hides old results/errors and refetches an eligible open query while
60
+ retaining query, controlled value and open state. Late responses from an old scope cannot
61
+ complete the current lookup. The app owns whether the selected id remains valid.
62
+
63
+ Sidebar workspace disclosure keeps the mobile sheet open. A selected workspace marked
64
+ `external: false` dismisses the sheet after selection; external destinations retain native
65
+ target/rel behavior. Forward renderer props, including the click handler, to the real link.
66
+ The app's router handles same-app addresses and preserves modifiers and new-tab gestures.
67
+ See the [workspace reference](./reference-workflows.md).
68
+
53
69
  ## Permissions
54
70
 
55
71
  Resolve authorization before constructing actions. Omit an action the user cannot perform;
@@ -58,16 +74,28 @@ through the module's tooltip or description seam.
58
74
 
59
75
  ## Translation
60
76
 
61
- Supply user-facing package copy through each component's `strings` prop. Keep product copy
77
+ Supply component-owned copy through the selected API's documented `strings` prop. Passive
78
+ components whose copy comes from children need no strings object. Keep product copy
62
79
  in application data or children. The default objects are exported so a locale can start
63
80
  from the complete shape rather than guessing keys; see [Internationalization](./i18n.md).
64
81
 
65
82
  ## Server rendering and client boundaries
66
83
 
67
- Import server-pure formatting modules freely. Interactive modules carry their own
68
- `"use client"` boundary in the published entrypoint. Do not wrap the whole package in one
69
- client module: exact subpaths keep server-pure imports server-pure and optional peers
70
- isolated.
84
+ Interactive and provider-dependent entrypoints carry `"use client"`, including
85
+ `themelia-ui/primitives` and `themelia-ui/ui-provider`; passive modules such as
86
+ `themelia-ui/base/structure` can remain server-pure. Inspect the exact published entrypoint
87
+ rather than inferring its boundary from its name. In a React Server Components framework, a server component
88
+ can render those client components with serializable props; callbacks, hooks and live
89
+ controlled state belong in an application client component. A provider can receive server
90
+ rendered children, so keep the boundary around the interactive region rather than the whole
91
+ page. Check your framework's serialization rules for config containing functions or modules.
92
+
93
+ Formatting helpers exported from `primitives` share its client boundary: do not call them
94
+ from an RSC server component as if they came from a server-pure subpath. Format server data
95
+ with the application's server utilities or render the corresponding display primitive.
96
+ `themelia-ui/theming` exports pure derivation helpers without a client directive. Exact
97
+ subpaths also keep optional peers isolated. Ordinary Node SSR can load and render the
98
+ component modules; that does not make their exports server-callable in RSC.
71
99
 
72
100
  For CSP, portals, nested theme regions, and defaults, use
73
101
  [Provider and scoping](./provider-and-scoping.md). Do not recreate provider context in an
@@ -5,8 +5,10 @@ overridable, and nothing in it inspects `navigator.language` or imports a transl
5
5
 
6
6
  ## Strings
7
7
 
8
- Each module exports a `*Strings` interface and a `default*Strings` object, and its
9
- components take a `strings` prop:
8
+ Components that own visible or accessible copy document a `strings` prop; their modules
9
+ export the matching `*Strings` interface and `default*Strings` object. Passive structures
10
+ and components whose copy comes entirely from children need no strings object. Read the
11
+ selected API rather than adding `strings` by analogy:
10
12
 
11
13
  ```tsx fragment — shape only, not a program
12
14
  <Copyable strings={{ copy: "Kopieren", copied: "Kopiert" }} value={apiKey} />
@@ -4,8 +4,9 @@
4
4
  npm install themelia-ui
5
5
  ```
6
6
 
7
- React 19 and `react-dom` are required peers. Everything else is optional and only pulled in
8
- by the module that needs it; see [Optional peers](#optional-peers).
7
+ React 19 and `react-dom` are required peers. The package's regular dependencies install
8
+ automatically; additional peer packages are optional and resolved only by the modules
9
+ that need them. See [Optional peers](#optional-peers).
9
10
 
10
11
  ## Browser support
11
12
 
@@ -107,8 +108,8 @@ releases.
107
108
 
108
109
  ## Optional peers
109
110
 
110
- Every peer other than React is optional, and each is reachable only from the modules that
111
- use it: a consumer who never imports `features/map` never resolves Leaflet.
111
+ Every peer other than `react` and `react-dom` is optional, and each is reachable only from
112
+ the modules that use it: a consumer who never imports `features/map` never resolves Leaflet.
112
113
 
113
114
  The peer-to-module table is in [`docs/generated/imports.md`](../generated/imports.md).
114
115
 
@@ -3,6 +3,25 @@
3
3
  What changes between versions, what to do when you upgrade, and which changes a stable major
4
4
  allows.
5
5
 
6
+ ## Upgrading to 3.2
7
+
8
+ 3.2 adds consumer diagnostics, complete reference workflows and optional `scopeKey` support
9
+ for asynchronous combobox lookups, alongside responsive grid, dialog and navigation fixes.
10
+ Applications already on 3.1 need no new codemod replacements. Applications upgrading from
11
+ 3.0 or earlier must also follow [Upgrading to 3.1](#upgrading-to-31).
12
+
13
+ After updating the package, refresh the installed consumer skill with the
14
+ [packaged installer](./verification.md#offline-diagnosis-and-skill-freshness). Review any
15
+ local edits before replacing them, then run the read-only diagnostics and your application's
16
+ type, build and interaction checks. The [structural guide](./structure-and-spacing.md),
17
+ [blocks guide](./blocks.md) and [complete workflows](./reference-workflows.md) describe the
18
+ public composition contracts.
19
+
20
+ For lookups whose results depend on a workspace, tenant or similar context, pass a stable
21
+ `scopeKey` that changes with that context. The lookup aborts and invalidates old responses;
22
+ the application still owns clearing a selection or query that is no longer valid. See the
23
+ [scoped resource selection workflow](./reference-workflows.md).
24
+
6
25
  ## Upgrading to 3.1
7
26
 
8
27
  3.1 renames and removes public API as well as adding to it. Upgrade deliberately, not
@@ -83,8 +102,9 @@ selects `[data-row-density]`, `[data-media-density]` or `[data-activity-density]
83
102
  colour a `light-dark(light, dark)` pair. For a theme of your own:
84
103
 
85
104
  - Declare it on `:root` alone. The selector list 3.0 asked for (`:root, [data-ui-scope],
86
- [data-density], [data-theme], .light, .dark`) is not needed: nothing below `:root` declares a
87
- theme variable again.
105
+ [data-density], [data-theme], .light, .dark`) is not needed: component styles inherit the
106
+ root defaults. Explicit regional overrides and density presets replace their corresponding
107
+ inherited values; set a preset-specific spacing override on that preset.
88
108
  - Write a dark value into the colour's pair, `--primary: light-dark(#2563eb, #60a5fa)`, rather
89
109
  than a `.dark { … }` block, which colours a dark page but misses a dark region inside a light
90
110
  one.
@@ -1,5 +1,11 @@
1
1
  # Provider and scoping
2
2
 
3
+ Choose one document density owner, then use UIScope for a deliberately different region.
4
+ Changing density changes spacing; keep primary copy in Text and headings in Heading or their
5
+ component owners. Check the last field/next heading together: CardContent, FormSection and
6
+ Stack can each contribute separation. A portal must stay inside its intended token scope;
7
+ UIPortalHost provides that boundary. Test open popups as well as the page beneath them.
8
+
3
9
  Three components, and the difference between them is what they are allowed to touch.
4
10
 
5
11
  | | Renders | Reaches | Use for |
@@ -71,8 +77,10 @@ value, and a value is replaced, not merged.
71
77
 
72
78
  That is the configuration components read, and the CSS follows it: each scope writes the
73
79
  merged overrides on its element, so inside the nested scope above `--primary` is red and
74
- `--secondary` is green. Nothing below `:root` declares a theme variable again, so every value
75
- inherits through any element, a `Scope` or a `data-density` region included; see
80
+ `--secondary` is green. Component styles inherit global tokens rather than redeclaring
81
+ theme defaults. Explicit Scope/UIScope overrides replace inherited values; a density preset
82
+ also resets its spacing and control heights on that region. A value outside that boundary
83
+ does not override its local declaration; see
76
84
  [Theming](./theming.md#setting-a-theme).
77
85
 
78
86
  ## Scope — tokens only
@@ -85,17 +93,18 @@ inherits through any element, a `Scope` or a `data-density` region included; see
85
93
  </Scope>
86
94
  ```
87
95
 
88
- `Scope` renders a `div` without layout. `render` picks another element, as on every
89
- component (`render={<section />}`), and `transparent={false}` gives it a box. Every
96
+ `Scope` renders a `div` without layout. Its `render` prop picks another element
97
+ (`render={<section />}`), and `transparent={false}` gives it a box. Every
90
98
  component inside reads the variables it sets, because components compute from the theme where
91
99
  they use it; any element with a `style` does the same, and `Scope` only adds the missing
92
100
  layout rule and the `data-ui-scope` marker.
93
101
 
94
102
  ## Portals and scopes
95
103
 
96
- A scope works through CSS custom properties and the `[data-density]` / `[data-theme]`
97
- attribute selectors, and both inherit down the DOM subtree. A portal renders elsewhere in the
98
- document, so by default neither follows it:
104
+ A scope uses CSS custom properties; its `data-density` and `data-theme` attributes establish
105
+ spacing and colour-scheme values that inherit through the DOM subtree. The attributes
106
+ themselves are not copied to descendants. A portal renders elsewhere in the document, so
107
+ those inherited values do not follow it by default:
99
108
 
100
109
  ```tsx fragment — a template with a hole in it
101
110
  <UIScope config={{ density: "compact" }}>
@@ -136,7 +145,7 @@ in — a shadow root, or a positioned overlay element it manages itself.
136
145
 
137
146
  ## Strict Content Security Policy
138
147
 
139
- Some Base UI-backed controls emit a tiny inline style or pre-hydration script for behavior
148
+ Some Base UI-backed controls emit a tiny inline style element or pre-hydration script for behavior
140
149
  the component stylesheet cannot express. Put `CSPProvider` at the same application boundary
141
150
  as `UIRoot` and pass the request nonce through:
142
151
 
@@ -154,7 +163,7 @@ React 19 hoists styles that carry `precedence`. During streaming SSR, configure
154
163
  renderer with the same style nonce as the provider; otherwise React omits the attribute and
155
164
  warns about the mismatch.
156
165
 
157
- When the policy permits no inline elements at all, suppress Base UI's style elements and
166
+ When the policy forbids inline style elements, suppress Base UI's style elements and
158
167
  ship the scrollbar rule in the consuming application's stylesheet:
159
168
 
160
169
  ```tsx fragment — App comes from the consuming application
@@ -171,6 +180,15 @@ ship the scrollbar rule in the consuming application's stylesheet:
171
180
  `disableStyleElements` does not disable kit behavior; it moves those small rules into the
172
181
  consuming app. It does not change how the kit's CSS loads.
173
182
 
183
+ This option does not remove inline scripts or `style` attributes. A nonce authorizes the
184
+ matching style/script elements; it does not authorize style attributes in server-rendered
185
+ HTML. Providers, responsive structure and popup positioning can use style attributes, so
186
+ a policy that blocks them needs a separate SSR strategy for the adopted components.
187
+ Inspect the rendered HTML and browser CSP reports; `disableStyleElements` alone does not
188
+ establish compatibility with that policy. See the
189
+ [Base UI CSP reference](https://base-ui.com/react/utils/csp-provider#inline-style-attributes)
190
+ for the distinction and available integration strategies.
191
+
174
192
  ## Reading the config
175
193
 
176
194
  Resolution, in every component:
@@ -0,0 +1,43 @@
1
+ # Complete reference workflows
2
+
3
+ These sources use public package imports and fixed local mock operations. Copy the complete
4
+ workflow directory, including relative helpers, then replace its transport and policy with
5
+ your application's. The [versioned inventory](../generated/workflows/index.json) lists every
6
+ file, import and API reference. Its package version must match the installed package.
7
+
8
+ | Workflow | Owners and application seam | Complete entry source |
9
+ | --- | --- | --- |
10
+ | Record detail | Page owns route heading; sibling Cards own subjects; MetadataList owns facts; app owns record commands | [Record detail](../generated/workflows/record-detail/page-layout/record-detail-reference.tsx) |
11
+ | Server resource index | DataView owns browser presentation; app owns server matching, unknown totals, cursor and page selection | [Resource index](../generated/workflows/resource-index/data-view/resource-index-reference.tsx) |
12
+ | Multi-section form | Card owns surface; flat FormSections retain insets; FieldGroup and FormField own names; app owns validation and completion | [Form](../generated/workflows/multi-section-form/form-workflow/multi-section-reference.tsx) |
13
+ | Scoped resource selection | ResourceCombobox owns lookup; app owns scopeKey and selection validity; Overlay owns task | [Selector](../generated/workflows/scoped-resource-selection/combobox/scoped-resource-reference.tsx) |
14
+ | Nested action flow | Parent owns draft; child owns confirmation; app closes explicitly after successful mutation | [Nested action](../generated/workflows/nested-action-flow/action-overlays/nested-action-reference.tsx) |
15
+ | Workspace navigation | Shell owns geometry; workspace owns disclosure; app owns router and same-app policy | [Workspace](../generated/workflows/workspace-navigation/app-shell/workspace-navigation-reference.tsx) |
16
+ | Media picking | MediaLibraryDialog owns query/picking UI; app commits a separate picker draft on confirmation, pages through footer slots, hydrates saved ids independently, reports unavailable selection and authorizes fetches | [Media](../generated/workflows/media-picking/media-library/media-picking-reference.tsx) |
17
+ | Communications overview | MetricGrid owns quantities; MetadataList owns prose facts; app owns period, delivery state and retry | [Communications](../generated/workflows/communications-overview/analytics/communications-reference.tsx) |
18
+ | Website editor | RichTextEditor owns editing; app retains locale drafts and alt text; RichText sanitizes preview | [Website editor](../generated/workflows/website-content-editor/rich-text-editor/website-content-reference.tsx) |
19
+
20
+ Forms using plain state need no form peer. The optional forms-rhf adapter connects existing
21
+ React Hook Form state without duplicating it. The resource-index workflow uses features/table and
22
+ needs its table peer; record-detail uses peer-free base/table; analytics and rich editing require the peers listed in their generated
23
+ API pages. The general reference consumer stays free of optional peers; the admin reference
24
+ consumer demonstrates adopted adapters.
25
+
26
+ Pending, empty, failed refresh, unavailable selection and resolved validation errors are
27
+ different states. These fixtures demonstrate local feedback and retained drafts. They do
28
+ not establish real persistence, permissions, cache invalidation or backend sanitization.
29
+ Exercise the named cases in your app; [record the evidence](./verification.md#completion-record)
30
+ and identify conditions you could not inspect.
31
+
32
+ The inventory's `verification` entries map required cases to behavioral tests or visual
33
+ inspection conditions. They specify coverage to run, not a claim that your application passed.
34
+ `repositoryFile` identifies a maintainer test in the source repository; copy workflow files
35
+ from the inventory's generated source list and adapt the named behaviors to your test runner.
36
+ The browser cases use explicit mock actions; the read-only observer supplies advisory geometry
37
+ findings. Review screenshots and semantics separately in both themes and densities at mobile,
38
+ desktop and wide widths, including long copy and RTL. Build/type checks prove the complete
39
+ sources and public imports; rich-text security tests exercise the sanitization boundary.
40
+
41
+ When embedding a contained app shell in an existing main landmark, use `contentRender={<div />}`
42
+ so the composition keeps one main. For a shell that owns the application's main, omit that
43
+ override. The reference uses the embedded form because it also appears inside a documentation page.
@@ -0,0 +1,147 @@
1
+ # Structure and spacing
2
+
3
+ Choose the semantic owner first, then arrange its siblings. A Page owns route geometry;
4
+ Card owns an independent subject; ContentBlock owns supporting content; FormSection owns
5
+ an editable region. Stack and Grid arrange those owners without adding a heading, landmark,
6
+ surface or inset. See [Composition](./composition.md#decide-the-owners-before-jsx) for names,
7
+ commands and field ownership, and [Blocks](./blocks.md) for ready subject arrangements.
8
+
9
+ ## Choose a structural block
10
+
11
+ | Need | Public owner | What it supplies |
12
+ | --- | --- | --- |
13
+ | Route title, description and commands | `Page` from `layout/page` | Container, page header and body rhythm. It does not create a main landmark or own scrolling. |
14
+ | Page scroll inside a bounded shell | `PageViewport` from `layout/containers` | Scroll region and an inline-size query container. Focusable by default for keyboard scrolling. |
15
+ | Centred width and inline gutter | `Container` from `layout/containers` | Width and gutter only. Page already includes it. |
16
+ | Semantic section with rhythm between groups | `Section` from `layout/containers` | A native section and twice the group gap; supply its heading or accessible name. |
17
+ | Primary work beside a secondary summary | `TwoColumnLayout` from `layout/containers` | Main/aside arrangement, optional full-width header/footer; stacks below `lg`. Aside stays second in the DOM. |
18
+ | One row or column of passive siblings | `Stack` from `base/structure` | Direction, gap, alignment, distribution and optional wrapping. No padding prop. |
19
+ | An explicit column count | `Grid` and `GridCell` from `base/structure` | One to four columns, responsive gaps and cell spans. |
20
+ | As many equal columns as fit | `AdaptiveGrid` from `base/structure` | Auto-fit tracks; `minColumnWidth` is `default` (16rem), `sm` (12rem) or a CSS length. |
21
+ | A fluid main region and a constrained rail | `Split` from `base/structure` | Two direct children: main first, side second. `sideWidth` defaults to 18rem; `collapseBelow` defaults to `md`. |
22
+ | Intentional edge-to-edge content in an inset | `Bleed` from `base/structure` | Negative margins on the selected axis. Match `amount` to the inset it cancels. |
23
+
24
+ Read the exact [structure API](../generated/components/base--structure.md),
25
+ [containers API](../generated/components/layout--containers.md) and
26
+ [page API](../generated/components/layout--page.md) before adding props. Container's
27
+ `maxWidth` (`sm`, `md`, `lg`, `xl`, `2xl`, `full`) and `gutter` (`none`, `sm`, `md`, `lg`)
28
+ are a different contract from Stack/Grid widths (`default`, `sm`, `full`, `none` or a CSS
29
+ length). Container gutters are zero, one, one-and-a-half or twice `--padding`; they do not
30
+ add spacing tokens. Stack/Grid `maxWidth` caps width without centring it or adding a gutter.
31
+
32
+ An app shell normally owns the single main landmark and page scroller. Do not add another
33
+ PageViewport around each section. For document scrolling without a bounded shell, Page can
34
+ live in the application's existing main. When embedding a shell inside another main, use
35
+ its documented `contentRender` seam; see [complete workflows](./reference-workflows.md).
36
+
37
+ ## Use the two spacing roles
38
+
39
+ | Role | Public tokens | Typical use |
40
+ | --- | --- | --- |
41
+ | Container inset | `--padding` | Card regions and decorated ContentBlocks |
42
+ | Item inset | `--padding-sm` | Rows, cells and compact items |
43
+ | Between groups | `--gap`, `gap="default"` | Fields, sibling Cards, sections |
44
+ | Within a group | `--gap-sm`, `gap="sm"` | Title/description, icon/label, toolbar controls |
45
+
46
+ The omitted structure gap is `default`. `gap="none"` removes separation; it is not a third
47
+ spacing step. Insets belong to the child surface and gaps to the parent arrangement. Avoid
48
+ giving both the same separation job. Density changes these lengths together; it does not
49
+ choose smaller text roles. [Theming](./theming.md#scale-and-density) explains density, scale
50
+ and typography scale, including overriding a preset's values.
51
+
52
+ CardContent supplies its inset and a group gap between direct children; it drops its top
53
+ padding after an undivided CardHeader. Card `surface="flat"` removes decoration and retains
54
+ region insets. FormSection is a Card composition, so a flat FormSection inside CardContent
55
+ still adds its own inset. Use `Stack gap="none"` between such sections when their insets
56
+ already supply the intended rhythm; do not assume flat means flush.
57
+
58
+ ContentBlock is different: its default `surface="plain"` supplies heading/content spacing
59
+ without an inset or frame. Its `bordered`, `muted` and `card` surfaces add an inset.
60
+ `flush` drops that decorated surface's inset for children that own their own padding.
61
+ Neither flat nor flush changes field associations or heading semantics.
62
+
63
+ ```tsx compile
64
+ import { Card } from "themelia-ui/base/cards"
65
+ import { ContentBlock, MetadataList } from "themelia-ui/base/display"
66
+ import { Grid, Stack } from "themelia-ui/base/structure"
67
+ import { Text } from "themelia-ui/base/typography"
68
+ import { Page } from "themelia-ui/layout/page"
69
+ import { TwoColumnLayout } from "themelia-ui/layout/containers"
70
+
71
+ export function AccountOverview() {
72
+ return <Page header={{ title: "Account" }} maxWidth="lg">
73
+ <TwoColumnLayout
74
+ main={<Stack>
75
+ <Grid columns={{ base: 1, md: 2 }}>
76
+ <Card title="Contact" titleLevel={2}>
77
+ <MetadataList items={[{ id: "email", label: "Email", value: "alex@example.com" }]} />
78
+ </Card>
79
+ <Card title="Billing" titleLevel={2}>
80
+ <MetadataList items={[{ id: "status", label: "Status", value: "Active" }]} />
81
+ </Card>
82
+ </Grid>
83
+ <Card title="Notes" titleLevel={2}>
84
+ <ContentBlock description="Context for the next account review.">
85
+ <Text>The customer prefers email updates.</Text>
86
+ </ContentBlock>
87
+ </Card>
88
+ </Stack>}
89
+ aside={<Card title="Summary" titleLevel={2}><Text>Review due next month.</Text></Card>}
90
+ />
91
+ </Page>
92
+ }
93
+ ```
94
+
95
+ The example has one route heading, three independent subjects in the main region and a
96
+ secondary summary. Page supplies the gutter; TwoColumnLayout supplies main/aside separation;
97
+ Stack/Grid separate subjects; each Card supplies its inset. The complete record and form
98
+ workflows add application-owned commands, validation and asynchronous states.
99
+
100
+ ## Know which width drives responsiveness
101
+
102
+ A responsive structure prop accepts a single value or an object such as
103
+ `gap={{ base: "sm", md: "default" }}`. Values carry forward until another named step replaces
104
+ them; omitted lower steps use the component's defaults. `sm` as a gap value means the small
105
+ gap; `sm` as an object key means a breakpoint.
106
+
107
+ | Step | Viewport minimum | Grid's nearest query-container minimum |
108
+ | --- | --- | --- |
109
+ | `base` | All widths | All widths |
110
+ | `sm` | 40rem | 24rem |
111
+ | `md` | 48rem | 32rem |
112
+ | `lg` | 64rem | 40rem |
113
+ | `xl` | 80rem | 52rem |
114
+ | `2xl` | 96rem | 64rem |
115
+
116
+ Grid uses viewport steps when no ancestor establishes inline-size containment. With a query
117
+ container, such as PageViewport or ContentBlock, Grid's columns, gaps, alignment and maximum
118
+ width use the nearest container and the smaller thresholds above. Grid does not create its
119
+ own query container. Stack, Split, Bleed, AdaptiveGrid's responsive gap/alignment and
120
+ GridCell spans use viewport steps. AdaptiveGrid's **column count** always follows available
121
+ width through auto-fit. TwoColumnLayout stacks below the viewport's `lg` step.
122
+
123
+ Grid defaults to one column and two from `md`; `columns={1}` stays one at every width.
124
+ Use `GridCell span="full"` for a full row at every width. Numeric spans must fit the current
125
+ column count; `span={{ base: 1, md: "full", xl: 2 }}` can switch between numeric and full
126
+ placement. Coordinate responsive spans with the Grid, especially inside a query
127
+ container where their thresholds differ. Prefer AdaptiveGrid when the available panel width
128
+ should decide the count without coordinating breakpoints. Visual side positioning in Split
129
+ or TwoColumnLayout does not change reading or keyboard order.
130
+
131
+ ## Bleed, custom CSS and verification
132
+
133
+ Bleed's default amount is `none` and its default axis is `inline`. An intentional image
134
+ escape can use `amount="default"` inside an inset of `--padding`, or `sm` inside
135
+ `--padding-sm`. The shipped spacing pairs have matching padding/gap values; a custom theme
136
+ that separates those values must account for the actual inset. Bleed does not create a
137
+ surface or crop media. Check edges and focus visibility in the surrounding surface.
138
+
139
+ Application-specific geometry can use `className` and CSS over public tokens; use the
140
+ documented props first. Do not target private `--_…` variables or CSS Module hashes. Keep
141
+ copy in Text/Heading and field labels in FormField rather than setting local type styles.
142
+ For product CSS, prefer logical properties so inline start/end follow RTL.
143
+
144
+ Check a narrow **parent** as well as a narrow viewport, long headings and translated labels,
145
+ both themes and densities, and keyboard access to the scroller. Verify that additional
146
+ surfaces or gaps serve an independent subject. [Consumer verification](./verification.md)
147
+ separates compiled examples, interaction evidence and visual inspection.
@@ -3,7 +3,8 @@
3
3
  The theme is about eighty variables, declared once at `:root` in `src/styles/theme/`. Each
4
4
  colour holds both modes as `light-dark(light, dark)`, and every length, shadow, tint and
5
5
  duration comes as a pair. Components derive everything else where they use it, so a value
6
- you set anywhere reaches every component below it. [`src/styles/TOKENS.md`](../../src/styles/TOKENS.md)
6
+ you set on a region reaches its descendants unless another scope or preset overrides it.
7
+ [`src/styles/TOKENS.md`](../../src/styles/TOKENS.md)
7
8
  names the variable for each role.
8
9
 
9
10
  ## Setting a theme
@@ -22,7 +23,8 @@ for both modes:
22
23
  ```
23
24
 
24
25
  That one block reaches every region, including popups portalled to `<body>` and a dark
25
- region inside a light page: nothing below `:root` declares a colour again.
26
+ region inside a light page. Component styles do not redeclare theme colour defaults;
27
+ an explicit regional theme override takes precedence within that region.
26
28
 
27
29
  The same values work on any element, for a region of their own:
28
30
 
@@ -139,6 +141,8 @@ and headings fall back to the interface font.
139
141
  Compose title and description regions with CardHeader, ContentBlock or OverlayHeader, action
140
142
  regions with CardFooter or OverlayFooter, and label/value facts with MetadataList. These
141
143
  components own their spacing, so the application does not reproduce it.
144
+ See [Structure and spacing](./structure-and-spacing.md) for the owner and responsive-width
145
+ contracts before changing layout values.
142
146
 
143
147
  ## Scale and density
144
148
 
@@ -1,5 +1,22 @@
1
1
  # Troubleshooting
2
2
 
3
+ Before patching a shared component, trace its callers and inspect one representative of
4
+ every affected surface. Recheck open portals, nested forms and small screens when shared
5
+ spacing or naming changes. Use the [read-only diagnosis and completion record](./verification.md)
6
+ to distinguish contract errors, advisory composition findings and unavailable coverage.
7
+
8
+ ## A lookup still shows another workspace
9
+
10
+ Set `scopeKey` on ResourceCombobox, SuggestionsCombobox or useSuggestions. Callback identity is not an invalidation API.
11
+ The key invalidates answers and errors while keeping query/open/value ownership; explicitly
12
+ clear a selected id if it is invalid in the new scope. See the [complete selector](./reference-workflows.md).
13
+
14
+ ## Validation resolves but the dialog closes
15
+
16
+ Set `closeOnAsyncComplete={false}` and close controlled `open` after successful validation
17
+ and mutation. Use `formId` for native submission and keep one mutation owner. Nested child
18
+ cancel/Escape should leave the parent draft visible; verify focus returns to the child invoker.
19
+
3
20
  ## A component is unstyled
4
21
 
5
22
  A component brings its own CSS in a bundler. When it arrives unstyled:
@@ -74,8 +91,8 @@ listed there, report it as a package containment defect.
74
91
 
75
92
  ## A theme or density override does not apply
76
93
 
77
- Every theme variable is declared once, at `:root`, and inherits from wherever you set it. When
78
- an override does not show:
94
+ Theme defaults are declared at `:root` and inherit until an explicit scope override or
95
+ density preset establishes another value. When an override does not show:
79
96
 
80
97
  - **Is it the theme's name?** A 3.0 name such as `--space-md`, `--control-h` or
81
98
  `--primary-10` is gone; the codemod renames or reports each one, and
@@ -121,6 +138,10 @@ set `disableStyleElements` and serve the documented Base UI behaviour rules from
121
138
  stylesheet. The kit's stylesheets are ordinary static CSS. See
122
139
  [Provider and scoping](./provider-and-scoping.md#strict-content-security-policy).
123
140
 
141
+ If the report names `style-src-attr`, a nonce or `disableStyleElements` will not authorize
142
+ server-rendered style attributes. Inspect the adopted components' SSR output and use the
143
+ separate attribute-policy strategy described in that guide.
144
+
124
145
  In a bundled app the kit's CSS follows your own pipeline: stylesheet files in production,
125
146
  while a dev server or `style-loader` that injects `<style>` elements needs the nonce, as your
126
147
  own CSS does.
@@ -128,11 +149,13 @@ own CSS does.
128
149
  ## The design looks inconsistent after overrides
129
150
 
130
151
  Override global semantic tokens first: colour roles, radius, spacing and density,
131
- typography, and control geometry. Component-level variables are implementation detail
132
- unless the module reference describes one as an extension point. Do not target hashed CSS
152
+ typography, and control geometry. Variables prefixed `--_` are private implementation detail.
153
+ Use a component-level variable only when its module reference documents a public extension
154
+ point. Do not target hashed CSS
133
155
  Module names; use the stable `data-slot` attributes and BEM hooks such as
134
156
  `{name}--component`, and for a state the attributes the primitives set (`data-open`, `data-checked`, `data-disabled`,
135
- `data-highlighted`, `data-popup-open`). Radix's `[data-state="open"]` matches nothing here.
157
+ `data-highlighted`, `data-popup-open`). Do not assume Radix's `[data-state="open"]` for Base
158
+ UI primitives. Use a module's own `data-state` only when its API documents that contract.
136
159
 
137
160
  ## Rich text and untrusted HTML
138
161
 
@@ -32,8 +32,9 @@ test("global search returns focus after dismissal", async ({ page }) => {
32
32
  Prefer role- and name-based assertions over CSS Module selectors. When a visual or state
33
33
  assertion needs a DOM hook, use `data-slot`, documented BEM hooks, or the state attributes
34
34
  the primitives set (`data-open`, `data-checked`, `data-disabled`, `data-highlighted`,
35
- `data-popup-open`). These are Base UI's attributes, not Radix's: `[data-state="open"]`
36
- matches nothing.
35
+ `data-popup-open`). Use Base UI's documented attributes for its primitive states rather
36
+ than assuming Radix's `[data-state="open"]`. Some kit modules expose their own `data-state`
37
+ contract; use it only where the selected API documents it.
37
38
 
38
39
  ## Theme contract check
39
40
 
@@ -41,6 +42,61 @@ Create one application fixture that changes primary colour, surface/background r
41
42
  radius, density, and typography scale at a scope boundary. If a component ignores it, report
42
43
  the module and token before adding a local patch: the package may be missing a derivation.
43
44
 
45
+ ## Offline diagnosis and skill freshness
46
+
47
+ Run the commands from the package installed in this application:
48
+
49
+ ```bash
50
+ node node_modules/themelia-ui/scripts/consumer/install-skill.mjs --project=. --check --json
51
+ node node_modules/themelia-ui/scripts/consumer/diagnose.mjs --project=. --json src/
52
+ ```
53
+
54
+ Both checks are read-only. Diagnostics use the consumer's installed TypeScript parser and
55
+ the package's generated export/migration facts. Supply explicit paths. Unsupported formats,
56
+ parse failures, missing paths, metadata or parser produce exit 2 (incomplete); definite
57
+ contract errors produce exit 1; complete checks without errors produce exit 0. Advisory
58
+ composition/private-style warnings do not establish a defect. Excluded build/dependency
59
+ directories and symlinks are not followed. Reports name the checked and skipped rules/files.
60
+
61
+ Freshness reports expected and installed versions plus missing, stale, modified or unmanaged
62
+ copies. Review modifications before reinstalling a generated copy. The installer refuses local edits
63
+ until `--overwrite-modified` explicitly replaces reviewed generated content. Keep application policy
64
+ in a separate skill; an unmanaged same-name skill is preserved. Missing metadata requires
65
+ repairing the package installation or consulting the matching release, never inventing an API.
66
+
67
+ If an installed copy's manifest is missing or invalid, the installer repairs it automatically
68
+ only when its remaining files match the packaged content. Differing or extra files require
69
+ review and `--overwrite-modified`, just like edits to a copy with a valid manifest.
70
+
71
+ Replacements are staged before writes, and caught write failures restore backups. An abrupt
72
+ process termination can leave mixed versions across targets and sibling `.backup-*` or
73
+ `.installing-*` directories. Run `--check`, review those retained directories, preserve any
74
+ local policy, and reinstall the intended generated copies. A backup-cleanup error means the
75
+ new copies were installed; the error names the retained backup for review and manual cleanup.
76
+
77
+ The [browser observer](../build/browser-composition-fixture.ts) accepts an already open caller
78
+ fixture and observes overflow, repeated sibling headings and control visibility. It performs
79
+ no navigation, click or submission. These geometry/semantic observations are advisory; they
80
+ do not establish accessibility or business acceptance. Optional browser tooling belongs in
81
+ the application's test dependencies, not the UI runtime.
82
+
83
+ Opt into interaction tests only against explicit mock actions. The [complete workflows](./reference-workflows.md)
84
+ provide scoped late responses, pending/empty/error lists, nested cancel, resolved validation,
85
+ workspace selection and retained locale drafts. Assert keyboard selection without accidental
86
+ form submission, one Escape layer at a time, focus return, associated errors and explicit
87
+ successful completion. Do not crawl unknown application routes or submit business forms.
88
+
89
+ ## Completion record
90
+
91
+ Record installed package/skill versions; selected owners and seams; shared callers inspected;
92
+ fixture routes and states; viewport widths; themes, densities and locales; keyboard/focus
93
+ interactions; commands and observed results. Separate type/build evidence, behavior tests,
94
+ visual inspection and application acceptance. Name unavailable conditions explicitly.
95
+
96
+ When no browser is available, report types/build/static findings and mark interaction and
97
+ visual checks unverified. Passing named static/browser checks does not establish permissions,
98
+ data integrity, persistence, backend sanitization or every product workflow.
99
+
44
100
  ## Upgrade check
45
101
 
46
102
  Read the [changelog](../../CHANGELOG.md), run the package's codemod when a release names one