@tai42/studio-sdk 25.0.1 → 27.0.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 (262) hide show
  1. package/README.md +143 -26
  2. package/dist/components/action-menu.d.ts +30 -0
  3. package/dist/components/action-menu.d.ts.map +1 -0
  4. package/dist/components/action-menu.js +101 -0
  5. package/dist/components/action-menu.js.map +1 -0
  6. package/dist/components/agent-picker.d.ts +22 -0
  7. package/dist/components/agent-picker.d.ts.map +1 -0
  8. package/dist/components/agent-picker.js +17 -0
  9. package/dist/components/agent-picker.js.map +1 -0
  10. package/dist/components/channel-picker.d.ts +27 -0
  11. package/dist/components/channel-picker.d.ts.map +1 -0
  12. package/dist/components/channel-picker.js +26 -0
  13. package/dist/components/channel-picker.js.map +1 -0
  14. package/dist/components/components.css +226 -8
  15. package/dist/components/confirm-dialog.d.ts +3 -1
  16. package/dist/components/confirm-dialog.d.ts.map +1 -1
  17. package/dist/components/confirm-dialog.js +2 -2
  18. package/dist/components/confirm-dialog.js.map +1 -1
  19. package/dist/components/execution-key-picker.d.ts +26 -10
  20. package/dist/components/execution-key-picker.d.ts.map +1 -1
  21. package/dist/components/execution-key-picker.js +134 -15
  22. package/dist/components/execution-key-picker.js.map +1 -1
  23. package/dist/components/extension-combo-builder.d.ts +7 -7
  24. package/dist/components/extension-combo-builder.d.ts.map +1 -1
  25. package/dist/components/extension-combo-builder.js +42 -16
  26. package/dist/components/extension-combo-builder.js.map +1 -1
  27. package/dist/components/extension-combo-parts.d.ts +13 -11
  28. package/dist/components/extension-combo-parts.d.ts.map +1 -1
  29. package/dist/components/extension-combo-parts.js +10 -19
  30. package/dist/components/extension-combo-parts.js.map +1 -1
  31. package/dist/components/extension-config-editor.d.ts +33 -0
  32. package/dist/components/extension-config-editor.d.ts.map +1 -0
  33. package/dist/components/extension-config-editor.js +190 -0
  34. package/dist/components/extension-config-editor.js.map +1 -0
  35. package/dist/components/extension-grouping.d.ts +4 -41
  36. package/dist/components/extension-grouping.d.ts.map +1 -1
  37. package/dist/components/extension-grouping.js +1 -48
  38. package/dist/components/extension-grouping.js.map +1 -1
  39. package/dist/components/extension-picker.d.ts +4 -3
  40. package/dist/components/extension-picker.d.ts.map +1 -1
  41. package/dist/components/extension-picker.js +6 -6
  42. package/dist/components/extension-picker.js.map +1 -1
  43. package/dist/components/fleet-report.d.ts +8 -3
  44. package/dist/components/fleet-report.d.ts.map +1 -1
  45. package/dist/components/fleet-report.js +12 -2
  46. package/dist/components/fleet-report.js.map +1 -1
  47. package/dist/components/holder-kinds.d.ts +18 -0
  48. package/dist/components/holder-kinds.d.ts.map +1 -0
  49. package/dist/components/holder-kinds.js +41 -0
  50. package/dist/components/holder-kinds.js.map +1 -0
  51. package/dist/components/holder-list.d.ts +58 -0
  52. package/dist/components/holder-list.d.ts.map +1 -0
  53. package/dist/components/holder-list.js +77 -0
  54. package/dist/components/holder-list.js.map +1 -0
  55. package/dist/components/icons/index.d.ts +1 -1
  56. package/dist/components/icons/index.d.ts.map +1 -1
  57. package/dist/components/icons/index.js +1 -1
  58. package/dist/components/icons/index.js.map +1 -1
  59. package/dist/components/icons/nav-map.d.ts.map +1 -1
  60. package/dist/components/icons/nav-map.js +1 -3
  61. package/dist/components/icons/nav-map.js.map +1 -1
  62. package/dist/components/icons/nav-marks.d.ts +0 -4
  63. package/dist/components/icons/nav-marks.d.ts.map +1 -1
  64. package/dist/components/icons/nav-marks.js +0 -4
  65. package/dist/components/icons/nav-marks.js.map +1 -1
  66. package/dist/components/named-list-picker.d.ts +39 -0
  67. package/dist/components/named-list-picker.d.ts.map +1 -0
  68. package/dist/components/named-list-picker.js +61 -0
  69. package/dist/components/named-list-picker.js.map +1 -0
  70. package/dist/components/named-list-test-utils.d.ts +23 -0
  71. package/dist/components/named-list-test-utils.d.ts.map +1 -0
  72. package/dist/components/named-list-test-utils.js +33 -0
  73. package/dist/components/named-list-test-utils.js.map +1 -0
  74. package/dist/components/recycle-roll.d.ts +23 -0
  75. package/dist/components/recycle-roll.d.ts.map +1 -0
  76. package/dist/components/recycle-roll.js +47 -0
  77. package/dist/components/recycle-roll.js.map +1 -0
  78. package/dist/components/stepper.d.ts +28 -0
  79. package/dist/components/stepper.d.ts.map +1 -0
  80. package/dist/components/stepper.js +33 -0
  81. package/dist/components/stepper.js.map +1 -0
  82. package/dist/components/subject-section.d.ts +2 -1
  83. package/dist/components/subject-section.d.ts.map +1 -1
  84. package/dist/components/subject-section.js +10 -5
  85. package/dist/components/subject-section.js.map +1 -1
  86. package/dist/components/tokens.css +31 -41
  87. package/dist/components/tokens.d.ts +3 -4
  88. package/dist/components/tokens.d.ts.map +1 -1
  89. package/dist/components/tokens.js +5 -13
  90. package/dist/components/tokens.js.map +1 -1
  91. package/dist/components/tool-picker-parts.d.ts +5 -1
  92. package/dist/components/tool-picker-parts.d.ts.map +1 -1
  93. package/dist/components/tool-picker-parts.js +8 -6
  94. package/dist/components/tool-picker-parts.js.map +1 -1
  95. package/dist/components/tool-picker.d.ts +77 -2
  96. package/dist/components/tool-picker.d.ts.map +1 -1
  97. package/dist/components/tool-picker.js +87 -29
  98. package/dist/components/tool-picker.js.map +1 -1
  99. package/dist/components/version-history-panel.d.ts +7 -1
  100. package/dist/components/version-history-panel.d.ts.map +1 -1
  101. package/dist/components/version-history-panel.js +2 -2
  102. package/dist/components/version-history-panel.js.map +1 -1
  103. package/dist/components/version-history-rows.d.ts +4 -2
  104. package/dist/components/version-history-rows.d.ts.map +1 -1
  105. package/dist/components/version-history-rows.js +14 -2
  106. package/dist/components/version-history-rows.js.map +1 -1
  107. package/dist/errors.d.ts +5 -0
  108. package/dist/errors.d.ts.map +1 -1
  109. package/dist/errors.js +7 -0
  110. package/dist/errors.js.map +1 -1
  111. package/dist/hooks/useApi.d.ts +524 -197
  112. package/dist/hooks/useApi.d.ts.map +1 -1
  113. package/dist/hooks/useCapabilities.d.ts +43 -10
  114. package/dist/hooks/useCapabilities.d.ts.map +1 -1
  115. package/dist/hooks/useCapabilities.js +39 -10
  116. package/dist/hooks/useCapabilities.js.map +1 -1
  117. package/dist/index.d.ts +25 -12
  118. package/dist/index.d.ts.map +1 -1
  119. package/dist/index.js +14 -6
  120. package/dist/index.js.map +1 -1
  121. package/dist/marketplace-install/env-var-fields.d.ts +17 -0
  122. package/dist/marketplace-install/env-var-fields.d.ts.map +1 -0
  123. package/dist/marketplace-install/env-var-fields.js +22 -0
  124. package/dist/marketplace-install/env-var-fields.js.map +1 -0
  125. package/dist/marketplace-install/index.d.ts +18 -0
  126. package/dist/marketplace-install/index.d.ts.map +1 -0
  127. package/dist/marketplace-install/index.js +8 -0
  128. package/dist/marketplace-install/index.js.map +1 -0
  129. package/dist/marketplace-install/install-env.d.ts +21 -0
  130. package/dist/marketplace-install/install-env.d.ts.map +1 -0
  131. package/dist/marketplace-install/install-env.js +22 -0
  132. package/dist/marketplace-install/install-env.js.map +1 -0
  133. package/dist/marketplace-install/install-mount.d.ts +42 -0
  134. package/dist/marketplace-install/install-mount.d.ts.map +1 -0
  135. package/dist/marketplace-install/install-mount.js +62 -0
  136. package/dist/marketplace-install/install-mount.js.map +1 -0
  137. package/dist/marketplace-install/install-report.d.ts +14 -0
  138. package/dist/marketplace-install/install-report.d.ts.map +1 -0
  139. package/dist/marketplace-install/install-report.js +19 -0
  140. package/dist/marketplace-install/install-report.js.map +1 -0
  141. package/dist/marketplace-install/install-shell.d.ts +34 -0
  142. package/dist/marketplace-install/install-shell.d.ts.map +1 -0
  143. package/dist/marketplace-install/install-shell.js +32 -0
  144. package/dist/marketplace-install/install-shell.js.map +1 -0
  145. package/dist/marketplace-install/mount-install.d.ts +41 -0
  146. package/dist/marketplace-install/mount-install.d.ts.map +1 -0
  147. package/dist/marketplace-install/mount-install.js +90 -0
  148. package/dist/marketplace-install/mount-install.js.map +1 -0
  149. package/dist/marketplace-install/plugin-detail.d.ts +17 -0
  150. package/dist/marketplace-install/plugin-detail.d.ts.map +1 -0
  151. package/dist/marketplace-install/plugin-detail.js +41 -0
  152. package/dist/marketplace-install/plugin-detail.js.map +1 -0
  153. package/dist/marketplace-install/plugin-install.d.ts +27 -0
  154. package/dist/marketplace-install/plugin-install.d.ts.map +1 -0
  155. package/dist/marketplace-install/plugin-install.js +89 -0
  156. package/dist/marketplace-install/plugin-install.js.map +1 -0
  157. package/dist/marketplace-install/route-items.d.ts +14 -0
  158. package/dist/marketplace-install/route-items.d.ts.map +1 -0
  159. package/dist/marketplace-install/route-items.js +11 -0
  160. package/dist/marketplace-install/route-items.js.map +1 -0
  161. package/dist/marketplace-install/use-debounced-value.d.ts +6 -0
  162. package/dist/marketplace-install/use-debounced-value.d.ts.map +1 -0
  163. package/dist/marketplace-install/use-debounced-value.js +18 -0
  164. package/dist/marketplace-install/use-debounced-value.js.map +1 -0
  165. package/dist/marketplace-install/use-install-action.d.ts +26 -0
  166. package/dist/marketplace-install/use-install-action.d.ts.map +1 -0
  167. package/dist/marketplace-install/use-install-action.js +35 -0
  168. package/dist/marketplace-install/use-install-action.js.map +1 -0
  169. package/dist/marketplace-install/use-install-preview.d.ts +33 -0
  170. package/dist/marketplace-install/use-install-preview.d.ts.map +1 -0
  171. package/dist/marketplace-install/use-install-preview.js +44 -0
  172. package/dist/marketplace-install/use-install-preview.js.map +1 -0
  173. package/dist/marketplace-install/use-mount-install.d.ts +80 -0
  174. package/dist/marketplace-install/use-mount-install.d.ts.map +1 -0
  175. package/dist/marketplace-install/use-mount-install.js +75 -0
  176. package/dist/marketplace-install/use-mount-install.js.map +1 -0
  177. package/dist/navigation/index.d.ts +1 -1
  178. package/dist/navigation/index.d.ts.map +1 -1
  179. package/dist/navigation/index.js.map +1 -1
  180. package/dist/navigation/types.d.ts +23 -7
  181. package/dist/navigation/types.d.ts.map +1 -1
  182. package/dist/plugin/registry.d.ts +0 -8
  183. package/dist/plugin/registry.d.ts.map +1 -1
  184. package/dist/plugin/registry.js +25 -1
  185. package/dist/plugin/registry.js.map +1 -1
  186. package/dist/plugin/types.d.ts +36 -12
  187. package/dist/plugin/types.d.ts.map +1 -1
  188. package/dist/plugin/version.d.ts +6 -4
  189. package/dist/plugin/version.d.ts.map +1 -1
  190. package/dist/plugin/version.js +6 -4
  191. package/dist/plugin/version.js.map +1 -1
  192. package/dist/query-keys.d.ts +45 -4
  193. package/dist/query-keys.d.ts.map +1 -1
  194. package/dist/query-keys.js +52 -4
  195. package/dist/query-keys.js.map +1 -1
  196. package/dist/schema-form/json-schema-meta.d.ts +3 -0
  197. package/dist/schema-form/json-schema-meta.d.ts.map +1 -0
  198. package/dist/schema-form/json-schema-meta.js +3 -0
  199. package/dist/schema-form/json-schema-meta.js.map +1 -0
  200. package/dist/testing.d.ts +2 -0
  201. package/dist/testing.d.ts.map +1 -1
  202. package/dist/testing.js +2 -0
  203. package/dist/testing.js.map +1 -1
  204. package/dist/token-rules.d.ts +26 -0
  205. package/dist/token-rules.d.ts.map +1 -0
  206. package/dist/token-rules.js +64 -0
  207. package/dist/token-rules.js.map +1 -0
  208. package/package.json +2 -2
  209. package/src/components/action-menu.tsx +160 -0
  210. package/src/components/agent-picker.tsx +53 -0
  211. package/src/components/channel-picker.tsx +68 -0
  212. package/src/components/components.css +226 -8
  213. package/src/components/confirm-dialog.tsx +4 -1
  214. package/src/components/execution-key-picker.tsx +252 -36
  215. package/src/components/extension-combo-builder.tsx +61 -29
  216. package/src/components/extension-combo-parts.tsx +28 -38
  217. package/src/components/extension-config-editor.tsx +307 -0
  218. package/src/components/extension-grouping.ts +4 -73
  219. package/src/components/extension-picker.tsx +13 -13
  220. package/src/components/fleet-report.tsx +38 -4
  221. package/src/components/holder-kinds.ts +60 -0
  222. package/src/components/holder-list.tsx +163 -0
  223. package/src/components/icons/index.ts +0 -2
  224. package/src/components/icons/nav-map.ts +0 -4
  225. package/src/components/icons/nav-marks.tsx +0 -15
  226. package/src/components/named-list-picker.tsx +182 -0
  227. package/src/components/named-list-test-utils.tsx +47 -0
  228. package/src/components/recycle-roll.tsx +120 -0
  229. package/src/components/stepper.tsx +111 -0
  230. package/src/components/subject-section.tsx +10 -5
  231. package/src/components/tokens.css +31 -41
  232. package/src/components/tokens.ts +5 -13
  233. package/src/components/tool-picker-parts.tsx +17 -6
  234. package/src/components/tool-picker.tsx +215 -41
  235. package/src/components/version-history-panel.tsx +8 -0
  236. package/src/components/version-history-rows.tsx +14 -2
  237. package/src/errors.ts +10 -0
  238. package/src/hooks/useCapabilities.ts +59 -10
  239. package/src/index.ts +54 -14
  240. package/src/marketplace-install/env-var-fields.tsx +59 -0
  241. package/src/marketplace-install/index.ts +17 -0
  242. package/src/marketplace-install/install-env.ts +39 -0
  243. package/src/marketplace-install/install-mount.ts +96 -0
  244. package/src/marketplace-install/install-report.tsx +54 -0
  245. package/src/marketplace-install/install-shell.tsx +99 -0
  246. package/src/marketplace-install/mount-install.tsx +290 -0
  247. package/src/marketplace-install/plugin-detail.ts +60 -0
  248. package/src/marketplace-install/plugin-install.tsx +221 -0
  249. package/src/marketplace-install/route-items.ts +20 -0
  250. package/src/marketplace-install/use-debounced-value.ts +18 -0
  251. package/src/marketplace-install/use-install-action.ts +60 -0
  252. package/src/marketplace-install/use-install-preview.ts +74 -0
  253. package/src/marketplace-install/use-mount-install.ts +116 -0
  254. package/src/navigation/index.ts +1 -0
  255. package/src/navigation/types.ts +27 -7
  256. package/src/plugin/registry.ts +19 -1
  257. package/src/plugin/types.ts +40 -12
  258. package/src/plugin/version.ts +6 -4
  259. package/src/query-keys.ts +71 -4
  260. package/src/schema-form/json-schema-meta.ts +2 -0
  261. package/src/testing.ts +2 -0
  262. package/src/token-rules.ts +66 -0
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Source rules over the design-token names, shared by the repo's token-usage gate and
3
+ * by any host that scans its own sources with the same rule (exported from
4
+ * `@tai42/studio-sdk/testing`). Each rule reads one source text and returns its
5
+ * offences; which files to read is the caller's choice, the rule never varies.
6
+ */
7
+ /**
8
+ * The property a declaration's value belongs to: the last `name:` before the
9
+ * value, with no statement boundary in between. Written as a backwards search
10
+ * rather than a forward `property: value` match so that a value containing its
11
+ * own commas and parentheses — `linear-gradient(90deg, …)`, or the next entry of
12
+ * a JSX style object — cannot make the scan attribute the value to the wrong
13
+ * property or miss it entirely.
14
+ */
15
+ export function governingProperty(source, valueIndex) {
16
+ const match = /([-A-Za-z][\w-]*)\s*:\s*[^;{}:]*$/.exec(source.slice(0, valueIndex));
17
+ return match?.[1];
18
+ }
19
+ /** Every reference to a destructive role token, in a stylesheet or a JSX inline style. */
20
+ const DESTRUCTIVE_REFERENCE = /var\(\s*--tai-color-(?:on-)?destructive/g;
21
+ /** The one rule whose label sits on the destructive fill. */
22
+ const DESTRUCTIVE_BUTTON_SELECTOR = /^\.tai-btn-danger(?![\w-])/;
23
+ /**
24
+ * Whether the declaration at `valueIndex` sits inside a CSS rule whose every
25
+ * selector is the destructive button. The selector is the text between the
26
+ * previous statement boundary and the innermost open brace, with comments removed.
27
+ */
28
+ function insideDestructiveButtonRule(source, valueIndex) {
29
+ const before = source.slice(0, valueIndex);
30
+ const open = before.lastIndexOf('{');
31
+ if (open === -1 || before.lastIndexOf('}') > open)
32
+ return false;
33
+ const head = before.slice(0, open);
34
+ const start = Math.max(head.lastIndexOf('}'), head.lastIndexOf('{'), head.lastIndexOf(';')) + 1;
35
+ const selectors = head
36
+ .slice(start)
37
+ .replace(/\/\*[\s\S]*?\*\//g, '')
38
+ .split(',')
39
+ .map((selector) => selector.trim());
40
+ return selectors.every((selector) => DESTRUCTIVE_BUTTON_SELECTOR.test(selector));
41
+ }
42
+ /**
43
+ * The properties in `source` that paint text with a destructive role token, in the
44
+ * order they appear. The destructive tokens are the fill, its hover and the label on
45
+ * that fill: a fill colour as text fails contrast on the dark grounds, so error text
46
+ * takes `--tai-color-err-text` instead. A `color` declaration (CSS or JSX) is an
47
+ * offence anywhere but the `.tai-btn-danger` rule, where the label colour belongs. A
48
+ * reference whose property cannot be read is reported as `<no property found>`: an
49
+ * unattributable declaration is a hole in the rule, not a pass.
50
+ */
51
+ export function destructiveColorOffences(source) {
52
+ const offences = [];
53
+ for (const match of source.matchAll(DESTRUCTIVE_REFERENCE)) {
54
+ const property = governingProperty(source, match.index);
55
+ if (property === undefined) {
56
+ offences.push('<no property found>');
57
+ }
58
+ else if (property === 'color' && !insideDestructiveButtonRule(source, match.index)) {
59
+ offences.push(property);
60
+ }
61
+ }
62
+ return offences;
63
+ }
64
+ //# sourceMappingURL=token-rules.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"token-rules.js","sourceRoot":"","sources":["../src/token-rules.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH;;;;;;;GAOG;AACH,MAAM,UAAU,iBAAiB,CAAC,MAAc,EAAE,UAAkB;IAClE,MAAM,KAAK,GAAG,mCAAmC,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,UAAU,CAAC,CAAC,CAAC;IACpF,OAAO,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC;AACpB,CAAC;AAED,0FAA0F;AAC1F,MAAM,qBAAqB,GAAG,0CAA0C,CAAC;AAEzE,6DAA6D;AAC7D,MAAM,2BAA2B,GAAG,4BAA4B,CAAC;AAEjE;;;;GAIG;AACH,SAAS,2BAA2B,CAAC,MAAc,EAAE,UAAkB;IACrE,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,UAAU,CAAC,CAAC;IAC3C,MAAM,IAAI,GAAG,MAAM,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;IACrC,IAAI,IAAI,KAAK,CAAC,CAAC,IAAI,MAAM,CAAC,WAAW,CAAC,GAAG,CAAC,GAAG,IAAI;QAAE,OAAO,KAAK,CAAC;IAChE,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC;IACnC,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,EAAE,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,EAAE,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC;IAChG,MAAM,SAAS,GAAG,IAAI;SACnB,KAAK,CAAC,KAAK,CAAC;SACZ,OAAO,CAAC,mBAAmB,EAAE,EAAE,CAAC;SAChC,KAAK,CAAC,GAAG,CAAC;SACV,GAAG,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC;IACtC,OAAO,SAAS,CAAC,KAAK,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,2BAA2B,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC;AACnF,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,wBAAwB,CAAC,MAAc;IACrD,MAAM,QAAQ,GAAa,EAAE,CAAC;IAC9B,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,QAAQ,CAAC,qBAAqB,CAAC,EAAE,CAAC;QAC3D,MAAM,QAAQ,GAAG,iBAAiB,CAAC,MAAM,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC;QACxD,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;YAC3B,QAAQ,CAAC,IAAI,CAAC,qBAAqB,CAAC,CAAC;QACvC,CAAC;aAAM,IAAI,QAAQ,KAAK,OAAO,IAAI,CAAC,2BAA2B,CAAC,MAAM,EAAE,KAAK,CAAC,KAAK,CAAC,EAAE,CAAC;YACrF,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QAC1B,CAAC;IACH,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tai42/studio-sdk",
3
- "version": "25.0.1",
3
+ "version": "27.0.0",
4
4
  "description": "The Studio plugin contract, shared UI components, and hooks that every feature builds on.",
5
5
  "license": "Apache-2.0",
6
6
  "author": "tai42 <oss@tai42.ai>",
@@ -96,7 +96,7 @@
96
96
  "@radix-ui/react-select": "^2.3.8",
97
97
  "@radix-ui/react-tabs": "^1.1.22",
98
98
  "@radix-ui/react-tooltip": "^1.3.0",
99
- "@tai42/api-client": "25.0.1"
99
+ "@tai42/api-client": "27.0.0"
100
100
  },
101
101
  "scripts": {
102
102
  "build": "tsc -b tsconfig.build.json && node scripts/copy-assets.mjs",
@@ -0,0 +1,160 @@
1
+ /**
2
+ * `ActionMenu` — a row's or a card's overflow menu: a ghost "Actions" button
3
+ * whose accessible name the caller completes ("Actions for <name>", which keeps
4
+ * the visible word at its start) that opens a `role="menu"` list of
5
+ * `role="menuitem"` buttons, the WAI-ARIA menu-button pattern.
6
+ *
7
+ * Opening moves focus to the first item; ArrowDown/ArrowUp move between items
8
+ * (wrapping), Home/End jump to the ends, Escape closes and returns focus to the
9
+ * trigger, Tab closes. Activating an item closes the menu, returns focus to the
10
+ * trigger, then runs the item's action, so a dialog the action opens takes focus
11
+ * from a settled trigger. A pointer press outside the menu closes it.
12
+ *
13
+ * The caller passes only the items the session may use; with none the menu is
14
+ * not drawn at all (a hidden control takes no line).
15
+ */
16
+ import { type KeyboardEvent, type ReactNode, useEffect, useId, useRef, useState } from 'react';
17
+
18
+ import { ChevronDownIcon } from './icons';
19
+
20
+ export interface ActionMenuItem {
21
+ readonly id: string;
22
+ readonly label: string;
23
+ readonly onSelect: () => void;
24
+ /** Paints the item in the error tone (a destructive action). */
25
+ readonly danger?: boolean;
26
+ }
27
+
28
+ export interface ActionMenuProps {
29
+ /** The trigger's accessible name, e.g. "Actions for Acme". */
30
+ readonly label: string;
31
+ readonly items: readonly ActionMenuItem[];
32
+ }
33
+
34
+ export function ActionMenu({ label, items }: ActionMenuProps): ReactNode {
35
+ const [open, setOpen] = useState(false);
36
+ const menuId = useId();
37
+ const triggerRef = useRef<HTMLButtonElement>(null);
38
+ const menuRef = useRef<HTMLDivElement>(null);
39
+ const itemRefs = useRef<(HTMLButtonElement | null)[]>([]);
40
+
41
+ useEffect(() => {
42
+ if (!open) return undefined;
43
+ itemRefs.current[0]?.focus();
44
+ const onPointerDown = (event: PointerEvent): void => {
45
+ const target = event.target as Node | null;
46
+ if (target === null) return;
47
+ if (menuRef.current?.contains(target) === true) return;
48
+ if (triggerRef.current?.contains(target) === true) return;
49
+ setOpen(false);
50
+ };
51
+ document.addEventListener('pointerdown', onPointerDown);
52
+ return () => {
53
+ document.removeEventListener('pointerdown', onPointerDown);
54
+ };
55
+ }, [open]);
56
+
57
+ if (items.length === 0) return null;
58
+
59
+ const close = (): void => {
60
+ setOpen(false);
61
+ triggerRef.current?.focus();
62
+ };
63
+
64
+ const focusItem = (index: number): void => {
65
+ const count = items.length;
66
+ itemRefs.current[((index % count) + count) % count]?.focus();
67
+ };
68
+
69
+ const onMenuKeyDown = (event: KeyboardEvent<HTMLDivElement>): void => {
70
+ const current = itemRefs.current.findIndex((item) => item === document.activeElement);
71
+ switch (event.key) {
72
+ case 'ArrowDown':
73
+ event.preventDefault();
74
+ focusItem(current + 1);
75
+ break;
76
+ case 'ArrowUp':
77
+ event.preventDefault();
78
+ focusItem(current - 1);
79
+ break;
80
+ case 'Home':
81
+ event.preventDefault();
82
+ focusItem(0);
83
+ break;
84
+ case 'End':
85
+ event.preventDefault();
86
+ focusItem(items.length - 1);
87
+ break;
88
+ case 'Escape':
89
+ event.preventDefault();
90
+ event.stopPropagation();
91
+ close();
92
+ break;
93
+ case 'Tab':
94
+ setOpen(false);
95
+ break;
96
+ default:
97
+ break;
98
+ }
99
+ };
100
+
101
+ return (
102
+ <div className="tai-action-menu">
103
+ <button
104
+ ref={triggerRef}
105
+ type="button"
106
+ className="tai-btn tai-btn-ghost"
107
+ aria-label={label}
108
+ aria-haspopup="menu"
109
+ aria-expanded={open}
110
+ aria-controls={open ? menuId : undefined}
111
+ onClick={() => {
112
+ setOpen((was) => !was);
113
+ }}
114
+ onKeyDown={(event) => {
115
+ if (event.key === 'ArrowDown' && !open) {
116
+ event.preventDefault();
117
+ setOpen(true);
118
+ }
119
+ }}
120
+ >
121
+ Actions
122
+ <ChevronDownIcon />
123
+ </button>
124
+ {open ? (
125
+ <div
126
+ ref={menuRef}
127
+ id={menuId}
128
+ role="menu"
129
+ tabIndex={-1}
130
+ aria-label={label}
131
+ className="tai-action-menu-list"
132
+ onKeyDown={onMenuKeyDown}
133
+ >
134
+ {items.map((item, index) => (
135
+ <button
136
+ key={item.id}
137
+ ref={(node) => {
138
+ itemRefs.current[index] = node;
139
+ }}
140
+ type="button"
141
+ role="menuitem"
142
+ tabIndex={-1}
143
+ className={
144
+ item.danger === true
145
+ ? 'tai-action-menu-item tai-action-menu-item-danger'
146
+ : 'tai-action-menu-item'
147
+ }
148
+ onClick={() => {
149
+ close();
150
+ item.onSelect();
151
+ }}
152
+ >
153
+ {item.label}
154
+ </button>
155
+ ))}
156
+ </div>
157
+ ) : null}
158
+ </div>
159
+ );
160
+ }
@@ -0,0 +1,53 @@
1
+ /**
2
+ * `AgentPicker` — picks a registered agent by name from the agent list read
3
+ * (`GET /api/agents`) the host owns and hands in. Presentational: it fetches nothing
4
+ * and names no query library. It draws the read's states and its own copy, and flags
5
+ * a stored agent the loaded list does not hold as "not registered".
6
+ */
7
+ import type { AgentListing } from '@tai42/api-client';
8
+ import type { ReactNode } from 'react';
9
+
10
+ import { AppLink } from '../navigation/app-link';
11
+ import { type ListReadFlags, type NamedListCopy, NamedListPicker } from './named-list-picker';
12
+
13
+ /** The subset of the agent list read this picker reads; a TanStack query result satisfies it. */
14
+ export interface AgentListQuery extends ListReadFlags {
15
+ readonly data: AgentListing | undefined;
16
+ }
17
+
18
+ export interface AgentPickerProps {
19
+ readonly query: AgentListQuery;
20
+ readonly value: string;
21
+ readonly onValueChange: (value: string) => void;
22
+ /** The host form's field error, shown under the control while a `Select` renders. */
23
+ readonly error?: string | undefined;
24
+ }
25
+
26
+ const COPY: NamedListCopy = {
27
+ label: 'Agent',
28
+ loadingPlaceholder: 'Loading agents…',
29
+ placeholder: 'Select an agent',
30
+ forbiddenNote:
31
+ 'This session cannot list agents. Picking one needs read access to GET /api/agents; ask an administrator to grant it.',
32
+ emptyNote: 'No agents are registered on this deployment.',
33
+ emptyAction: (
34
+ <AppLink to="agents" className="tai-link">
35
+ Open the Agents screen
36
+ </AppLink>
37
+ ),
38
+ unlistedLabel: 'not registered',
39
+ };
40
+
41
+ export function AgentPicker({ query, value, onValueChange, error }: AgentPickerProps): ReactNode {
42
+ const names = (query.data?.items ?? []).map((item) => item.name);
43
+ return (
44
+ <NamedListPicker
45
+ query={query}
46
+ names={names}
47
+ value={value}
48
+ onValueChange={onValueChange}
49
+ error={error}
50
+ copy={COPY}
51
+ />
52
+ );
53
+ }
@@ -0,0 +1,68 @@
1
+ /**
2
+ * `ChannelPicker` — picks an installed channel plugin by name from the channel list
3
+ * read (`GET /api/channels`) the host owns and hands in. Presentational: it fetches
4
+ * nothing and names no query library. It draws the read's states and its own copy,
5
+ * and flags a stored channel the loaded list does not hold as "not installed".
6
+ */
7
+ import type { Channels } from '@tai42/api-client';
8
+ import type { ReactNode } from 'react';
9
+
10
+ import { AppLink } from '../navigation/app-link';
11
+ import { type ListReadFlags, type NamedListCopy, NamedListPicker } from './named-list-picker';
12
+
13
+ /** The subset of the channel list read this picker reads; a TanStack query result satisfies it. */
14
+ export interface ChannelListQuery extends ListReadFlags {
15
+ readonly data: Channels | undefined;
16
+ }
17
+
18
+ export interface ChannelPickerProps {
19
+ readonly query: ChannelListQuery;
20
+ readonly value: string;
21
+ readonly onValueChange: (value: string) => void;
22
+ /** The host form's field error, shown under the control while a `Select` renders. */
23
+ readonly error?: string | undefined;
24
+ }
25
+
26
+ /**
27
+ * The named channel's declared hint for a route's identity: the hint, `null` for a
28
+ * listed channel that declares none, `undefined` when the name is not listed.
29
+ */
30
+ export function channelIdentityHint(data: Channels, name: string): string | null | undefined {
31
+ const entry = data.channels.find((channel) => channel.name === name);
32
+ return entry === undefined ? undefined : entry.identity_hint;
33
+ }
34
+
35
+ const COPY: NamedListCopy = {
36
+ label: 'Channel',
37
+ description: 'An installed channel plugin; the answer goes back on the same channel.',
38
+ loadingPlaceholder: 'Loading channels…',
39
+ placeholder: 'Select a channel',
40
+ forbiddenNote:
41
+ 'This session cannot list channels. Picking one needs read access to GET /api/channels; ask an administrator to grant it.',
42
+ emptyNote: 'No channels are installed on this deployment.',
43
+ emptyAction: (
44
+ <AppLink to="marketplace" search={{ kind: 'channel' }} className="tai-link">
45
+ Browse channel plugins
46
+ </AppLink>
47
+ ),
48
+ unlistedLabel: 'not installed',
49
+ };
50
+
51
+ export function ChannelPicker({
52
+ query,
53
+ value,
54
+ onValueChange,
55
+ error,
56
+ }: ChannelPickerProps): ReactNode {
57
+ const names = (query.data?.channels ?? []).map((channel) => channel.name);
58
+ return (
59
+ <NamedListPicker
60
+ query={query}
61
+ names={names}
62
+ value={value}
63
+ onValueChange={onValueChange}
64
+ error={error}
65
+ copy={COPY}
66
+ />
67
+ );
68
+ }
@@ -167,6 +167,8 @@
167
167
  /* Radix hard-codes `tabIndex: 0` on every `TabsContent`, so a tab panel is a
168
168
  tab stop whether or not it holds one — it needs the ring like any other. */
169
169
  .tai-tabpanel:focus-visible,
170
+ .tai-stepper-button:focus-visible,
171
+ .tai-action-menu-item:focus-visible,
170
172
  .tai-dialog:focus-visible {
171
173
  outline: 2px solid var(--tai-color-focus-ring);
172
174
  outline-offset: 2px;
@@ -279,14 +281,14 @@
279
281
  }
280
282
 
281
283
  .tai-btn-danger {
282
- background: var(--tai-color-danger);
283
- border-color: var(--tai-color-danger);
284
- color: var(--tai-color-danger-text);
284
+ background: var(--tai-color-destructive);
285
+ border-color: var(--tai-color-destructive);
286
+ color: var(--tai-color-on-destructive);
285
287
  }
286
288
 
287
289
  .tai-btn-danger:hover:not(:disabled):not([aria-disabled='true']) {
288
- background: var(--tai-color-danger-hover);
289
- border-color: var(--tai-color-danger-hover);
290
+ background: var(--tai-color-destructive-hover);
291
+ border-color: var(--tai-color-destructive-hover);
290
292
  }
291
293
 
292
294
  /* A square button whose only content is a 16 px icon; its accessible name
@@ -331,6 +333,34 @@
331
333
  color: var(--tai-color-accent-hover);
332
334
  }
333
335
 
336
+ /* -- Holder list ----------------------------------------------------------
337
+ The things that still use a name, each a link to where it lives. A long
338
+ list scrolls inside its own bordered region, which takes focus so the
339
+ keyboard can scroll it. */
340
+ .tai-holder-list {
341
+ padding: var(--tai-space-2) var(--tai-space-3);
342
+ max-height: 16rem;
343
+ overflow-y: auto;
344
+ color: var(--tai-color-text);
345
+ border: 1px solid var(--tai-color-border);
346
+ border-radius: var(--tai-radius-sm);
347
+ }
348
+
349
+ .tai-holder-list > ul {
350
+ margin: 0;
351
+ padding: 0;
352
+ list-style: none;
353
+ }
354
+
355
+ .tai-holder-list > ul > li + li {
356
+ margin-top: var(--tai-space-1);
357
+ }
358
+
359
+ .tai-holder-lead {
360
+ margin: 0 0 var(--tai-space-2);
361
+ color: var(--tai-color-err-text);
362
+ }
363
+
334
364
  /* -- Icons ----------------------------------------------------------------
335
365
  Rendered at 16 px in currentColor at full opacity; a 24-unit viewBox and a
336
366
  1.6 stroke keep the whole set optically consistent. */
@@ -362,7 +392,9 @@
362
392
  .tai-nav-link > svg,
363
393
  .tai-segment > svg,
364
394
  .tai-select-trigger > svg,
365
- .tai-status > svg {
395
+ .tai-status > svg,
396
+ .tai-stepper-button > svg,
397
+ .tai-action-menu-item > svg {
366
398
  flex: none;
367
399
  }
368
400
 
@@ -1236,12 +1268,14 @@
1236
1268
  .tai-table .tai-choice,
1237
1269
  .tai-table .tai-nav-item,
1238
1270
  .tai-table .tai-nav-link,
1239
- .tai-table .tai-select-trigger {
1271
+ .tai-table .tai-select-trigger,
1272
+ .tai-table .tai-stepper-button,
1273
+ .tai-table .tai-action-menu-item {
1240
1274
  min-width: auto;
1241
1275
  overflow-wrap: normal;
1242
1276
  }
1243
1277
 
1244
- /* `.tai-segment` takes the wrap half ONLY. The eight above carry
1278
+ /* `.tai-segment` takes the wrap half ONLY. The ten above carry
1245
1279
  `min-width: 0`, so `auto` is what hands them an intrinsic minimum back;
1246
1280
  this one carries a 28 px / 44 px POINTER TARGET (WCAG 2.5.8), and `auto` on
1247
1281
  a flex item is the CONTENT-based minimum — for the icon-only option this
@@ -1838,6 +1872,10 @@
1838
1872
  gap: var(--tai-space-4);
1839
1873
  }
1840
1874
 
1875
+ .tai-stack-1 {
1876
+ gap: var(--tai-space-1);
1877
+ }
1878
+
1841
1879
  .tai-stack-2 {
1842
1880
  gap: var(--tai-space-2);
1843
1881
  }
@@ -2275,6 +2313,176 @@
2275
2313
  }
2276
2314
  }
2277
2315
 
2316
+ /* -- Stepper: a flow's step progress. A 24 px marker per step, a 2 px connector
2317
+ between steps (the ok fill after a done step), the label beside it. -- */
2318
+ .tai-stepper {
2319
+ display: flex;
2320
+ flex-wrap: wrap;
2321
+ align-items: center;
2322
+ gap: var(--tai-space-2);
2323
+ margin: 0;
2324
+ padding: 0;
2325
+ list-style: none;
2326
+ }
2327
+
2328
+ .tai-stepper-step {
2329
+ display: flex;
2330
+ align-items: center;
2331
+ gap: var(--tai-space-2);
2332
+ }
2333
+
2334
+ .tai-stepper-step + .tai-stepper-step::before {
2335
+ content: '';
2336
+ display: block;
2337
+ width: var(--tai-space-6);
2338
+ height: 2px;
2339
+ background: var(--tai-color-border);
2340
+ transition: background-color var(--tai-motion-fast);
2341
+ }
2342
+
2343
+ .tai-stepper-step-done + .tai-stepper-step::before {
2344
+ background: var(--tai-color-ok-fill);
2345
+ }
2346
+
2347
+ .tai-stepper-button {
2348
+ display: inline-flex;
2349
+ align-items: center;
2350
+ gap: var(--tai-space-2);
2351
+ min-width: 0;
2352
+ padding: 0;
2353
+ border: 0;
2354
+ background: none;
2355
+ font: inherit;
2356
+ color: inherit;
2357
+ overflow-wrap: anywhere;
2358
+ }
2359
+
2360
+ button.tai-stepper-button {
2361
+ cursor: pointer;
2362
+ border-radius: var(--tai-radius-sm);
2363
+ }
2364
+
2365
+ .tai-stepper-marker {
2366
+ display: inline-flex;
2367
+ align-items: center;
2368
+ justify-content: center;
2369
+ flex-shrink: 0;
2370
+ width: 24px;
2371
+ height: 24px;
2372
+ border-radius: 50%;
2373
+ font-size: var(--tai-text-sm);
2374
+ font-weight: 600;
2375
+ transition:
2376
+ background-color var(--tai-motion-fast),
2377
+ box-shadow var(--tai-motion-fast),
2378
+ color var(--tai-motion-fast);
2379
+ }
2380
+
2381
+ .tai-stepper-marker > svg {
2382
+ width: 14px;
2383
+ height: 14px;
2384
+ }
2385
+
2386
+ .tai-stepper-marker-done {
2387
+ background: var(--tai-color-ok-fill);
2388
+ color: var(--tai-color-on-fill);
2389
+ }
2390
+
2391
+ .tai-stepper-marker-current {
2392
+ box-shadow: inset 0 0 0 2px var(--tai-color-heading);
2393
+ color: var(--tai-color-heading);
2394
+ }
2395
+
2396
+ .tai-stepper-marker-upcoming {
2397
+ box-shadow: inset 0 0 0 1px var(--tai-color-border-strong);
2398
+ color: var(--tai-color-text-muted);
2399
+ }
2400
+
2401
+ .tai-stepper-marker-error {
2402
+ background: var(--tai-color-err-fill);
2403
+ color: var(--tai-color-on-fill);
2404
+ }
2405
+
2406
+ .tai-stepper-step-current .tai-stepper-label {
2407
+ font-weight: 600;
2408
+ }
2409
+
2410
+ .tai-stepper-step-upcoming .tai-stepper-label {
2411
+ color: var(--tai-color-text-muted);
2412
+ }
2413
+
2414
+ /* The "Step 2 of 3: " prefix is the narrow layout's; the wide row shows every step. */
2415
+ .tai-stepper-position {
2416
+ display: none;
2417
+ }
2418
+
2419
+ /* -- ActionMenu: an overflow menu stacked in flow under its trigger. -- */
2420
+ .tai-action-menu {
2421
+ position: relative;
2422
+ display: inline-block;
2423
+ }
2424
+
2425
+ .tai-action-menu-list {
2426
+ position: absolute;
2427
+ inset-inline-end: 0;
2428
+ top: calc(100% + var(--tai-space-1));
2429
+ z-index: var(--tai-z-dropdown);
2430
+ display: flex;
2431
+ flex-direction: column;
2432
+ min-width: 10rem;
2433
+ padding: var(--tai-space-1);
2434
+ border: 1px solid var(--tai-color-border);
2435
+ border-radius: var(--tai-radius-md);
2436
+ background: var(--tai-color-surface-raised);
2437
+ box-shadow: var(--tai-shadow-lift);
2438
+ }
2439
+
2440
+ .tai-action-menu-item {
2441
+ display: flex;
2442
+ align-items: center;
2443
+ min-width: 0;
2444
+ padding: var(--tai-space-2) var(--tai-space-3);
2445
+ border: 0;
2446
+ border-radius: var(--tai-radius-sm);
2447
+ background: none;
2448
+ font: inherit;
2449
+ font-size: var(--tai-text-md);
2450
+ color: var(--tai-color-text);
2451
+ text-align: start;
2452
+ overflow-wrap: anywhere;
2453
+ cursor: pointer;
2454
+ }
2455
+
2456
+ .tai-action-menu-item:hover,
2457
+ .tai-action-menu-item:focus-visible {
2458
+ background: var(--tai-color-surface);
2459
+ }
2460
+
2461
+ .tai-action-menu-item-danger {
2462
+ color: var(--tai-color-err-text);
2463
+ }
2464
+
2465
+ /* <640: the stepper collapses to its current step, "Step 2 of 3: Pick tools";
2466
+ the other steps stay in the list for assistive technology. */
2467
+ @media (max-width: 639px) {
2468
+ .tai-stepper-step:not(.tai-stepper-step-current) {
2469
+ position: absolute;
2470
+ width: 1px;
2471
+ height: 1px;
2472
+ overflow: hidden;
2473
+ clip-path: inset(50%);
2474
+ white-space: nowrap;
2475
+ }
2476
+
2477
+ .tai-stepper-step + .tai-stepper-step::before {
2478
+ display: none;
2479
+ }
2480
+
2481
+ .tai-stepper-position {
2482
+ display: inline;
2483
+ }
2484
+ }
2485
+
2278
2486
  /* <640 phone: the rail gives way to a sticky 56 px top bar; the shell, the card
2279
2487
  grid and the page header each fall to a single column, and the page tightens
2280
2488
  its padding and its gap. */
@@ -2343,6 +2551,11 @@
2343
2551
  min-height: var(--tai-control-height-coarse);
2344
2552
  }
2345
2553
 
2554
+ .tai-stepper-button,
2555
+ .tai-action-menu-item {
2556
+ min-height: var(--tai-control-height-coarse);
2557
+ }
2558
+
2346
2559
  .tai-icon-btn {
2347
2560
  width: var(--tai-control-height-coarse);
2348
2561
  height: var(--tai-control-height-coarse);
@@ -2417,6 +2630,11 @@
2417
2630
  transition: none;
2418
2631
  }
2419
2632
 
2633
+ .tai-stepper-marker,
2634
+ .tai-stepper-step + .tai-stepper-step::before {
2635
+ transition: none;
2636
+ }
2637
+
2420
2638
  .tai-btn:hover:not(:disabled):not([aria-disabled='true']),
2421
2639
  .tai-card-interactive:hover,
2422
2640
  .tai-card-interactive:focus-within {
@@ -47,6 +47,8 @@ export interface ConfirmDialogProps {
47
47
  * the confirm button — the action can only refuse, so there is nothing to retry.
48
48
  */
49
49
  readonly disabledNote?: ReactNode;
50
+ /** Hold the confirm button disabled while the caller knows the action cannot pass. */
51
+ readonly confirmDisabled?: boolean;
50
52
  readonly confirmVariant?: 'primary' | 'danger';
51
53
  /** The cancel button's label; defaults to `Cancel`. Set it when the negative
52
54
  * choice reads better as a specific verb (e.g. `Keep existing`). */
@@ -64,6 +66,7 @@ export function ConfirmDialog({
64
66
  isPending,
65
67
  error,
66
68
  disabledNote,
69
+ confirmDisabled = false,
67
70
  confirmVariant = 'danger',
68
71
  cancelLabel = 'Cancel',
69
72
  children,
@@ -85,7 +88,7 @@ export function ConfirmDialog({
85
88
  <Button
86
89
  type="button"
87
90
  variant={confirmVariant}
88
- disabled={isPending || disabledNote != null}
91
+ disabled={isPending || disabledNote != null || confirmDisabled}
89
92
  onClick={onConfirm}
90
93
  >
91
94
  {isPending ? <Spinner label={pendingLabel} /> : null}