@elabs-ai/components-ui 4.0.0 → 4.1.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 (223) hide show
  1. package/README.md +10 -8
  2. package/dist/chunk-JKPPTL63.js +48 -0
  3. package/dist/chunk-JKPPTL63.js.map +1 -0
  4. package/dist/form.d.ts +28 -0
  5. package/dist/form.js +123 -0
  6. package/dist/form.js.map +1 -0
  7. package/dist/index.d.ts +2485 -167
  8. package/dist/index.js +10716 -2706
  9. package/dist/index.js.map +1 -1
  10. package/package.json +21 -7
  11. package/src/_peer-deps.test.ts +59 -0
  12. package/src/blocks-comparison-table.stories.tsx +2 -2
  13. package/src/components/accordion/accordion.tsx +1 -1
  14. package/src/components/advanced-group/advanced-group.tsx +1 -1
  15. package/src/components/alert/alert.tsx +4 -1
  16. package/src/components/app-shell/app-shell.stories.tsx +44 -2
  17. package/src/components/app-shell/app-shell.tsx +35 -2
  18. package/src/components/app-sidebar/app-sidebar.stories.tsx +9 -1
  19. package/src/components/attribution-panel/attribution-panel.tsx +1 -1
  20. package/src/components/attribution-panel/attributions.generated.ts +81 -77
  21. package/src/components/badge/badge.stories.tsx +8 -0
  22. package/src/components/badge/badge.tsx +1 -1
  23. package/src/components/bento-grid/bento-grid.test.tsx +1 -1
  24. package/src/components/bento-grid/bento-grid.tsx +1 -1
  25. package/src/components/breadcrumb/breadcrumb.stories.tsx +38 -0
  26. package/src/components/breadcrumb/breadcrumb.test.tsx +24 -1
  27. package/src/components/breadcrumb/breadcrumb.tsx +7 -2
  28. package/src/components/button/button.stories.tsx +116 -8
  29. package/src/components/button/button.tsx +1 -1
  30. package/src/components/card/card.stories.tsx +11 -6
  31. package/src/components/card/card.tsx +1 -1
  32. package/src/components/change-review/change-review.stories.tsx +145 -1
  33. package/src/components/change-review/change-review.test.tsx +143 -0
  34. package/src/components/change-review/change-review.tsx +185 -4
  35. package/src/components/checkbox/checkbox.tsx +1 -1
  36. package/src/components/color-picker/color-picker.tsx +6 -5
  37. package/src/components/command/command.stories.tsx +16 -6
  38. package/src/components/command/command.test.tsx +73 -0
  39. package/src/components/command/command.tsx +30 -7
  40. package/src/components/command/index.ts +1 -0
  41. package/src/components/command-trigger/command-trigger.stories.tsx +39 -0
  42. package/src/components/command-trigger/command-trigger.test.tsx +18 -0
  43. package/src/components/command-trigger/command-trigger.tsx +70 -0
  44. package/src/components/command-trigger/index.ts +1 -0
  45. package/src/components/context-rail/context-rail.stories.tsx +285 -0
  46. package/src/components/context-rail/context-rail.test.tsx +263 -0
  47. package/src/components/context-rail/context-rail.tsx +678 -0
  48. package/src/components/context-rail/index.ts +1 -0
  49. package/src/components/copyable-value/copyable-value.tsx +1 -1
  50. package/src/components/dialog/dialog.tsx +2 -2
  51. package/src/components/dropdown-menu/dropdown-menu.stories.tsx +1 -1
  52. package/src/components/empty-state/empty-state.test.tsx +26 -0
  53. package/src/components/empty-state/empty-state.tsx +6 -1
  54. package/src/components/expand-dialog/expand-dialog.tsx +1 -2
  55. package/src/components/field/field-context.ts +59 -0
  56. package/src/components/field/field.stories.tsx +142 -0
  57. package/src/components/field/field.test.tsx +415 -0
  58. package/src/components/field/field.tsx +340 -0
  59. package/src/components/field/index.ts +9 -0
  60. package/src/components/field-row/field-row.stories.tsx +85 -1
  61. package/src/components/field-row/field-row.test.tsx +145 -0
  62. package/src/components/field-row/field-row.tsx +48 -6
  63. package/src/components/file-upload/file-upload.stories.tsx +16 -0
  64. package/src/components/file-upload/file-upload.test.tsx +7 -2
  65. package/src/components/file-upload/file-upload.tsx +15 -4
  66. package/src/components/form/form.tsx +5 -2
  67. package/src/components/icon-button/icon-button.test.tsx +1 -1
  68. package/src/components/icon-button/icon-button.tsx +2 -2
  69. package/src/components/input/input.stories.tsx +121 -0
  70. package/src/components/input/input.test.tsx +13 -0
  71. package/src/components/input/input.tsx +2 -2
  72. package/src/components/input-group/input-group.stories.tsx +74 -0
  73. package/src/components/input-group/input-group.tsx +13 -4
  74. package/src/components/input-otp/input-otp.tsx +5 -1
  75. package/src/components/keyboard-shortcuts/index.ts +6 -0
  76. package/src/components/keyboard-shortcuts/keyboard-shortcuts.stories.tsx +177 -0
  77. package/src/components/keyboard-shortcuts/keyboard-shortcuts.test.tsx +73 -0
  78. package/src/components/keyboard-shortcuts/keyboard-shortcuts.tsx +203 -0
  79. package/src/components/link-preview/link-preview.tsx +1 -1
  80. package/src/components/locale-provider/index.ts +6 -1
  81. package/src/components/locale-provider/locale-provider.stories.tsx +89 -1
  82. package/src/components/locale-provider/locale-provider.test.tsx +257 -1
  83. package/src/components/locale-provider/locale-provider.tsx +63 -8
  84. package/src/components/locale-provider/messages.ts +501 -2
  85. package/src/components/match-highlight/match-highlight.stories.tsx +1 -1
  86. package/src/components/mention-input/mention-input-mirror.stories.tsx +9 -1
  87. package/src/components/mention-input/mention-input.tsx +8 -38
  88. package/src/components/mention-input/mention-value.ts +8 -2
  89. package/src/components/metric-card/metric-card.stories.tsx +5 -3
  90. package/src/components/metric-card/metric-card.tsx +21 -2
  91. package/src/components/model-picker/model-picker.stories.tsx +63 -3
  92. package/src/components/model-picker/model-picker.test.tsx +63 -0
  93. package/src/components/model-picker/model-picker.tsx +62 -36
  94. package/src/components/nav-main/nav-main.tsx +1 -1
  95. package/src/components/nav-notifications/nav-notifications.stories.tsx +110 -0
  96. package/src/components/nav-notifications/nav-notifications.tsx +57 -5
  97. package/src/components/navigation-menu/navigation-menu.stories.tsx +22 -0
  98. package/src/components/navigation-menu/navigation-menu.test.tsx +174 -0
  99. package/src/components/navigation-menu/navigation-menu.tsx +189 -12
  100. package/src/components/page-shell/page-shell.stories.tsx +163 -0
  101. package/src/components/page-shell/page-shell.test.tsx +229 -0
  102. package/src/components/page-shell/page-shell.tsx +108 -25
  103. package/src/components/pagination/pagination.test.tsx +90 -0
  104. package/src/components/pagination/pagination.tsx +9 -5
  105. package/src/components/radio-group/radio-group.tsx +1 -1
  106. package/src/components/rating/rating.tsx +1 -1
  107. package/src/components/resizable/resizable.stories.tsx +114 -2
  108. package/src/components/resizable/resizable.tsx +1 -1
  109. package/src/components/revision-timeline/revision-timeline.stories.tsx +8 -1
  110. package/src/components/revision-timeline/revision-timeline.test.tsx +26 -0
  111. package/src/components/revision-timeline/revision-timeline.tsx +46 -3
  112. package/src/components/schema-form/from-json-schema.test.ts +390 -0
  113. package/src/components/schema-form/from-json-schema.ts +375 -0
  114. package/src/components/schema-form/index.ts +81 -0
  115. package/src/components/schema-form/schema-form-spec.ts +698 -0
  116. package/src/components/schema-form/schema-form.stories.tsx +566 -0
  117. package/src/components/schema-form/schema-form.test.tsx +900 -0
  118. package/src/components/schema-form/schema-form.tsx +1545 -0
  119. package/src/components/section-header/section-header.stories.tsx +37 -0
  120. package/src/components/section-header/section-header.tsx +12 -1
  121. package/src/components/select/select.tsx +6 -2
  122. package/src/components/sheet/sheet.tsx +1 -1
  123. package/src/components/side-dock/index.ts +1 -0
  124. package/src/components/side-dock/side-dock.stories.tsx +137 -0
  125. package/src/components/side-dock/side-dock.test.tsx +273 -0
  126. package/src/components/side-dock/side-dock.tsx +502 -0
  127. package/src/components/sidebar/index.ts +1 -0
  128. package/src/components/sidebar/sidebar-frame.test.tsx +162 -0
  129. package/src/components/sidebar/sidebar.stories.tsx +421 -1
  130. package/src/components/sidebar/sidebar.test.tsx +36 -1
  131. package/src/components/sidebar/sidebar.tsx +245 -33
  132. package/src/components/skip-link/index.ts +1 -0
  133. package/src/components/skip-link/skip-link.stories.tsx +106 -0
  134. package/src/components/skip-link/skip-link.test.tsx +20 -0
  135. package/src/components/skip-link/skip-link.tsx +34 -0
  136. package/src/components/slider/slider.tsx +1 -1
  137. package/src/components/split-panel/index.ts +6 -1
  138. package/src/components/split-panel/split-panel.stories.tsx +18 -2
  139. package/src/components/split-panel/split-panel.test.tsx +20 -0
  140. package/src/components/split-panel/split-panel.tsx +28 -8
  141. package/src/components/state-panel/state-panel.stories.tsx +52 -0
  142. package/src/components/state-panel/state-panel.test.tsx +125 -0
  143. package/src/components/state-panel/state-panel.tsx +96 -17
  144. package/src/components/status-badge/status-badge.stories.tsx +8 -0
  145. package/src/components/switch/switch.tsx +1 -1
  146. package/src/components/table/table.stories.tsx +62 -0
  147. package/src/components/table/table.test.tsx +142 -1
  148. package/src/components/table/table.tsx +60 -1
  149. package/src/components/tabs/tabs.tsx +8 -2
  150. package/src/components/tag-input/tag-input.tsx +3 -2
  151. package/src/components/team-switcher/team-switcher.tsx +34 -5
  152. package/src/components/textarea/textarea.tsx +1 -1
  153. package/src/components/theme-switcher/theme-switcher.tsx +6 -2
  154. package/src/components/theme-switcher/use-theme-transition.ts +2 -2
  155. package/src/components/timeline/timeline.stories.tsx +27 -0
  156. package/src/components/timeline/timeline.tsx +11 -1
  157. package/src/components/toggle/toggle.tsx +1 -1
  158. package/src/components/toolbar/toolbar.stories.tsx +10 -2
  159. package/src/components/tree/tree.stories.tsx +2 -2
  160. package/src/components/tree/tree.tsx +14 -9
  161. package/src/components/typography/prose.tsx +1 -1
  162. package/src/components/typography/typography.stories.tsx +228 -0
  163. package/src/components/view-toolbar/view-toolbar.stories.tsx +41 -1
  164. package/src/components/view-toolbar/view-toolbar.test.tsx +33 -0
  165. package/src/components/view-toolbar/view-toolbar.tsx +37 -4
  166. package/src/components/virtual-select/virtual-select.test.tsx +1 -1
  167. package/src/components/wizard/wizard.tsx +1 -1
  168. package/src/components/workspace-picker/index.ts +3 -0
  169. package/src/components/workspace-picker/workspace-picker-state.ts +50 -0
  170. package/src/components/workspace-picker/workspace-picker.stories.tsx +126 -0
  171. package/src/components/workspace-picker/workspace-picker.test.tsx +207 -0
  172. package/src/components/workspace-picker/workspace-picker.tsx +172 -0
  173. package/src/illustrations/empty-list-illustration.tsx +26 -0
  174. package/src/illustrations/error-illustration.tsx +60 -0
  175. package/src/illustrations/first-run-illustration.tsx +27 -0
  176. package/src/illustrations/illustration-base.tsx +108 -0
  177. package/src/illustrations/illustrations.stories.tsx +94 -0
  178. package/src/illustrations/illustrations.test.tsx +98 -0
  179. package/src/illustrations/index.ts +16 -0
  180. package/src/illustrations/no-access-illustration.tsx +45 -0
  181. package/src/illustrations/no-results-illustration.tsx +46 -0
  182. package/src/illustrations/offline-illustration.tsx +37 -0
  183. package/src/illustrations/success-illustration.tsx +40 -0
  184. package/src/index.ts +75 -2
  185. package/src/lib/agent-event-model.test.ts +12 -0
  186. package/src/lib/agent-event-model.ts +42 -0
  187. package/src/lib/approval-option.test.ts +17 -0
  188. package/src/lib/approval-option.ts +53 -0
  189. package/src/lib/check-result.ts +33 -0
  190. package/src/lib/diff-line.test.ts +36 -0
  191. package/src/lib/diff-line.ts +63 -0
  192. package/src/lib/diff-rows.test.ts +76 -0
  193. package/src/lib/diff-rows.ts +92 -0
  194. package/src/lib/format-duration.test.ts +22 -0
  195. package/src/lib/format-duration.ts +30 -0
  196. package/src/lib/has-renderable-content.test.ts +49 -0
  197. package/src/lib/has-renderable-content.ts +32 -0
  198. package/src/lib/operating-mode.test.ts +36 -0
  199. package/src/lib/operating-mode.ts +81 -0
  200. package/src/lib/optional-peer.ts +59 -0
  201. package/src/lib/session-launch.ts +37 -0
  202. package/src/lib/slash-command.test.ts +44 -0
  203. package/src/lib/slash-command.ts +45 -0
  204. package/src/lib/trigger-query.test.ts +94 -0
  205. package/src/lib/trigger-query.ts +88 -0
  206. package/src/lib/use-mobile.ts +12 -6
  207. package/src/motion.stories.tsx +6 -0
  208. package/src/templates-object-detail-hub.stories.tsx +2 -2
  209. package/src/templates-screen-states.stories.tsx +5 -5
  210. package/src/templates-settings.stories.tsx +1 -1
  211. package/src/blocks/sidebar-02/app-sidebar.tsx +0 -160
  212. package/src/blocks/sidebar-02/logo.tsx +0 -18
  213. package/src/blocks/sidebar-02/nav-main.tsx +0 -13
  214. package/src/blocks/sidebar-02/nav-notifications.tsx +0 -7
  215. package/src/blocks/sidebar-02/sidebar-02.stories.tsx +0 -23
  216. package/src/blocks/sidebar-02/team-switcher.tsx +0 -7
  217. package/src/blocks/sidebar-04/app-sidebar.tsx +0 -236
  218. package/src/blocks/sidebar-04/mail-context.tsx +0 -29
  219. package/src/blocks/sidebar-04/nav-user.tsx +0 -6
  220. package/src/blocks/sidebar-04/sidebar-04.stories.tsx +0 -150
  221. package/src/blocks/sidebar-05/app-sidebar.tsx +0 -375
  222. package/src/blocks/sidebar-05/sidebar-05.stories.tsx +0 -18
  223. package/src/blocks/sidebar-05/team-switcher.tsx +0 -6
@@ -1,5 +1,26 @@
1
1
  "use client";
2
2
 
3
+ /**
4
+ * ICU cardinal-plural category, per `Intl.PluralRules` ("zero" | "one" | "two"
5
+ * | "few" | "many" | "other"). Locales vary in which categories they use —
6
+ * English has only "one"/"other"; Polish and Russian also use "few"/"many" —
7
+ * so a `PluralMessage` may omit any category it doesn't need. `"other"` is the
8
+ * universal fallback every locale defines.
9
+ */
10
+ export type PluralCategory = Intl.LDMLPluralRule;
11
+
12
+ /**
13
+ * A per-count message: one string per plural category, selected at render
14
+ * time via `Intl.PluralRules(locale).select(count)`. Each string still
15
+ * supports the same `{name}`-style interpolation as a plain message.
16
+ * Note: when passed to `t()`, `count` in `vars` must be a number, not a
17
+ * numeric string, to select the correct plural category.
18
+ */
19
+ export type PluralMessage = Partial<Record<PluralCategory, string>>;
20
+
21
+ /** A single message value: a plain string, or a plural-form map. */
22
+ export type MessageValue = string | PluralMessage;
23
+
3
24
  /**
4
25
  * Shipped English (en-US) default microcopy bundle.
5
26
  *
@@ -7,7 +28,7 @@
7
28
  * translate cleanly into any target locale. Keep this list small and real —
8
29
  * only add a key here when a component actually needs it.
9
30
  */
10
- export const DEFAULT_MESSAGES: Record<string, string> = {
31
+ export const DEFAULT_MESSAGES: Record<string, MessageValue> = {
11
32
  // ── Generic (shared across packages — reuse these before minting a new key) ──
12
33
  close: "Close",
13
34
  copy: "Copy",
@@ -20,6 +41,11 @@ export const DEFAULT_MESSAGES: Record<string, string> = {
20
41
  loading: "Loading…",
21
42
  more: "More",
22
43
  selectAll: "Select all",
44
+ // Cardinal-plural example (#19) — a shared "N item(s) selected" microcopy
45
+ // any multi-select surface can reuse via `t("itemsSelected", { count })`.
46
+ // Demonstrates the plural-form shape: pick the primitive over inventing a
47
+ // near-duplicate flat string per component.
48
+ itemsSelected: { one: "{count} item selected", other: "{count} items selected" },
23
49
 
24
50
  // ── @elabs-ai/components-ui ───────────────────────────────────────────────────────────────
25
51
  "ui.metricCard.loading": "Loading metric…",
@@ -43,6 +69,24 @@ export const DEFAULT_MESSAGES: Record<string, string> = {
43
69
  "ui.modelPicker.loadFailed": "Couldn't load the list",
44
70
  "ui.modelPicker.nothingYet": "Nothing to show yet",
45
71
  "ui.modelPicker.retry": "Retry",
72
+ // KeyboardShortcuts (#113). `emptyFiltered` keeps the user's own query in the
73
+ // sentence so the empty state says WHY it is empty, not merely that it is.
74
+ "ui.keyboardShortcuts.searchPlaceholder": "Search shortcuts…",
75
+ "ui.keyboardShortcuts.emptyTitle": "No shortcuts found",
76
+ "ui.keyboardShortcuts.emptyFiltered": "No shortcuts match “{query}”.",
77
+ "ui.keyboardShortcuts.empty": "No shortcuts to show.",
78
+ // WorkspacePicker (#111). It composes ModelPicker, so it carries its OWN
79
+ // label/search strings rather than inheriting the generic "Choose a target".
80
+ // `current` is appended into the row's meta so the in-force workspace reaches
81
+ // the option's accessible name as a WORD, not only a check glyph (1.4.1).
82
+ "ui.workspacePicker.label": "Choose a workspace",
83
+ "ui.workspacePicker.placeholder": "No workspace selected",
84
+ "ui.workspacePicker.recent": "Recent workspaces",
85
+ "ui.workspacePicker.searchPlaceholder": "Search workspaces…",
86
+ "ui.workspacePicker.current": "Current",
87
+ "ui.workspacePicker.pathLabel": "Workspace path",
88
+ "ui.workspacePicker.pathPlaceholder": "/path/to/project…",
89
+ "ui.workspacePicker.openPath": "Open",
46
90
  // ViewToolbar (#331). `removeFilter` deliberately WRAPS the visible chip text
47
91
  // so the accessible name contains it (WCAG 2.5.3 Label in Name).
48
92
  "ui.viewToolbar.about": "About this view",
@@ -81,6 +125,54 @@ export const DEFAULT_MESSAGES: Record<string, string> = {
81
125
  "ui.confirmDialog.cancel": "Cancel",
82
126
  "ui.advancedGroup.title": "Advanced",
83
127
  "ui.advancedGroup.changed": "{count} changed",
128
+ // SchemaForm — the spec-driven config-form renderer (issue #22).
129
+ "ui.schemaForm.selectPlaceholder": "Select…",
130
+ "ui.schemaForm.label": "Form",
131
+ "ui.schemaForm.submit": "Submit",
132
+ "ui.schemaForm.submitting": "Submitting…",
133
+ "ui.schemaForm.submitted": "Submitted",
134
+ // SchemaFormTestAction — a form/group-level "Test connection" affordance
135
+ // (issue #22 maintainer ruling, 2026-09-01), independent of field validity
136
+ // and never gating submit.
137
+ "ui.schemaForm.testAction.label": "Test connection",
138
+ "ui.schemaForm.testAction.pending": "Testing…",
139
+ "ui.schemaForm.testAction.success": "Connected",
140
+ "ui.schemaForm.testAction.failure": "Test failed",
141
+ // Pagination's ellipsis (sr-only — the visible glyph is decorative).
142
+ "ui.pagination.morePages": "More pages",
143
+ // Pagination's prev/next links — the ACCESSIBLE NAME (`aria-label`), distinct
144
+ // from the visible `previous`/`next` text below it: an `aria-label` overrides
145
+ // visible text content as the accessible name, so leaving these hardcoded in
146
+ // English meant a non-English `LocaleProvider` translated the visible label
147
+ // but a screen-reader user still heard English (#12/#53 review, P2).
148
+ "ui.pagination.previous": "Go to previous page",
149
+ "ui.pagination.next": "Go to next page",
150
+ // Sidebar's mobile Sheet title (sr-only header — the sheet itself has no
151
+ // visible chrome, so this is only ever read by assistive tech).
152
+ "ui.sidebar.title": "Sidebar",
153
+ // ContextRail's switcher count phrase (composed into the accessible name,
154
+ // e.g. "Sources 3 items") and its default empty-state copy.
155
+ "ui.contextRail.sectionCount": { one: "{count} item", other: "{count} items" },
156
+ "ui.contextRail.empty": "No sections",
157
+ // SideDock's resize handle — its sr-only label and its live width readout.
158
+ "ui.sideDock.resize": "Resize",
159
+ "ui.sideDock.widthValue": { one: "{size} pixel", other: "{size} pixels" },
160
+ // Tree's error row — shared by the virtualized and non-virtualized branches.
161
+ "ui.tree.failedToLoad": "Failed to load",
162
+ // ThemeSwitcher's "follow the OS" option, in both dropdown and toggle modes.
163
+ "ui.themeSwitcher.system": "System",
164
+ "ui.navNotifications.label": "Notifications",
165
+ "ui.teamSwitcher.label": "Teams",
166
+ "ui.teamSwitcher.addTeam": "Add team",
167
+ // Table's own scroll wrapper (#366). Rendered ONLY when the wrapper actually
168
+ // overflows, so it is never announced for a table that fits — see
169
+ // `packages/ui/src/components/table/table.tsx`. Deliberately a `ui.*`
170
+ // sibling of `data.table.scrollRegion` rather than a shared cross-package
171
+ // key: `Table` is a `@elabs-ai/components-ui` component and each package
172
+ // keeps its own namespace even where the English string coincides (compare
173
+ // `viewer.content` vs this), so a translator can phrase either
174
+ // independently and neither package depends on a string owned by another.
175
+ "ui.table.scrollRegion": "Table contents, scrollable",
84
176
 
85
177
  // ── @elabs-ai/components-data ─────────────────────────────────────────────────────────────
86
178
  // The scroll region's accessible name is rendered ONLY when the table actually
@@ -89,6 +181,57 @@ export const DEFAULT_MESSAGES: Record<string, string> = {
89
181
  // Fallback name for a row's activation control when the row's first cell holds
90
182
  // no primitive value to name it after (see `rowActionLabel`).
91
183
  "data.table.rowAction": "Activate row",
184
+ // createSelectionColumn (#11) — the header select-all checkbox and each row's
185
+ // own checkbox. Kept table-scoped (not the generic `selectAll` key) so a
186
+ // translator can phrase "rows" distinctly from other bulk-select surfaces.
187
+ "data.table.selectAllRows": "Select all rows",
188
+ // `selectRowNamed` names each row's checkbox from its own first data column
189
+ // (#11 I4) so screen-reader users hear "Select Alpha", not `selectRow`'s
190
+ // identical generic label repeated on every row; `selectRow` stays the
191
+ // fallback when no data column value is derivable.
192
+ "data.table.selectRowNamed": "Select {name}",
193
+ "data.table.selectRow": "Select row",
194
+ // Column resizing (#12) — the accessible name for the WAI-ARIA
195
+ // separator-as-slider resize handle at the end of a resizable header cell.
196
+ "data.table.resizeColumn": "Resize column, {name}",
197
+ // #51 — the handle's accessible VALUE, paired with aria-valuenow (which
198
+ // stays a plain number for AT/TanStack). A bare number reads as a
199
+ // dimensionless ordinal; the unit makes it a size. A `PluralMessage` (PR
200
+ // #81 review, "Format the announced resize value for the active locale") —
201
+ // the call site passes both `count` (the raw number, so a locale whose
202
+ // plural rules select something other than "other" for a given size is
203
+ // reachable) and `size` (pre-formatted via `formatNumber`, so a locale
204
+ // override renders locale-appropriate digits/grouping instead of a raw
205
+ // Latin-digit JS number).
206
+ "data.table.resizeColumnValue": { one: "{size} pixel", other: "{size} pixels" },
207
+ // Row drag-reorder (#13). `reorderHandle`/`reorderColumnHeader` name the
208
+ // grip control and its column; the four `reorder*` announcement keys back
209
+ // the aria-live region dnd-kit's `accessibility.announcements` renders on
210
+ // pickup/move/drop/cancel (WCAG 4.1.3) — `position`/`total` are 1-based so
211
+ // the announcement reads like "3 of 8", not a 0-based index.
212
+ "data.table.reorderColumnHeader": "Reorder",
213
+ "data.table.reorderHandle": "Reorder {name}",
214
+ "data.table.reorderPickedUp": "Picked up {name}.",
215
+ "data.table.reorderMoved": "{name} moved to position {position} of {total}.",
216
+ "data.table.reorderDropped": "{name} dropped at position {position} of {total}.",
217
+ "data.table.reorderCancelled":
218
+ "Reordering cancelled. {name} returned to position {position} of {total}.",
219
+ // #98: `@dnd-kit` renders two more AT-visible strings for this same
220
+ // feature that this repo's source never writes — the hidden keyboard
221
+ // usage instructions (wired to the grip via `aria-describedby`) and the
222
+ // activator's `aria-roledescription`. Both ship a hardcoded English
223
+ // default deep inside the library; localizing them means overriding them
224
+ // explicitly with these two keys. The English DEFAULT below is dnd-kit's
225
+ // own default text verbatim (`defaultScreenReaderInstructions.draggable`
226
+ // in `@dnd-kit/core`, and `useSortable`'s `roleDescription: 'sortable'` in
227
+ // `@dnd-kit/sortable`) — not a rewrite — so an English consumer with no
228
+ // `LocaleProvider` override sees byte-identical output to before this
229
+ // fix; only a `messages` override changes it.
230
+ "data.table.reorderInstructions":
231
+ "\n To pick up a draggable item, press the space bar.\n While dragging, use the arrow keys to move the item.\n Press space again to drop the item in its new position, or press escape to cancel.\n ",
232
+ "data.table.reorderRoleDescription": "sortable",
233
+ "data.facetFilter.clearFilters": "Clear filters",
234
+ "data.columnPicker.toggleColumns": "Toggle columns",
92
235
 
93
236
  // ── @elabs-ai/components-charts ───────────────────────────────────────────────────────────
94
237
  // Shared caption for any bare chart surface's layout-shaped skeleton
@@ -105,6 +248,12 @@ export const DEFAULT_MESSAGES: Record<string, string> = {
105
248
  // Announced when `copyValueOnActivate` puts a datapoint's exact value on the
106
249
  // clipboard — the recovery path for a compact axis label.
107
250
  "charts.datapoint.copied": "Exact value copied",
251
+ // ChartFrame's SVG/PNG export actions (RM-025). Each string is used twice —
252
+ // as the icon button's `aria-label` (its ONLY accessible name) and as the
253
+ // tooltip a sighted user reads — so one key serves both and they cannot
254
+ // drift apart in translation.
255
+ "charts.chartFrame.exportSvg": "Export as SVG",
256
+ "charts.chartFrame.exportPng": "Export as PNG",
108
257
 
109
258
  // ── @elabs-ai/components-ai ───────────────────────────────────────────────────────────────
110
259
  // Namespaced `ai.<area>.<key>` so package microcopy can't collide with the
@@ -120,25 +269,66 @@ export const DEFAULT_MESSAGES: Record<string, string> = {
120
269
  "ai.promptInput.uploadFiles": "Upload files",
121
270
  "ai.promptInput.submit": "Submit",
122
271
  "ai.promptInput.stop": "Stop",
272
+ "ai.agent.instructions": "Instructions",
273
+ "ai.agent.tools": "Tools",
123
274
  "ai.codeBlock.generating": "Generating…",
124
275
  "ai.composer.placeholder": "Ask me anything…",
125
- "ai.context.usage": "Model context usage",
276
+ "ai.tokenUsage.usage": "Model context usage",
277
+ "ai.tokenUsage.totalCost": "Total cost",
278
+ "ai.tokenUsage.input": "Input",
279
+ "ai.tokenUsage.output": "Output",
280
+ "ai.tokenUsage.reasoning": "Reasoning",
281
+ "ai.tokenUsage.cache": "Cache",
126
282
  "ai.contextPanel.back": "Back to context",
127
283
  "ai.contextPanel.toggle": "Toggle context panel",
284
+ // The mobile Sheet's sr-only title (#18) — distinct from `contextPanel.toggle`,
285
+ // which labels the button that opens/closes it.
286
+ "ai.contextPanel.title": "Context panel",
128
287
  "ai.environmentVariables.toggleVisibility": "Toggle value visibility",
129
288
  "ai.gallery.label": "Image gallery",
130
289
  "ai.gallery.expandImage": "Expand image",
131
290
  "ai.gallery.downloadImage": "Download image",
291
+ "ai.gallery.noImages": "No images",
292
+ "ai.gallery.noDetails": "No details",
293
+ // Shared by Tool's technical view and SchemaDisplay's request panel.
294
+ "ai.schemaDisplay.parameters": "Parameters",
295
+ "ai.schemaDisplay.response": "Response",
296
+ // ReasoningTrigger's default not-yet-timed message (before `duration` is known).
297
+ "ai.reasoning.thoughtDefault": "Thought for a few seconds",
298
+ "ai.stackTrace.empty": "No stack frames",
299
+ "ai.webPreview.noConsoleOutput": "No console output",
300
+ // PlanTrigger's icon-only collapse/expand control (sr-only).
301
+ "ai.plan.togglePlan": "Toggle plan",
132
302
  "ai.message.actions": "Message actions",
133
303
  "ai.message.editMessage": "Edit message",
134
304
  "ai.message.feedback": "Message feedback",
135
305
  "ai.message.previousBranch": "Previous branch",
136
306
  "ai.message.nextBranch": "Next branch",
307
+ "ai.messageCompare.error": "Error",
308
+ "ai.messageCompare.tabs": "Compare responses",
137
309
  "ai.persona.idle": "Assistant idle",
138
310
  "ai.persona.listening": "Assistant listening",
139
311
  "ai.persona.thinking": "Assistant thinking…",
140
312
  "ai.persona.speaking": "Assistant speaking",
141
313
  "ai.persona.asleep": "Assistant asleep",
314
+ // Optional-peer lazy engines (issue #33) — one shared pair every lazy
315
+ // boundary's missing-dependency panel resolves through, plus one
316
+ // `feature`-name key per boundary that renders it (mermaid, the terminal).
317
+ "ai.error.engineMissing": "{feature} unavailable",
318
+ "ai.error.engineMissingBody": "{feature} needs {packages} to be installed.",
319
+ "ai.error.retry": "Try again",
320
+ "ai.mermaid.feature": "Mermaid diagrams",
321
+ "ai.mermaid.renderError": "Diagram couldn't be drawn",
322
+ "ai.terminal.feature": "Interactive terminal",
323
+ "ai.terminal.renderError": "Terminal couldn't start",
324
+ "ai.audioPlayer.feature": "Audio player",
325
+ "ai.audioPlayer.renderError": "Audio player couldn't load",
326
+ // AudioVisualizer. Announced through a throttled `role="status"` region —
327
+ // the canvas itself is decorative (see .claude/rules/loading-states.md and
328
+ // issue #21's accessibility guidance).
329
+ "ai.audioVisualizer.idle": "Microphone not connected",
330
+ "ai.audioVisualizer.silent": "No input detected",
331
+ "ai.audioVisualizer.active": "Microphone active",
142
332
  "ai.selectionToolbar.label": "Selection actions",
143
333
  "ai.webPreview.urlPlaceholder": "Enter URL...",
144
334
  "ai.micSelector.searchPlaceholder": "Search microphones...",
@@ -148,6 +338,60 @@ export const DEFAULT_MESSAGES: Record<string, string> = {
148
338
  "ai.voiceSelector.playPreview": "Play preview",
149
339
  "ai.voiceSelector.pausePreview": "Pause preview",
150
340
 
341
+ // TurnStatus / SessionStatusBar (#105). `label` itself is caller-supplied
342
+ // ("Working…", "Editing files…") and rendered verbatim, so it needs no key
343
+ // here — only the component-owned completed-turn sentence and controls do.
344
+ "ai.turnStatus.completedIn": "Turn completed in {elapsed}",
345
+ "ai.turnStatus.completed": "Turn completed",
346
+ "ai.turnStatus.scrollToBottom": "Scroll to bottom",
347
+ "ai.sessionStatusBar.connections": "{connected} of {total} connections",
348
+ "ai.sessionStatusBar.connecting": "Connecting…",
349
+ // SessionHeader (#110). The section headings and the quick-action group name
350
+ // are component-owned chrome; the capability/what's-new item text itself is
351
+ // caller-supplied and rendered verbatim.
352
+ "ai.sessionHeader.capabilities": "Capabilities",
353
+ "ai.sessionHeader.whatsNew": "What’s new",
354
+ "ai.sessionHeader.quickActions": "Quick actions",
355
+ // PermissionModeSelect (#104). The in-force marker is a WORD inside the mode
356
+ // label, so the current mode survives greyscale and reaches the accessible name.
357
+ "ai.permissionModeSelect.current": "Current",
358
+ // AgentEvent (#109). The pass/fail WORD is the non-colour channel — the tone
359
+ // is redundant with it, so a check outcome survives greyscale (WCAG 1.4.1).
360
+ "ai.agentEvent.checkPassed": "Passed",
361
+ "ai.agentEvent.checkFailed": "Failed",
362
+ "ai.agentEvent.checksSummary": "{passed}/{ran} checks passed",
363
+ "ai.agentEvent.phaseBefore": "Before",
364
+ "ai.agentEvent.phaseAfter": "After",
365
+ "ai.agentEvent.phaseLifecycle": "Lifecycle",
366
+ // DiffView (#102). `addedLine` / `removedLine` are the sr-only polarity
367
+ // prefixes — the +/− glyph is aria-hidden, so these WORDS are the channel a
368
+ // greyscale or screen-reader user recovers the polarity from (WCAG 1.4.1).
369
+ // They intentionally end in a space so they read as a prefix to the code line.
370
+ "ai.diffView.addedLine": "Added: ",
371
+ "ai.diffView.removedLine": "Removed: ",
372
+ "ai.diffView.statsSummary": "{additions} additions, {deletions} deletions",
373
+ "ai.diffView.showMore": { one: "Show {count} more line", other: "Show {count} more lines" },
374
+ "ai.diffView.pagerLegend": "Arrow keys scroll, Page Up/Down page, Home/End jump",
375
+ "ai.diffView.regionLabel": "Code diff",
376
+ "ai.diffView.loading": "Loading diff…",
377
+ // ApprovalCard (#103). The SCOPE sentence is what makes an N-option
378
+ // permission prompt safe to answer: "Yes" and "Yes, and don't ask again" look
379
+ // alike and mean very different things. Each option links its scope sentence
380
+ // through aria-describedby, so the blast radius of a choice reaches assistive
381
+ // tech as words — never as a colour or a data-* attribute.
382
+ "ai.approvalCard.scopeOnceDescription": "Applies to this action only.",
383
+ "ai.approvalCard.scopeSessionDescription":
384
+ "Applies to actions like this for the rest of this session.",
385
+ "ai.approvalCard.scopeAlwaysDescription": "Applies to actions like this from now on.",
386
+ "ai.approvalCard.scopeDenyDescription": "Rejects this action.",
387
+ "ai.approvalCard.reasonLabel": "Reason",
388
+ "ai.approvalCard.reasonPlaceholder": "Add a reason (optional)…",
389
+ // PromptInputSlash (#106). `listLabel` goes to cmdk's own `label` prop, not
390
+ // `aria-label` — cmdk overwrites a consumer `aria-label` on CommandList and
391
+ // reads its accessible name off `label` instead.
392
+ "ai.promptInputSlash.listLabel": "Commands",
393
+ "ai.promptInputSlash.empty": "No matching commands.",
394
+
151
395
  // ── Streamdown chrome (third-party rendering surface) ──────────────────────
152
396
  // `streamdown` renders its OWN controls inside every streamed-markdown block
153
397
  // (code header, table menus, Mermaid toolbar, external-link interstitial) and
@@ -297,4 +541,259 @@ export const DEFAULT_MESSAGES: Record<string, string> = {
297
541
  "viewer.highlight.previous": "Previous passage",
298
542
  "viewer.highlight.next": "Next passage",
299
543
  "viewer.highlight.count": "Passage {index} of {total}",
544
+
545
+ // ── @elabs-ai/components-terminal ─────────────────────────────────────────
546
+ // Terminal (the read-only ANSI log): the ONE live-region announcement for
547
+ // its `isStreaming` rung. The blinking cursor block is the only other
548
+ // streaming signal and it is purely visual, so without this a screen-reader
549
+ // user attached to a running build or deploy log gets no indication that
550
+ // anything is still arriving.
551
+ "terminal.output.streaming": "Streaming output…",
552
+ // TerminalTranscriptRow (#117 T2): the gutter's meaning as words, so the
553
+ // "who spoke / what it printed / did it fail" grammar survives greyscale
554
+ // and reaches assistive tech, not only the glyph + colour.
555
+ "terminal.transcriptRow.user": "Prompt",
556
+ "terminal.transcriptRow.agent": "Agent",
557
+ "terminal.transcriptRow.output": "Output",
558
+ "terminal.transcriptRow.error": "Error",
559
+ "terminal.transcriptRow.exitCode": "Exit {code}",
560
+ // TerminalTodoList (#117 T5): the three-state checklist's announced word
561
+ // per row — the second, non-colour channel beside the ✔ / ◼ / ◻ glyph and
562
+ // the strikethrough/bold treatment. Wording verified 2026-09-01 against
563
+ // Claude Code v2.1.207's own upstream state words, parentheses included.
564
+ "terminal.todoList.done": "(completed)",
565
+ "terminal.todoList.active": "(in progress)",
566
+ "terminal.todoList.pending": "(pending)",
567
+ // TerminalEventLine (#117 T6): the lifecycle/hook event line. `outcome*` is
568
+ // the sr-only word beside the (always aria-hidden) StatusIcon glyph —
569
+ // never omitted for the default "ok" case, so "succeeded" is exactly as
570
+ // recoverable as "failed". `hooksTotal`/`hooksResult` are the terminal's
571
+ // own literal `[hooks: …]` bracket vocabulary, verified live 2026-09-01
572
+ // against Grok CLI v0.2.93; `hooksFailed` is the sr-only, count-aware
573
+ // "N hooks failed" that makes a partial hook failure (e.g. `3/1`) read as
574
+ // bad without relying on colour.
575
+ "terminal.eventLine.outcomeOk": "Succeeded",
576
+ "terminal.eventLine.outcomeBlocked": "Blocked",
577
+ "terminal.eventLine.outcomeFailed": "Failed",
578
+ "terminal.eventLine.phaseBefore": "Before",
579
+ "terminal.eventLine.phaseAfter": "After",
580
+ "terminal.eventLine.phaseLifecycle": "Lifecycle",
581
+ "terminal.eventLine.hooksTotal": "[hooks: {total}]",
582
+ "terminal.eventLine.hooksResult": "[hooks: {ran}/{passed}]",
583
+ "terminal.eventLine.hooksFailed": {
584
+ one: "{count} hook failed",
585
+ other: "{count} hooks failed",
586
+ },
587
+ // TerminalWorking (#117 T3): the in-turn footer's default label and its two
588
+ // icon-only controls' accessible names. No generic "stop"/"scroll to
589
+ // bottom" key exists yet (only the ai-namespaced `ai.promptInput.stop` /
590
+ // `ai.turnStatus.scrollToBottom`), so these are minted under the package's
591
+ // own namespace rather than borrowed cross-package.
592
+ "terminal.working.label": "Waiting for response…",
593
+ "terminal.working.stop": "Stop",
594
+ "terminal.working.scrollToBottom": "Scroll to bottom",
595
+ // TerminalStatusBar (#117 T4): the ambient chrome row's accessible name and
596
+ // the sr-only words beside its aria-hidden numerals/glyphs. Mirrors
597
+ // `ai.sessionStatusBar.connections`' wording under this package's own
598
+ // namespace (cannot import the `ai` key — sibling packages never import
599
+ // each other, `.claude/rules/terminal-components.md` § Reuse means
600
+ // promotion). `disconnected` and `stepsComplete` ("steps complete") are
601
+ // verified live 2026-09-01 against Grok CLI v0.2.93.
602
+ "terminal.statusBar.label": "Session status",
603
+ "terminal.statusBar.connecting": "Connecting…",
604
+ "terminal.statusBar.connections": "{connected} of {total} connections",
605
+ "terminal.statusBar.disconnected": "Disconnected",
606
+ "terminal.statusBar.context": "{used} of {limit} context used",
607
+ "terminal.statusBar.stepsComplete": "{current} of {total} steps complete",
608
+ // TerminalBanner (#117 T7): the launch card above an empty transcript.
609
+ // Mirrors `ai.sessionHeader.*`'s wording under this package's own
610
+ // namespace — sibling packages never import each other's locale keys
611
+ // (`.claude/rules/terminal-components.md` § Reuse means promotion).
612
+ "terminal.banner.capabilities": "Capabilities",
613
+ "terminal.banner.whatsNew": "What’s new",
614
+ "terminal.banner.quickActions": "Quick actions",
615
+ // TerminalToolCall (#117 T8): the tool-call row's status word (announced
616
+ // beside the glyph via TerminalRow's gutterLabel — never colour alone),
617
+ // the result row's swapped heading (mirrors ToolOutput's own "Result"/
618
+ // "Error" heading swap, `@elabs-ai/components-ai`), and the expand
619
+ // trigger's accessible name. Unlike upstream's inert `"(ctrl+o to
620
+ // expand)"` hint text, this IS a real focusable control's label — no CLI
621
+ // chord required.
622
+ "terminal.toolCall.succeeded": "Succeeded",
623
+ "terminal.toolCall.failed": "Failed",
624
+ "terminal.toolCall.running": "Running",
625
+ "terminal.toolCall.result": "Result",
626
+ "terminal.toolCall.error": "Error",
627
+ "terminal.toolCall.expandHint": "Show details",
628
+ // TerminalDiffHunk (#117 T9): the header's fixed "Update ({file})" phrase,
629
+ // and the collapsed-context-run disclosure's count-aware label. Mirrors
630
+ // `ai.diffView.showMore`'s wording under this package's own namespace —
631
+ // sibling packages never import each other's locale keys
632
+ // (`.claude/rules/terminal-components.md` § Reuse means promotion). The
633
+ // per-line `add`/`del` polarity words are NOT duplicated here: they ride
634
+ // the shared `diffLineAccessibleLabel()` keys (`ai.diffView.addedLine` /
635
+ // `ai.diffView.removedLine`) above, the actual promoted channel.
636
+ "terminal.diffHunk.header": "Update ({file})",
637
+ "terminal.diffHunk.showMore": {
638
+ one: "Show {count} more line",
639
+ other: "Show {count} more lines",
640
+ },
641
+ // TerminalPermission (#117 T10): the per-call scoped approval prompt's
642
+ // default title/question and its three scoped option labels, verified
643
+ // live 2026-09-01 against Claude Code v2.1.207. The third option is the
644
+ // vendor-free `deny` scope: upstream names its own product in this label,
645
+ // ours reads "the agent" instead (#117 acceptance criterion). The reason
646
+ // field reuses `ai.approvalCard.reasonLabel`/`reasonPlaceholder` and the
647
+ // option descriptions reuse `APPROVAL_SCOPE_DESCRIPTION_KEYS` — both
648
+ // already generic, so nothing new is minted for either.
649
+ "terminal.permission.title": "Bash command",
650
+ "terminal.permission.question": "Do you want to proceed?",
651
+ "terminal.permission.optionOnce": "Yes",
652
+ "terminal.permission.optionSession": "Yes, and don’t ask again this session",
653
+ "terminal.permission.optionDeny": "No, and tell the agent what to do differently",
654
+ // TerminalComposer (#117 T11): the prompt composer's placeholder, its
655
+ // merged submit/stop affordance's accessible name (mirrors
656
+ // `ai.promptInput.submit`/`stop` under this package's own namespace —
657
+ // sibling packages never import each other's locale keys,
658
+ // `.claude/rules/terminal-components.md` § Reuse means promotion), the
659
+ // effort scale's default accessible name, and the shortcut-hint row's
660
+ // three default words.
661
+ "terminal.composer.placeholder": "Type your next instruction…",
662
+ "terminal.composer.submit": "Send",
663
+ "terminal.composer.stop": "Stop",
664
+ "terminal.composer.effort": "Effort",
665
+ "terminal.composer.shortcutSend": "send",
666
+ "terminal.composer.shortcutNewline": "newline",
667
+ "terminal.composer.shortcutCancel": "cancel",
668
+ // TerminalSlashMenu (#117 T12): the `/`-command palette's listbox
669
+ // accessible name (mirrors `ai.promptInputSlash.listLabel`'s wording under
670
+ // this package's own namespace — sibling packages never import each
671
+ // other's locale keys, `.claude/rules/terminal-components.md` § Reuse
672
+ // means promotion). The empty state reuses the generic `noResults` key
673
+ // rather than minting a second one.
674
+ "terminal.slashMenu.listLabel": "Commands",
675
+ // ── @elabs-ai/components-process ──────────────────────────────────────────────────────────
676
+ // ProcessMap (RM-051). Namespaced `process.<area>.<key>` like every other
677
+ // package; a sibling package's keys are never reused across the boundary.
678
+ // The table twin's column headers are here because that table IS the
679
+ // accessible reading of the canvas — an untranslated header would leave a
680
+ // screen-reader user with numbers and no measure name.
681
+ "process.map.loading": "Discovering the process…",
682
+ "process.map.empty": "No activities to map",
683
+ "process.map.emptyBody":
684
+ "This log has no events, or the abstraction hid every activity. Widen the abstraction or load a different log.",
685
+ "process.map.label": "Process map",
686
+ "process.map.filter": "Filter…",
687
+ "process.map.activityCaption": "Activities — {metric} per activity",
688
+ "process.map.transitionCaption": "Transitions — {metric} per directly-follows pair",
689
+ "process.map.columnActivity": "Activity",
690
+ "process.map.columnRole": "Role",
691
+ "process.map.columnRework": "Rework",
692
+ "process.map.columnFrom": "From",
693
+ "process.map.columnTo": "To",
694
+ "process.map.columnShape": "Shape",
695
+ // State column (#373) — the table twin's own channel for the selection/filter state the
696
+ // canvas already says through real text in its accessible names; the twin used to carry
697
+ // it only as a `data-*` attribute, which reaches no user.
698
+ "process.map.columnState": "State",
699
+ // The State column's own CELL values (#413 review, PRRT_kwDOT6D7ts6gJX2C). #373 localized
700
+ // the column HEADER but left every cell printing the literal English word — since that cell
701
+ // is also the row's screen-reader channel for its selection/filter state
702
+ // (`selectionStateLabel` below is the un-localized canonical word `process-map.tsx` maps
703
+ // through these keys at the render site), a non-English `LocaleProvider` left both the
704
+ // visible AND the accessible content untranslated. `"associated"` (the ordinary case) has
705
+ // no key: it prints nothing, by design.
706
+ "process.map.stateSelected": "Selected",
707
+ "process.map.stateExcluded": "Excluded",
708
+ // Filter-intent menu item ACCESSIBLE names (#346). A transition's menu offers the same
709
+ // four intents once per endpoint (eight items total); the visible text
710
+ // (`PROCESS_FILTER_INTENT_LABELS` in map-model.ts) stays unsuffixed and compact, so these
711
+ // compose the activity into the item's real accessible name instead — the channel a
712
+ // screen-reader user actually hears, and the one the visible-only text left ambiguous.
713
+ "process.map.filterIntentWith": "Keep cases containing {activity}",
714
+ "process.map.filterIntentWithout": "Keep cases without {activity}",
715
+ "process.map.filterIntentStartsWith": "Keep cases starting with {activity}",
716
+ "process.map.filterIntentEndsWith": "Keep cases ending with {activity}",
717
+ // The map's own live-region summary (#375) — one polite `role="status"` announces what
718
+ // the active selection/filter just changed, mirroring `AbstractionControls`' hidden-count
719
+ // summary below: two independently-pluralized fragments, composed at the call site
720
+ // (`process-map.tsx`) rather than merged into one message, for the same reason.
721
+ "process.map.excludedActivities": {
722
+ one: "{count} of {total} activity excluded",
723
+ other: "{count} of {total} activities excluded",
724
+ },
725
+ "process.map.excludedTransitions": {
726
+ one: "{count} of {total} transition excluded",
727
+ other: "{count} of {total} transitions excluded",
728
+ },
729
+ // The live region's AFFECTED-SET fragments (#413 review, PRRT_kwDOT6D7ts6gJX2I). Counts
730
+ // alone are not enough: two selections/filters that exclude DIFFERENT elements but land on
731
+ // the SAME counts produce byte-for-byte identical text, so the polite region — which
732
+ // announces content CHANGES, not model changes — stays silent even though the map
733
+ // re-inked. Naming which elements are excluded makes the string track the actual set, not
734
+ // only its size. Composed at the call site alongside the two counts above, and omitted
735
+ // entirely when nothing of that kind is excluded (so the common case stays as short as
736
+ // before this fix).
737
+ "process.map.excludedActivityNames": "excluding {names}",
738
+ "process.map.excludedTransitionNames": "excluding {names}",
739
+ // AbstractionControls (RM-052, issue #227). The two sliders' own labels,
740
+ // the invert switch, the "Auto" heuristic button, and the hidden-count
741
+ // status line. `hiddenActivities`/`hiddenPaths` are separate `PluralMessage`s
742
+ // (not one combined key) because `t()` selects its plural category from a
743
+ // SINGLE `count` var — see `resolveMessage` in `locale-provider.tsx` — so
744
+ // the two independently-pluralizable counts are composed as two `t()` calls
745
+ // joined at the call site (`abstraction-controls.tsx`), not one key with two
746
+ // numbers.
747
+ "process.abstractionControls.label": "Abstraction",
748
+ "process.abstractionControls.activities": "Activities",
749
+ "process.abstractionControls.paths": "Paths",
750
+ "process.abstractionControls.invert": "Invert",
751
+ "process.abstractionControls.auto": "Auto",
752
+ "process.abstractionControls.hiddenActivities": {
753
+ one: "{count} activity hidden",
754
+ other: "{count} activities hidden",
755
+ },
756
+ "process.abstractionControls.hiddenPaths": {
757
+ one: "{count} path hidden",
758
+ other: "{count} paths hidden",
759
+ },
760
+ // MetricLayerSwitch (RM-052, issue #227). The Frequency/Performance/Rework
761
+ // toggle group, the node/edge metric selects' field labels, and the lock
762
+ // toggle's two accessible-name states. The per-VALUE metric labels
763
+ // ("Occurrences", "Share of transitions", …) are deliberately NOT
764
+ // duplicated here — they are read straight from `nodeMetricLabel`/
765
+ // `edgeMetricLabel` (`process-map/map-model.ts`), which already resolve
766
+ // each `ProcessMetric` to its correct, audience-specific text; see
767
+ // `metric-layer-switch.tsx`'s module docblock.
768
+ "process.metricLayerSwitch.label": "Metric",
769
+ "process.metricLayerSwitch.layer": "Metric layer",
770
+ "process.metricLayerSwitch.frequency": "Frequency",
771
+ "process.metricLayerSwitch.performance": "Performance",
772
+ "process.metricLayerSwitch.rework": "Rework",
773
+ "process.metricLayerSwitch.node": "Activity",
774
+ "process.metricLayerSwitch.edge": "Transition",
775
+ "process.metricLayerSwitch.lockOn": "Unlock activity and transition metrics",
776
+ "process.metricLayerSwitch.lockOff": "Lock activity and transition metrics together",
777
+ // ProcessKpiStrip (RM-052, issue #227). Six tiles' labels, plus the
778
+ // conformance tile's genuine "not available" state (no conformance model
779
+ // has run yet) — a real state, not a fabricated 0%.
780
+ "process.kpiStrip.cases": "Cases",
781
+ "process.kpiStrip.events": "Events",
782
+ "process.kpiStrip.variants": "Variants",
783
+ "process.kpiStrip.medianThroughput": "Median throughput",
784
+ "process.kpiStrip.reworkRate": "Rework rate",
785
+ "process.kpiStrip.conformance": "Conformance",
786
+ "process.kpiStrip.conformanceUnavailable": "Not available",
787
+ "process.kpiStrip.conformanceUnavailableHint": "Run conformance checking to see this metric.",
788
+ // Sparkline text alternative (#359). `Sparkline`'s own default alt text is more
789
+ // informative than the tile's own label — passing the label straight through
790
+ // overrode it and left a screen-reader user hearing the tile's name twice with no
791
+ // sense of the trend. `direction` is itself a resolved `t()` value (trendRising/
792
+ // Falling/Steady), composed into this template the same way `hiddenSummary`
793
+ // (abstraction-controls.tsx) composes two already-localized fragments.
794
+ "process.kpiStrip.trendAlt":
795
+ "{subject}, {periods}-period trend, {direction} from {first} to {last}",
796
+ "process.kpiStrip.trendRising": "rising",
797
+ "process.kpiStrip.trendFalling": "falling",
798
+ "process.kpiStrip.trendSteady": "steady",
300
799
  };
@@ -4,7 +4,7 @@ import { Button } from "../button";
4
4
  import { MatchHighlight, queryToRanges } from "./match-highlight";
5
5
 
6
6
  const meta = {
7
- title: "Typography/MatchHighlight",
7
+ title: "Display/MatchHighlight",
8
8
  component: MatchHighlight,
9
9
  tags: ["autodocs"],
10
10
  parameters: {
@@ -25,6 +25,14 @@ import type { MentionOption, MentionValue } from "./mention-value";
25
25
  * lock that only holds when it runs alone is not a lock at all in CI — so it
26
26
  * gets its own story file, which the runner loads as its own test file.
27
27
  *
28
+ * It shares the `Forms/MentionInput` TITLE with the main story file so it reads
29
+ * as one sidebar entry rather than a sub-group of its own (RM-005). That is a
30
+ * sidebar concern only: `@storybook/addon-vitest` turns each story FILE into its
31
+ * own test file, so the isolation above survives the shared title. Do NOT
32
+ * "finish the job" by moving these stories into `mention-input.stories.tsx` —
33
+ * that is the exact co-residency the audit measured as green-with-the-bug (ADR
34
+ * 0023 §6).
35
+ *
28
36
  * ### What it pins
29
37
  *
30
38
  * Every box-affecting property of the field is nailed to absolute pixels, so
@@ -39,7 +47,7 @@ import type { MentionOption, MentionValue } from "./mention-value";
39
47
  * the test rather than argued for in a comment.
40
48
  */
41
49
  const meta = {
42
- title: "Forms/MentionInput/Mirror re-measure",
50
+ title: "Forms/MentionInput",
43
51
  component: MentionInput,
44
52
  parameters: {
45
53
  docs: {