@tai42/studio-sdk 16.0.0 → 17.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 (486) hide show
  1. package/dist/components/code-block.js +1 -1
  2. package/dist/components/code-block.js.map +1 -1
  3. package/dist/components/confirm-dialog.js +1 -1
  4. package/dist/components/confirm-dialog.js.map +1 -1
  5. package/dist/components/copy-field.js +1 -1
  6. package/dist/components/copy-field.js.map +1 -1
  7. package/dist/components/date-range-picker.d.ts.map +1 -1
  8. package/dist/components/date-range-picker.js.map +1 -1
  9. package/dist/components/dialog.d.ts.map +1 -1
  10. package/dist/components/dialog.js.map +1 -1
  11. package/dist/components/dirty-guard.d.ts.map +1 -1
  12. package/dist/components/dirty-guard.js +1 -1
  13. package/dist/components/dirty-guard.js.map +1 -1
  14. package/dist/components/explorer-body.d.ts +49 -0
  15. package/dist/components/explorer-body.d.ts.map +1 -0
  16. package/dist/components/explorer-body.js +67 -0
  17. package/dist/components/explorer-body.js.map +1 -0
  18. package/dist/components/explorer-page-size.d.ts +4 -0
  19. package/dist/components/explorer-page-size.d.ts.map +1 -0
  20. package/dist/components/explorer-page-size.js +36 -0
  21. package/dist/components/explorer-page-size.js.map +1 -0
  22. package/dist/components/explorer-tag-filter.d.ts +17 -0
  23. package/dist/components/explorer-tag-filter.d.ts.map +1 -0
  24. package/dist/components/explorer-tag-filter.js +29 -0
  25. package/dist/components/explorer-tag-filter.js.map +1 -0
  26. package/dist/components/explorer-tags.d.ts +22 -0
  27. package/dist/components/explorer-tags.d.ts.map +1 -0
  28. package/dist/components/explorer-tags.js +51 -0
  29. package/dist/components/explorer-tags.js.map +1 -0
  30. package/dist/components/explorer-view.d.ts +3 -17
  31. package/dist/components/explorer-view.d.ts.map +1 -1
  32. package/dist/components/explorer-view.js +53 -170
  33. package/dist/components/explorer-view.js.map +1 -1
  34. package/dist/components/extension-combo-builder.d.ts +1 -6
  35. package/dist/components/extension-combo-builder.d.ts.map +1 -1
  36. package/dist/components/extension-combo-builder.js +4 -79
  37. package/dist/components/extension-combo-builder.js.map +1 -1
  38. package/dist/components/extension-combo-parts.d.ts +44 -0
  39. package/dist/components/extension-combo-parts.d.ts.map +1 -0
  40. package/dist/components/extension-combo-parts.js +52 -0
  41. package/dist/components/extension-combo-parts.js.map +1 -0
  42. package/dist/components/field.d.ts +8 -0
  43. package/dist/components/field.d.ts.map +1 -1
  44. package/dist/components/field.js +0 -8
  45. package/dist/components/field.js.map +1 -1
  46. package/dist/components/fleet-report.d.ts +1 -1
  47. package/dist/components/fleet-report.d.ts.map +1 -1
  48. package/dist/components/fleet-report.js +51 -37
  49. package/dist/components/fleet-report.js.map +1 -1
  50. package/dist/components/folder-nav.d.ts.map +1 -1
  51. package/dist/components/folder-nav.js +1 -1
  52. package/dist/components/folder-nav.js.map +1 -1
  53. package/dist/components/form-dialog.d.ts.map +1 -1
  54. package/dist/components/form-dialog.js +1 -1
  55. package/dist/components/form-dialog.js.map +1 -1
  56. package/dist/components/icons/chrome-marks.d.ts +50 -0
  57. package/dist/components/icons/chrome-marks.d.ts.map +1 -0
  58. package/dist/components/icons/chrome-marks.js +51 -0
  59. package/dist/components/icons/chrome-marks.js.map +1 -0
  60. package/dist/components/icons/icon-frame.d.ts +29 -0
  61. package/dist/components/icons/icon-frame.d.ts.map +1 -0
  62. package/dist/components/icons/icon-frame.js +19 -0
  63. package/dist/components/icons/icon-frame.js.map +1 -0
  64. package/dist/components/icons/index.d.ts +40 -0
  65. package/dist/components/icons/index.d.ts.map +1 -0
  66. package/dist/components/icons/index.js +39 -0
  67. package/dist/components/icons/index.js.map +1 -0
  68. package/dist/components/icons/nav-map.d.ts +14 -0
  69. package/dist/components/icons/nav-map.d.ts.map +1 -0
  70. package/dist/components/icons/nav-map.js +28 -0
  71. package/dist/components/icons/nav-map.js.map +1 -0
  72. package/dist/components/icons/nav-marks.d.ts +44 -0
  73. package/dist/components/icons/nav-marks.d.ts.map +1 -0
  74. package/dist/components/icons/nav-marks.js +45 -0
  75. package/dist/components/icons/nav-marks.js.map +1 -0
  76. package/dist/components/icons/status-marks.d.ts +21 -0
  77. package/dist/components/icons/status-marks.d.ts.map +1 -0
  78. package/dist/components/icons/status-marks.js +22 -0
  79. package/dist/components/icons/status-marks.js.map +1 -0
  80. package/dist/components/icons/theme-marks.d.ts +9 -0
  81. package/dist/components/icons/theme-marks.d.ts.map +1 -0
  82. package/dist/components/icons/theme-marks.js +10 -0
  83. package/dist/components/icons/theme-marks.js.map +1 -0
  84. package/dist/components/inputs.js.map +1 -1
  85. package/dist/components/json-diff.d.ts.map +1 -1
  86. package/dist/components/json-diff.js +1 -3
  87. package/dist/components/json-diff.js.map +1 -1
  88. package/dist/components/json-tree-context.d.ts +13 -0
  89. package/dist/components/json-tree-context.d.ts.map +1 -0
  90. package/dist/components/json-tree-context.js +15 -0
  91. package/dist/components/json-tree-context.js.map +1 -0
  92. package/dist/components/json-tree-model.d.ts +56 -0
  93. package/dist/components/json-tree-model.d.ts.map +1 -0
  94. package/dist/components/json-tree-model.js +125 -0
  95. package/dist/components/json-tree-model.js.map +1 -0
  96. package/dist/components/json-tree-nodes.d.ts +35 -0
  97. package/dist/components/json-tree-nodes.d.ts.map +1 -0
  98. package/dist/components/json-tree-nodes.js +113 -0
  99. package/dist/components/json-tree-nodes.js.map +1 -0
  100. package/dist/components/json-tree.d.ts +20 -0
  101. package/dist/components/json-tree.d.ts.map +1 -1
  102. package/dist/components/json-tree.js +7 -315
  103. package/dist/components/json-tree.js.map +1 -1
  104. package/dist/components/json-value-format.d.ts +20 -0
  105. package/dist/components/json-value-format.d.ts.map +1 -0
  106. package/dist/components/json-value-format.js +63 -0
  107. package/dist/components/json-value-format.js.map +1 -0
  108. package/dist/components/markdown-blocks.d.ts +38 -0
  109. package/dist/components/markdown-blocks.d.ts.map +1 -0
  110. package/dist/components/markdown-blocks.js +168 -0
  111. package/dist/components/markdown-blocks.js.map +1 -0
  112. package/dist/components/markdown-inline.d.ts +24 -0
  113. package/dist/components/markdown-inline.d.ts.map +1 -0
  114. package/dist/components/markdown-inline.js +257 -0
  115. package/dist/components/markdown-inline.js.map +1 -0
  116. package/dist/components/markdown.d.ts +3 -52
  117. package/dist/components/markdown.d.ts.map +1 -1
  118. package/dist/components/markdown.js +4 -376
  119. package/dist/components/markdown.js.map +1 -1
  120. package/dist/components/observer-registry.d.ts +38 -0
  121. package/dist/components/observer-registry.d.ts.map +1 -0
  122. package/dist/components/observer-registry.js +131 -0
  123. package/dist/components/observer-registry.js.map +1 -0
  124. package/dist/components/overflow-measure.d.ts +38 -0
  125. package/dist/components/overflow-measure.d.ts.map +1 -0
  126. package/dist/components/overflow-measure.js +54 -0
  127. package/dist/components/overflow-measure.js.map +1 -0
  128. package/dist/components/page-fill.d.ts.map +1 -1
  129. package/dist/components/page-fill.js.map +1 -1
  130. package/dist/components/prose-regions.d.ts +81 -0
  131. package/dist/components/prose-regions.d.ts.map +1 -0
  132. package/dist/components/prose-regions.js +128 -0
  133. package/dist/components/prose-regions.js.map +1 -0
  134. package/dist/components/radio-group.d.ts.map +1 -1
  135. package/dist/components/radio-group.js.map +1 -1
  136. package/dist/components/scroll-region.d.ts +24 -0
  137. package/dist/components/scroll-region.d.ts.map +1 -1
  138. package/dist/components/scroll-region.js +3 -322
  139. package/dist/components/scroll-region.js.map +1 -1
  140. package/dist/components/tab-stop-parse.d.ts +53 -0
  141. package/dist/components/tab-stop-parse.d.ts.map +1 -0
  142. package/dist/components/tab-stop-parse.js +202 -0
  143. package/dist/components/tab-stop-parse.js.map +1 -0
  144. package/dist/components/table.d.ts +1 -1
  145. package/dist/components/table.d.ts.map +1 -1
  146. package/dist/components/tags.d.ts.map +1 -1
  147. package/dist/components/tags.js +1 -1
  148. package/dist/components/tags.js.map +1 -1
  149. package/dist/components/templated-text-field.d.ts +1 -1
  150. package/dist/components/templated-text-field.d.ts.map +1 -1
  151. package/dist/components/templated-text-field.js +10 -174
  152. package/dist/components/templated-text-field.js.map +1 -1
  153. package/dist/components/templated-text-kwargs-editor.d.ts +24 -0
  154. package/dist/components/templated-text-kwargs-editor.d.ts.map +1 -0
  155. package/dist/components/templated-text-kwargs-editor.js +48 -0
  156. package/dist/components/templated-text-kwargs-editor.js.map +1 -0
  157. package/dist/components/templated-text-kwargs.d.ts +37 -0
  158. package/dist/components/templated-text-kwargs.d.ts.map +1 -0
  159. package/dist/components/templated-text-kwargs.js +66 -0
  160. package/dist/components/templated-text-kwargs.js.map +1 -0
  161. package/dist/components/templated-text-source.d.ts +33 -0
  162. package/dist/components/templated-text-source.d.ts.map +1 -0
  163. package/dist/components/templated-text-source.js +53 -0
  164. package/dist/components/templated-text-source.js.map +1 -0
  165. package/dist/components/tool-picker-parts.d.ts +39 -0
  166. package/dist/components/tool-picker-parts.d.ts.map +1 -0
  167. package/dist/components/tool-picker-parts.js +87 -0
  168. package/dist/components/tool-picker-parts.js.map +1 -0
  169. package/dist/components/tool-picker.d.ts +1 -2
  170. package/dist/components/tool-picker.d.ts.map +1 -1
  171. package/dist/components/tool-picker.js +4 -66
  172. package/dist/components/tool-picker.js.map +1 -1
  173. package/dist/components/version-history-panel.d.ts.map +1 -1
  174. package/dist/components/version-history-panel.js +7 -34
  175. package/dist/components/version-history-panel.js.map +1 -1
  176. package/dist/components/version-history-rows.d.ts +48 -0
  177. package/dist/components/version-history-rows.d.ts.map +1 -0
  178. package/dist/components/version-history-rows.js +51 -0
  179. package/dist/components/version-history-rows.js.map +1 -0
  180. package/dist/components/view-toggle.d.ts.map +1 -1
  181. package/dist/components/view-toggle.js.map +1 -1
  182. package/dist/element-slot.d.ts +11 -0
  183. package/dist/element-slot.d.ts.map +1 -1
  184. package/dist/element-slot.js +0 -11
  185. package/dist/element-slot.js.map +1 -1
  186. package/dist/guards.d.ts +6 -0
  187. package/dist/guards.d.ts.map +1 -0
  188. package/dist/guards.js +8 -0
  189. package/dist/guards.js.map +1 -0
  190. package/dist/hooks/interactions-connection.d.ts +43 -0
  191. package/dist/hooks/interactions-connection.d.ts.map +1 -0
  192. package/dist/hooks/interactions-connection.js +171 -0
  193. package/dist/hooks/interactions-connection.js.map +1 -0
  194. package/dist/hooks/interactions-stream.d.ts +80 -0
  195. package/dist/hooks/interactions-stream.d.ts.map +1 -0
  196. package/dist/hooks/interactions-stream.js +233 -0
  197. package/dist/hooks/interactions-stream.js.map +1 -0
  198. package/dist/hooks/useApi.d.ts +2259 -2189
  199. package/dist/hooks/useApi.d.ts.map +1 -1
  200. package/dist/hooks/useApi.js +0 -11
  201. package/dist/hooks/useApi.js.map +1 -1
  202. package/dist/hooks/useAuth.d.ts +6 -0
  203. package/dist/hooks/useAuth.d.ts.map +1 -1
  204. package/dist/hooks/useAuth.js +0 -6
  205. package/dist/hooks/useAuth.js.map +1 -1
  206. package/dist/hooks/useCapabilities.d.ts +18 -2
  207. package/dist/hooks/useCapabilities.d.ts.map +1 -1
  208. package/dist/hooks/useCapabilities.js +1 -17
  209. package/dist/hooks/useCapabilities.js.map +1 -1
  210. package/dist/hooks/useSse.d.ts +34 -6
  211. package/dist/hooks/useSse.d.ts.map +1 -1
  212. package/dist/hooks/useSse.js +27 -471
  213. package/dist/hooks/useSse.js.map +1 -1
  214. package/dist/hooks/useSystemKinds.d.ts +1 -1
  215. package/dist/hooks/useSystemKinds.d.ts.map +1 -1
  216. package/dist/hooks/useSystemKinds.js +0 -18
  217. package/dist/hooks/useSystemKinds.js.map +1 -1
  218. package/dist/hooks/useTheme.d.ts +15 -0
  219. package/dist/hooks/useTheme.d.ts.map +1 -1
  220. package/dist/hooks/useTheme.js +0 -15
  221. package/dist/hooks/useTheme.js.map +1 -1
  222. package/dist/hooks/useToolDisplayNames.d.ts +0 -14
  223. package/dist/hooks/useToolDisplayNames.d.ts.map +1 -1
  224. package/dist/hooks/useToolDisplayNames.js +0 -14
  225. package/dist/hooks/useToolDisplayNames.js.map +1 -1
  226. package/dist/host.d.ts +3 -3
  227. package/dist/host.d.ts.map +1 -1
  228. package/dist/host.js +2 -9
  229. package/dist/host.js.map +1 -1
  230. package/dist/index.d.ts +103 -103
  231. package/dist/index.d.ts.map +1 -1
  232. package/dist/index.js +49 -62
  233. package/dist/index.js.map +1 -1
  234. package/dist/navigation/app-link.d.ts +25 -0
  235. package/dist/navigation/app-link.d.ts.map +1 -0
  236. package/dist/navigation/app-link.js +33 -0
  237. package/dist/navigation/app-link.js.map +1 -0
  238. package/dist/navigation/context.d.ts +3 -20
  239. package/dist/navigation/context.d.ts.map +1 -1
  240. package/dist/navigation/context.js +7 -267
  241. package/dist/navigation/context.js.map +1 -1
  242. package/dist/navigation/entry-state.d.ts +23 -0
  243. package/dist/navigation/entry-state.d.ts.map +1 -0
  244. package/dist/navigation/entry-state.js +39 -0
  245. package/dist/navigation/entry-state.js.map +1 -0
  246. package/dist/navigation/guard-registry.d.ts +30 -0
  247. package/dist/navigation/guard-registry.d.ts.map +1 -0
  248. package/dist/navigation/guard-registry.js +46 -0
  249. package/dist/navigation/guard-registry.js.map +1 -0
  250. package/dist/navigation/guarded-navigation.d.ts +24 -0
  251. package/dist/navigation/guarded-navigation.d.ts.map +1 -0
  252. package/dist/navigation/guarded-navigation.js +102 -0
  253. package/dist/navigation/guarded-navigation.js.map +1 -0
  254. package/dist/navigation/index.d.ts +5 -4
  255. package/dist/navigation/index.d.ts.map +1 -1
  256. package/dist/navigation/index.js +2 -1
  257. package/dist/navigation/index.js.map +1 -1
  258. package/dist/navigation/types.d.ts +8 -11
  259. package/dist/navigation/types.d.ts.map +1 -1
  260. package/dist/navigation/use-back-forward-guard.d.ts +12 -0
  261. package/dist/navigation/use-back-forward-guard.d.ts.map +1 -0
  262. package/dist/navigation/use-back-forward-guard.js +75 -0
  263. package/dist/navigation/use-back-forward-guard.js.map +1 -0
  264. package/dist/navigation/use-search-commit.d.ts.map +1 -1
  265. package/dist/navigation/use-search-commit.js.map +1 -1
  266. package/dist/plugin/host-state.d.ts.map +1 -1
  267. package/dist/plugin/host-state.js.map +1 -1
  268. package/dist/plugin/registry.d.ts +6 -15
  269. package/dist/plugin/registry.d.ts.map +1 -1
  270. package/dist/plugin/registry.js +83 -75
  271. package/dist/plugin/registry.js.map +1 -1
  272. package/dist/schema-editor/SchemaEditor.d.ts.map +1 -1
  273. package/dist/schema-editor/SchemaEditor.js.map +1 -1
  274. package/dist/schema-editor/index.d.ts +3 -3
  275. package/dist/schema-editor/index.d.ts.map +1 -1
  276. package/dist/schema-editor/index.js +1 -6
  277. package/dist/schema-editor/index.js.map +1 -1
  278. package/dist/schema-form/SecretRefField.d.ts +18 -3
  279. package/dist/schema-form/SecretRefField.d.ts.map +1 -1
  280. package/dist/schema-form/SecretRefField.js +21 -26
  281. package/dist/schema-form/SecretRefField.js.map +1 -1
  282. package/dist/schema-form/classify.d.ts +1 -113
  283. package/dist/schema-form/classify.d.ts.map +1 -1
  284. package/dist/schema-form/classify.js +174 -246
  285. package/dist/schema-form/classify.js.map +1 -1
  286. package/dist/schema-form/context.d.ts +6 -0
  287. package/dist/schema-form/context.d.ts.map +1 -1
  288. package/dist/schema-form/context.js +0 -6
  289. package/dist/schema-form/context.js.map +1 -1
  290. package/dist/schema-form/default-value.d.ts.map +1 -1
  291. package/dist/schema-form/default-value.js +42 -19
  292. package/dist/schema-form/default-value.js.map +1 -1
  293. package/dist/schema-form/enum-field.d.ts +1 -1
  294. package/dist/schema-form/enum-field.d.ts.map +1 -1
  295. package/dist/schema-form/field-group.d.ts +6 -0
  296. package/dist/schema-form/field-group.d.ts.map +1 -1
  297. package/dist/schema-form/field-group.js +0 -6
  298. package/dist/schema-form/field-group.js.map +1 -1
  299. package/dist/schema-form/field-model.d.ts +121 -0
  300. package/dist/schema-form/field-model.d.ts.map +1 -0
  301. package/dist/schema-form/field-model.js +2 -0
  302. package/dist/schema-form/field-model.js.map +1 -0
  303. package/dist/schema-form/field-node.d.ts.map +1 -1
  304. package/dist/schema-form/field-node.js +25 -14
  305. package/dist/schema-form/field-node.js.map +1 -1
  306. package/dist/schema-form/index.d.ts +8 -8
  307. package/dist/schema-form/index.d.ts.map +1 -1
  308. package/dist/schema-form/index.js +3 -34
  309. package/dist/schema-form/index.js.map +1 -1
  310. package/dist/schema-form/json-field.d.ts +19 -0
  311. package/dist/schema-form/json-field.d.ts.map +1 -1
  312. package/dist/schema-form/json-field.js +0 -19
  313. package/dist/schema-form/json-field.js.map +1 -1
  314. package/dist/schema-form/media-field.d.ts +1 -1
  315. package/dist/schema-form/media-field.d.ts.map +1 -1
  316. package/dist/schema-form/media-field.js +49 -30
  317. package/dist/schema-form/media-field.js.map +1 -1
  318. package/dist/schema-form/record-field.d.ts +12 -0
  319. package/dist/schema-form/record-field.d.ts.map +1 -1
  320. package/dist/schema-form/record-field.js +1 -13
  321. package/dist/schema-form/record-field.js.map +1 -1
  322. package/dist/schema-form/string-annotations.d.ts +20 -0
  323. package/dist/schema-form/string-annotations.d.ts.map +1 -0
  324. package/dist/schema-form/string-annotations.js +105 -0
  325. package/dist/schema-form/string-annotations.js.map +1 -0
  326. package/dist/schema-form/string-field.d.ts +1 -1
  327. package/dist/schema-form/string-field.d.ts.map +1 -1
  328. package/dist/schema-form/string-field.js +1 -1
  329. package/dist/schema-form/string-field.js.map +1 -1
  330. package/dist/schema-form/union-field.d.ts +1 -1
  331. package/dist/schema-form/union-field.d.ts.map +1 -1
  332. package/dist/schema-form/union-field.js.map +1 -1
  333. package/dist/schema-form/union.d.ts +8 -1
  334. package/dist/schema-form/union.d.ts.map +1 -1
  335. package/dist/schema-form/union.js +0 -7
  336. package/dist/schema-form/union.js.map +1 -1
  337. package/dist/schema-form/validate.d.ts.map +1 -1
  338. package/dist/schema-form/validate.js +126 -105
  339. package/dist/schema-form/validate.js.map +1 -1
  340. package/dist/state-binding/AdapterMapping.d.ts.map +1 -1
  341. package/dist/state-binding/AdapterMapping.js +0 -0
  342. package/dist/state-binding/AdapterMapping.js.map +1 -1
  343. package/dist/state-binding/BindingJqField.d.ts.map +1 -1
  344. package/dist/state-binding/BindingJqField.js +2 -2
  345. package/dist/state-binding/BindingJqField.js.map +1 -1
  346. package/dist/state-binding/InputInjectionRow.d.ts.map +1 -1
  347. package/dist/state-binding/InputInjectionRow.js +3 -3
  348. package/dist/state-binding/InputInjectionRow.js.map +1 -1
  349. package/dist/state-binding/StateAttachRow.d.ts.map +1 -1
  350. package/dist/state-binding/StateAttachRow.js +57 -41
  351. package/dist/state-binding/StateAttachRow.js.map +1 -1
  352. package/dist/state-binding/StateBindingSection.d.ts.map +1 -1
  353. package/dist/state-binding/StateBindingSection.js.map +1 -1
  354. package/dist/state-binding/SubjectScopeFields.d.ts.map +1 -1
  355. package/dist/state-binding/SubjectScopeFields.js.map +1 -1
  356. package/dist/state-binding/UpdateRow.d.ts.map +1 -1
  357. package/dist/state-binding/UpdateRow.js +2 -2
  358. package/dist/state-binding/UpdateRow.js.map +1 -1
  359. package/dist/state-binding/adapter-mapping-row.d.ts +17 -0
  360. package/dist/state-binding/adapter-mapping-row.d.ts.map +1 -0
  361. package/dist/state-binding/adapter-mapping-row.js +87 -0
  362. package/dist/state-binding/adapter-mapping-row.js.map +1 -0
  363. package/dist/state-binding/index.d.ts +21 -21
  364. package/dist/state-binding/index.d.ts.map +1 -1
  365. package/dist/state-binding/index.js +9 -15
  366. package/dist/state-binding/index.js.map +1 -1
  367. package/dist/state-binding/templated-text-catalog.d.ts.map +1 -1
  368. package/dist/state-binding/templated-text-catalog.js.map +1 -1
  369. package/dist/state-binding/types.d.ts +2 -2
  370. package/dist/state-binding/types.d.ts.map +1 -1
  371. package/dist/structured-output/StructuredOutput.d.ts.map +1 -1
  372. package/dist/structured-output/StructuredOutput.js +1 -3
  373. package/dist/structured-output/StructuredOutput.js.map +1 -1
  374. package/package.json +2 -2
  375. package/src/components/code-block.tsx +1 -1
  376. package/src/components/confirm-dialog.tsx +1 -1
  377. package/src/components/copy-field.tsx +1 -1
  378. package/src/components/date-range-picker.tsx +1 -1
  379. package/src/components/dialog.tsx +1 -1
  380. package/src/components/dirty-guard.tsx +3 -3
  381. package/src/components/explorer-body.tsx +239 -0
  382. package/src/components/explorer-page-size.ts +39 -0
  383. package/src/components/explorer-tag-filter.tsx +80 -0
  384. package/src/components/explorer-tags.ts +64 -0
  385. package/src/components/explorer-view.tsx +115 -367
  386. package/src/components/extension-combo-builder.tsx +35 -153
  387. package/src/components/extension-combo-parts.tsx +236 -0
  388. package/src/components/field.tsx +1 -1
  389. package/src/components/fleet-report.tsx +98 -55
  390. package/src/components/folder-nav.tsx +2 -2
  391. package/src/components/form-dialog.tsx +2 -2
  392. package/src/components/icons/chrome-marks.tsx +195 -0
  393. package/src/components/icons/icon-frame.tsx +50 -0
  394. package/src/components/icons/index.ts +84 -0
  395. package/src/components/icons/nav-map.ts +55 -0
  396. package/src/components/icons/nav-marks.tsx +205 -0
  397. package/src/components/icons/status-marks.tsx +46 -0
  398. package/src/components/icons/theme-marks.tsx +33 -0
  399. package/src/components/inputs.tsx +1 -1
  400. package/src/components/json-diff.tsx +1 -4
  401. package/src/components/json-tree-context.ts +26 -0
  402. package/src/components/json-tree-model.ts +139 -0
  403. package/src/components/json-tree-nodes.tsx +224 -0
  404. package/src/components/json-tree.tsx +15 -434
  405. package/src/components/json-value-format.ts +64 -0
  406. package/src/components/markdown-blocks.ts +198 -0
  407. package/src/components/markdown-inline.tsx +365 -0
  408. package/src/components/markdown.tsx +6 -472
  409. package/src/components/observer-registry.ts +139 -0
  410. package/src/components/overflow-measure.ts +69 -0
  411. package/src/components/page-fill.tsx +1 -1
  412. package/src/components/prose-regions.ts +153 -0
  413. package/src/components/radio-group.tsx +1 -1
  414. package/src/components/scroll-region.tsx +22 -361
  415. package/src/components/tab-stop-parse.ts +217 -0
  416. package/src/components/table.tsx +1 -1
  417. package/src/components/tags.tsx +2 -2
  418. package/src/components/templated-text-field.tsx +39 -332
  419. package/src/components/templated-text-kwargs-editor.tsx +147 -0
  420. package/src/components/templated-text-kwargs.ts +92 -0
  421. package/src/components/templated-text-source.tsx +226 -0
  422. package/src/components/tool-picker-parts.tsx +184 -0
  423. package/src/components/tool-picker.tsx +22 -114
  424. package/src/components/version-history-panel.tsx +39 -143
  425. package/src/components/version-history-rows.tsx +220 -0
  426. package/src/components/view-toggle.tsx +1 -1
  427. package/src/element-slot.ts +1 -1
  428. package/src/guards.ts +7 -0
  429. package/src/hooks/interactions-connection.ts +227 -0
  430. package/src/hooks/interactions-stream.ts +274 -0
  431. package/src/hooks/useApi.ts +1 -1
  432. package/src/hooks/useAuth.ts +1 -1
  433. package/src/hooks/useCapabilities.ts +3 -3
  434. package/src/hooks/useSse.ts +51 -478
  435. package/src/hooks/useSystemKinds.tsx +2 -2
  436. package/src/hooks/useTheme.ts +1 -1
  437. package/src/hooks/useToolDisplayNames.tsx +2 -2
  438. package/src/host.ts +7 -7
  439. package/src/index.ts +292 -292
  440. package/src/navigation/app-link.tsx +70 -0
  441. package/src/navigation/context.tsx +12 -331
  442. package/src/navigation/entry-state.ts +42 -0
  443. package/src/navigation/guard-registry.ts +65 -0
  444. package/src/navigation/guarded-navigation.ts +147 -0
  445. package/src/navigation/index.ts +14 -14
  446. package/src/navigation/types.ts +8 -11
  447. package/src/navigation/use-back-forward-guard.ts +90 -0
  448. package/src/navigation/use-search-commit.ts +1 -1
  449. package/src/plugin/host-state.ts +1 -1
  450. package/src/plugin/registry.ts +111 -90
  451. package/src/schema-editor/SchemaEditor.tsx +1 -1
  452. package/src/schema-editor/index.ts +3 -3
  453. package/src/schema-form/SecretRefField.tsx +122 -72
  454. package/src/schema-form/classify.ts +245 -396
  455. package/src/schema-form/context.ts +1 -1
  456. package/src/schema-form/default-value.ts +48 -17
  457. package/src/schema-form/enum-field.tsx +1 -1
  458. package/src/schema-form/field-group.tsx +1 -1
  459. package/src/schema-form/field-model.ts +133 -0
  460. package/src/schema-form/field-node.tsx +95 -39
  461. package/src/schema-form/index.ts +10 -10
  462. package/src/schema-form/json-field.tsx +1 -1
  463. package/src/schema-form/media-field.tsx +136 -67
  464. package/src/schema-form/record-field.tsx +2 -2
  465. package/src/schema-form/string-annotations.ts +111 -0
  466. package/src/schema-form/string-field.tsx +2 -2
  467. package/src/schema-form/union-field.tsx +3 -2
  468. package/src/schema-form/union.ts +1 -1
  469. package/src/schema-form/validate.ts +185 -104
  470. package/src/state-binding/AdapterMapping.tsx +0 -0
  471. package/src/state-binding/BindingJqField.tsx +3 -3
  472. package/src/state-binding/InputInjectionRow.tsx +4 -4
  473. package/src/state-binding/StateAttachRow.tsx +229 -154
  474. package/src/state-binding/StateBindingSection.tsx +1 -1
  475. package/src/state-binding/SubjectScopeFields.tsx +1 -1
  476. package/src/state-binding/UpdateRow.tsx +3 -3
  477. package/src/state-binding/adapter-mapping-row.tsx +232 -0
  478. package/src/state-binding/index.ts +43 -43
  479. package/src/state-binding/templated-text-catalog.ts +0 -1
  480. package/src/state-binding/types.ts +2 -2
  481. package/src/structured-output/StructuredOutput.tsx +1 -4
  482. package/dist/components/icons.d.ts +0 -160
  483. package/dist/components/icons.d.ts.map +0 -1
  484. package/dist/components/icons.js +0 -160
  485. package/dist/components/icons.js.map +0 -1
  486. package/src/components/icons.tsx +0 -586
@@ -0,0 +1,227 @@
1
+ /**
2
+ * The SSE reconnect driver behind `useInteractionsStream`: it owns the fetch +
3
+ * ReadableStream tail (via the api-client's `streamInteractions`, never
4
+ * EventSource), the capped-exponential-with-jitter reconnect backoff, the
5
+ * on-(re)connect resync, and the wiring that folds each frame through the pure
6
+ * overlay reducer and republishes the merged list. React state flows in as the
7
+ * dispatchers the hook passes; this file holds no React.
8
+ */
9
+ import type { ApiClient, Interaction } from '@tai42/api-client';
10
+ import type { Dispatch, RefObject, SetStateAction } from 'react';
11
+
12
+ import { isFeatureDisabled } from '../feature-disabled';
13
+ import {
14
+ emptyOverlay,
15
+ type LiveOverlay,
16
+ merge,
17
+ overlayApplyAdd,
18
+ overlayApplyAnswered,
19
+ overlayApplyRemoved,
20
+ parseAddFrame,
21
+ parseId,
22
+ type StreamInteraction,
23
+ sweepAnswered,
24
+ } from './interactions-stream';
25
+
26
+ // Reconnect backoff: capped exponential with full jitter. The delay for attempt
27
+ // n is a random value in [0, min(CAP, BASE * 2**n)); the attempt counter resets
28
+ // only when a connection PROVES HEALTHY — it stayed open at least
29
+ // `HEALTHY_CONNECTION_MS` — not merely when it opens, so a server that accepts the
30
+ // request then drops the body still backs off instead of tight-looping.
31
+ const RECONNECT_BASE_MS = 1500;
32
+ const RECONNECT_CAP_MS = 30000;
33
+ const HEALTHY_CONNECTION_MS = RECONNECT_BASE_MS;
34
+
35
+ /** The live connection status the hook renders from. */
36
+ export interface ConnectionState {
37
+ readonly connected: boolean;
38
+ readonly error: Error | null;
39
+ /**
40
+ * The interactions store is not configured on this deployment: the stream
41
+ * answered with a terminal 501 `interactions-not-configured`. Reconnection is
42
+ * abandoned (retrying would replay the same refusal forever) and the consumer
43
+ * renders the muted OFF state instead of a red error or a floating badge.
44
+ */
45
+ readonly disabled: boolean;
46
+ }
47
+
48
+ /** Everything the driver reads or writes, supplied by the hook's render. */
49
+ export interface InteractionsStreamDeps {
50
+ readonly api: ApiClient;
51
+ readonly overlayRef: RefObject<LiveOverlay>;
52
+ readonly epochRef: RefObject<number>;
53
+ readonly seedRef: RefObject<readonly Interaction[]>;
54
+ readonly onResyncRef: RefObject<() => boolean | Promise<boolean>>;
55
+ readonly onUnauthorized: () => void;
56
+ readonly setConnectionState: Dispatch<SetStateAction<ConnectionState>>;
57
+ readonly setInteractions: Dispatch<SetStateAction<StreamInteraction[]>>;
58
+ readonly setCountEpoch: Dispatch<SetStateAction<number>>;
59
+ }
60
+
61
+ /** The driver's per-connection context: the deps plus this run's abort source and overlay. */
62
+ interface StreamContext extends InteractionsStreamDeps {
63
+ readonly controller: AbortController;
64
+ readonly overlay: LiveOverlay;
65
+ }
66
+
67
+ function aborted(ctx: StreamContext): boolean {
68
+ return ctx.controller.signal.aborted;
69
+ }
70
+
71
+ function surfaceMalformed(ctx: StreamContext, event: string): void {
72
+ ctx.setConnectionState((prev) => ({
73
+ ...prev,
74
+ error: new Error(`malformed interaction frame: ${event}`),
75
+ }));
76
+ }
77
+
78
+ /**
79
+ * A successful data frame sweeps aged answered cards, republishes the merged list,
80
+ * and clears any transient frame-parse error — one bad frame on a healthy stream
81
+ * must not stick. It does NOT refetch: the overlay already updated the list and the
82
+ * derived count, and the resync fires on (re)connect only.
83
+ */
84
+ function commit(ctx: StreamContext): void {
85
+ if (aborted(ctx)) return;
86
+ sweepAnswered(ctx.overlay);
87
+ ctx.setConnectionState((prev) => (prev.error === null ? prev : { ...prev, error: null }));
88
+ ctx.setInteractions(merge(ctx.seedRef.current, ctx.overlay));
89
+ }
90
+
91
+ function applyFrame(ctx: StreamContext, event: string, data: string): void {
92
+ if (event === 'interaction.removed') {
93
+ const id = parseId(data);
94
+ if (id === null) {
95
+ surfaceMalformed(ctx, event);
96
+ return;
97
+ }
98
+ overlayApplyRemoved(ctx.overlay, id, ctx.epochRef.current);
99
+ commit(ctx);
100
+ return;
101
+ }
102
+ if (event === 'interaction.answered') {
103
+ const id = parseId(data);
104
+ if (id === null) {
105
+ surfaceMalformed(ctx, event);
106
+ return;
107
+ }
108
+ overlayApplyAnswered(ctx.overlay, id, ctx.epochRef.current, ctx.seedRef.current);
109
+ commit(ctx);
110
+ return;
111
+ }
112
+ // interaction.add — the full question.
113
+ const interaction = parseAddFrame(data);
114
+ if (interaction === null) {
115
+ surfaceMalformed(ctx, event);
116
+ return;
117
+ }
118
+ overlayApplyAdd(ctx.overlay, interaction, ctx.epochRef.current);
119
+ commit(ctx);
120
+ }
121
+
122
+ /** The resume cursor: the id of the last frame seen on this mount, sent as Last-Event-ID. */
123
+ interface ResumeCursor {
124
+ lastEventId: string | undefined;
125
+ }
126
+
127
+ /**
128
+ * Open one connection and consume its frames until the tail ends. Refetches the
129
+ * paged base on connect under a fresh epoch (tail-only carries no backlog) and
130
+ * records that epoch as `countEpoch` only once the refetch LANDED, so the count
131
+ * applies only deltas not yet folded into the fresh `total`.
132
+ */
133
+ async function connectAndConsume(ctx: StreamContext, cursor: ResumeCursor): Promise<void> {
134
+ const frames = await ctx.api.streamInteractions(ctx.controller.signal, cursor.lastEventId);
135
+ if (aborted(ctx)) return;
136
+ ctx.setConnectionState((prev) => ({ ...prev, connected: true, error: null, disabled: false }));
137
+ const epoch = (ctx.epochRef.current += 1);
138
+ const landed = await ctx.onResyncRef.current();
139
+ if (aborted(ctx)) return;
140
+ if (landed) ctx.setCountEpoch(epoch);
141
+ for await (const frame of frames) {
142
+ if (aborted(ctx)) return;
143
+ applyFrame(ctx, frame.event, frame.data);
144
+ // Advance the resume cursor to every id-bearing frame (even a malformed one)
145
+ // so a reconnect never re-requests a frame already delivered.
146
+ if (frame.id !== undefined) cursor.lastEventId = frame.id;
147
+ }
148
+ }
149
+
150
+ /** Classify a connection error into whether the driver should stop or reconnect. */
151
+ function handleConnectionError(ctx: StreamContext, err: unknown): 'stop' | 'retry' {
152
+ const error = err instanceof Error ? err : new Error(String(err));
153
+ ctx.setConnectionState((prev) => ({ ...prev, connected: false, error }));
154
+ if (error.name === 'ApiUnauthorizedError') {
155
+ // A dead credential — reconnecting would replay the same bad key forever. Hand
156
+ // off to the app's 401→login handler, the same path every other call takes.
157
+ ctx.onUnauthorized();
158
+ return 'stop';
159
+ }
160
+ if (isFeatureDisabled(error)) {
161
+ // A terminal 501 `interactions-not-configured`: reconnecting would replay the
162
+ // same refusal on every backoff forever. Flag the OFF state and stop.
163
+ ctx.setConnectionState((prev) => ({ ...prev, connected: false, disabled: true }));
164
+ return 'stop';
165
+ }
166
+ return 'retry';
167
+ }
168
+
169
+ /** Wait out the backoff for `attempt`, resolving early if the signal aborts. */
170
+ function waitBackoff(signal: AbortSignal, attempt: number): Promise<void> {
171
+ const ceiling = Math.min(RECONNECT_CAP_MS, RECONNECT_BASE_MS * 2 ** attempt);
172
+ const delay = Math.random() * ceiling;
173
+ return new Promise<void>((resolve) => {
174
+ const onAbort = () => {
175
+ clearTimeout(timer);
176
+ resolve();
177
+ };
178
+ const timer = setTimeout(() => {
179
+ // Normal wake: drop the abort listener so it does not accumulate across
180
+ // reconnects ({ once: true } only self-removes on firing).
181
+ signal.removeEventListener('abort', onAbort);
182
+ resolve();
183
+ }, delay);
184
+ signal.addEventListener('abort', onAbort, { once: true });
185
+ });
186
+ }
187
+
188
+ /** The reconnect loop: connect, consume, back off, repeat until aborted or terminal. */
189
+ async function runConnectionLoop(ctx: StreamContext): Promise<void> {
190
+ const cursor: ResumeCursor = { lastEventId: undefined };
191
+ let reconnectAttempt = 0;
192
+ while (!aborted(ctx)) {
193
+ const openedAt = Date.now();
194
+ try {
195
+ await connectAndConsume(ctx, cursor);
196
+ if (aborted(ctx)) return;
197
+ } catch (err) {
198
+ if (aborted(ctx)) return;
199
+ if (handleConnectionError(ctx, err) === 'stop') return;
200
+ }
201
+ // A connection open at least HEALTHY_CONNECTION_MS proved healthy — clear the
202
+ // backoff so the next drop reconnects promptly; an accept-then-drop flap does not.
203
+ if (Date.now() - openedAt >= HEALTHY_CONNECTION_MS) reconnectAttempt = 0;
204
+ if (aborted(ctx)) return;
205
+ ctx.setConnectionState((prev) => ({ ...prev, connected: false }));
206
+ await waitBackoff(ctx.controller.signal, reconnectAttempt);
207
+ reconnectAttempt += 1;
208
+ }
209
+ }
210
+
211
+ /**
212
+ * Start the tail-only interactions stream and return the effect cleanup. The
213
+ * abort signal is the single cancellation source of truth: cleanup aborts it, every
214
+ * loop guard reads it fresh, and a fresh mount starts from an empty overlay at epoch
215
+ * 0 so a re-run never inherits the prior run's tail or its epoch stamps.
216
+ */
217
+ export function startInteractionsStream(deps: InteractionsStreamDeps): () => void {
218
+ const controller = new AbortController();
219
+ const ctx: StreamContext = { ...deps, controller, overlay: deps.overlayRef.current };
220
+ void runConnectionLoop(ctx);
221
+ return () => {
222
+ controller.abort();
223
+ deps.overlayRef.current = emptyOverlay();
224
+ deps.epochRef.current = 0;
225
+ deps.setCountEpoch(0);
226
+ };
227
+ }
@@ -0,0 +1,274 @@
1
+ /**
2
+ * The pure reducer behind the live interactions inbox: frame parsing, the live
3
+ * overlay accumulated over the paged base, and the two derived projections
4
+ * (`merge` → the published list, `pendingCount` → the live badge tally). No React,
5
+ * no I/O — the connection driver owns the SSE loop and feeds frames through here.
6
+ */
7
+ import type { Interaction } from '@tai42/api-client';
8
+
9
+ import { isRecord } from '../guards';
10
+
11
+ /** A live interaction plus the client-maintained `answered` flag. */
12
+ export type StreamInteraction = Interaction & { readonly answered: boolean };
13
+
14
+ // The server emits no removal for an answered card — only `interaction.answered`,
15
+ // never `interaction.removed` for it — and the paged base never carries answered
16
+ // items, so answered cards would otherwise pile up unbounded in the always-mounted
17
+ // badge/inbox. The client ages them out: drop an answered card
18
+ // ANSWERED_RETENTION_MS after its answered frame, keeping at most ANSWERED_CAP as a
19
+ // hard flood backstop. Swept lazily on frame/reconnect ticks (the stream carries no
20
+ // idle heartbeat frame), never a per-item timer.
21
+ const ANSWERED_RETENTION_MS = 10 * 60 * 1000;
22
+ const ANSWERED_CAP = 200;
23
+
24
+ // The wire `answer_format` values. Duplicated from the api-client enum by design:
25
+ // the SDK is the leaf and must not import api-client at runtime. The `satisfies`
26
+ // pin against `Interaction['answer_format']` fails to compile if a listed value
27
+ // leaves the union, so this runtime guard cannot silently drift from the type.
28
+ const ANSWER_FORMATS = new Set<string>([
29
+ 'text',
30
+ 'confirm',
31
+ 'select',
32
+ 'form',
33
+ 'external',
34
+ ] as const satisfies readonly Interaction['answer_format'][]);
35
+
36
+ /** Parse a frame's JSON to a plain object, or null when it is not one. */
37
+ function parseJson(data: string): Record<string, unknown> | null {
38
+ if (!data) return null;
39
+ try {
40
+ const obj: unknown = JSON.parse(data);
41
+ return isRecord(obj) ? obj : null;
42
+ } catch {
43
+ return null;
44
+ }
45
+ }
46
+
47
+ /** The strictly-required frame fields, narrowed once so the assembly needs no casts. */
48
+ interface RequiredFrameFields {
49
+ interaction_id: string;
50
+ group_id: string;
51
+ created_at: string;
52
+ timeout_at: string;
53
+ answer_format: Interaction['answer_format'];
54
+ }
55
+
56
+ /**
57
+ * The strict-required fields: `interaction_id`, `group_id`, `created_at`,
58
+ * `timeout_at` must each be a string and `answer_format` a known enum value.
59
+ * `question` is `z.string().default('')` — a missing question is valid (normalized
60
+ * to `''` on assembly) but a present non-string is malformed; `format_payload` is
61
+ * nullish — absent/null is valid but a present non-object is malformed.
62
+ */
63
+ function requiredFieldsValid(
64
+ obj: Record<string, unknown>,
65
+ ): obj is Record<string, unknown> & RequiredFrameFields {
66
+ if (typeof obj.interaction_id !== 'string') return false;
67
+ if (typeof obj.group_id !== 'string') return false;
68
+ if (obj.question !== undefined && typeof obj.question !== 'string') return false;
69
+ if (typeof obj.created_at !== 'string') return false;
70
+ if (typeof obj.timeout_at !== 'string') return false;
71
+ if (typeof obj.answer_format !== 'string' || !ANSWER_FORMATS.has(obj.answer_format)) return false;
72
+ if (obj.format_payload != null && !isRecord(obj.format_payload)) return false;
73
+ return true;
74
+ }
75
+
76
+ /**
77
+ * The optional fields, each rejected only when PRESENT with the wrong type: an
78
+ * absent value stays absent (or takes its schema default on assembly). `media`
79
+ * items stay unknown by design — each is validated per item by the renderer.
80
+ */
81
+ function optionalFieldsValid(obj: Record<string, unknown>): boolean {
82
+ if (obj.sensitive !== undefined && typeof obj.sensitive !== 'boolean') return false;
83
+ if (obj.server_verified !== undefined && typeof obj.server_verified !== 'boolean') return false;
84
+ if (obj.channel !== undefined && typeof obj.channel !== 'string') return false;
85
+ if (obj.recipient !== undefined && typeof obj.recipient !== 'string') return false;
86
+ if (obj.origin !== undefined && typeof obj.origin !== 'string') return false;
87
+ if (obj.audience !== undefined && typeof obj.audience !== 'string') return false;
88
+ if (obj.media !== undefined && !Array.isArray(obj.media)) return false;
89
+ return true;
90
+ }
91
+
92
+ /** Assemble the validated frame into an `Interaction`, applying the schema defaults. */
93
+ function buildInteraction(obj: Record<string, unknown> & RequiredFrameFields): Interaction {
94
+ return {
95
+ interaction_id: obj.interaction_id,
96
+ group_id: obj.group_id,
97
+ question: typeof obj.question === 'string' ? obj.question : '',
98
+ answer_format: obj.answer_format,
99
+ format_payload: isRecord(obj.format_payload) ? obj.format_payload : {},
100
+ created_at: obj.created_at,
101
+ timeout_at: obj.timeout_at,
102
+ sensitive: typeof obj.sensitive === 'boolean' ? obj.sensitive : false,
103
+ ...(typeof obj.server_verified === 'boolean' ? { server_verified: obj.server_verified } : {}),
104
+ ...(typeof obj.channel === 'string' ? { channel: obj.channel } : {}),
105
+ ...(typeof obj.recipient === 'string' ? { recipient: obj.recipient } : {}),
106
+ ...(typeof obj.origin === 'string' ? { origin: obj.origin } : {}),
107
+ ...(typeof obj.audience === 'string' ? { audience: obj.audience } : {}),
108
+ ...(Array.isArray(obj.media) ? { media: obj.media } : {}),
109
+ };
110
+ }
111
+
112
+ /**
113
+ * Validate an `interaction.add` frame into an `Interaction`, or null when it is
114
+ * malformed. This mirrors the api-client `interaction` zod schema exactly — it
115
+ * accepts what that schema accepts, no more, no less — so the caller surfaces a
116
+ * malformed frame as an error rather than rendering a blank card, and never
117
+ * silently coerces one.
118
+ */
119
+ export function parseAddFrame(data: string): Interaction | null {
120
+ const obj = parseJson(data);
121
+ if (obj === null) return null;
122
+ if (!requiredFieldsValid(obj) || !optionalFieldsValid(obj)) return null;
123
+ return buildInteraction(obj);
124
+ }
125
+
126
+ /** The interaction id from an `answered` / `removed` frame, or null. */
127
+ export function parseId(data: string): string | null {
128
+ const id = parseJson(data)?.interaction_id;
129
+ return typeof id === 'string' ? id : null;
130
+ }
131
+
132
+ /**
133
+ * The live overlay a connection accumulates over the paged base: the tail's own
134
+ * adds (kept whether or not the base already lists them, so a fresher add wins),
135
+ * the answered stamps (wall-clock ms, for the aging sweep), and the tombstones for
136
+ * ids removed on the tail OR aged out of the answered window (a tombstone stops a
137
+ * still-stale seed from re-showing a gone card; ids are never reused). `addEpoch`
138
+ * and `goneEpoch` carry the resync epoch current when an add / a first terminal frame
139
+ * arrived, so the count applies an add (or subtracts a terminal) only while it is not
140
+ * yet folded into the refetched `total`; the merge and the aging sweep ignore them.
141
+ */
142
+ export interface LiveOverlay {
143
+ readonly adds: Map<string, Interaction>;
144
+ readonly addEpoch: Map<string, number>;
145
+ readonly answeredAt: Map<string, number>;
146
+ readonly removed: Set<string>;
147
+ readonly goneEpoch: Map<string, number>;
148
+ }
149
+
150
+ export function emptyOverlay(): LiveOverlay {
151
+ return {
152
+ adds: new Map(),
153
+ addEpoch: new Map(),
154
+ answeredAt: new Map(),
155
+ removed: new Set(),
156
+ goneEpoch: new Map(),
157
+ };
158
+ }
159
+
160
+ /** Record an `interaction.add`: a live add overwrites any prior copy in place. */
161
+ export function overlayApplyAdd(
162
+ overlay: LiveOverlay,
163
+ interaction: Interaction,
164
+ epoch: number,
165
+ ): void {
166
+ overlay.adds.set(interaction.interaction_id, interaction);
167
+ overlay.addEpoch.set(interaction.interaction_id, epoch);
168
+ }
169
+
170
+ /**
171
+ * Record an `interaction.answered`. The answered time and terminal epoch are
172
+ * stamped on the FIRST answered frame only (a redelivery keeps the original stamp,
173
+ * so the retention window is not extended and a later terminal never re-dates it
174
+ * into a fresher resync). A seed-origin card is promoted into the overlay so it
175
+ * lingers uniformly with a live-add card, since the paged base never carries
176
+ * answered items and a refetch would otherwise drop it before its window elapses.
177
+ */
178
+ export function overlayApplyAnswered(
179
+ overlay: LiveOverlay,
180
+ id: string,
181
+ epoch: number,
182
+ seed: readonly Interaction[],
183
+ ): void {
184
+ if (!overlay.answeredAt.has(id)) overlay.answeredAt.set(id, Date.now());
185
+ if (!overlay.goneEpoch.has(id)) overlay.goneEpoch.set(id, epoch);
186
+ if (!overlay.adds.has(id)) {
187
+ const seeded = seed.find((item) => item.interaction_id === id);
188
+ if (seeded !== undefined) {
189
+ overlay.adds.set(id, seeded);
190
+ overlay.addEpoch.set(id, epoch);
191
+ }
192
+ }
193
+ }
194
+
195
+ /** Record an `interaction.removed`: tombstone the id and stamp its first terminal epoch. */
196
+ export function overlayApplyRemoved(overlay: LiveOverlay, id: string, epoch: number): void {
197
+ overlay.adds.delete(id);
198
+ overlay.addEpoch.delete(id);
199
+ overlay.answeredAt.delete(id);
200
+ overlay.removed.add(id);
201
+ if (!overlay.goneEpoch.has(id)) overlay.goneEpoch.set(id, epoch);
202
+ }
203
+
204
+ /**
205
+ * Age answered cards out (the server sends no removal for them): tombstone any past
206
+ * the retention window, then — as a flood backstop — the oldest beyond the count
207
+ * cap. A tombstone (not a bare delete) so a still-stale seed cannot resurrect a card
208
+ * the client has retired; the id keeps its original `goneEpoch` so moving
209
+ * answered→removed never re-dates it into a fresher resync.
210
+ */
211
+ export function sweepAnswered(overlay: LiveOverlay): void {
212
+ const now = Date.now();
213
+ const retire = (id: string) => {
214
+ overlay.answeredAt.delete(id);
215
+ overlay.adds.delete(id);
216
+ overlay.addEpoch.delete(id);
217
+ overlay.removed.add(id);
218
+ };
219
+ for (const [id, at] of overlay.answeredAt) {
220
+ if (now - at >= ANSWERED_RETENTION_MS) retire(id);
221
+ }
222
+ if (overlay.answeredAt.size > ANSWERED_CAP) {
223
+ const oldest = [...overlay.answeredAt.entries()].sort((a, b) => a[1] - b[1]);
224
+ for (const [id] of oldest.slice(0, oldest.length - ANSWERED_CAP)) retire(id);
225
+ }
226
+ }
227
+
228
+ /** Fold the paged base and the live overlay into the published, ordered list. */
229
+ export function merge(seed: readonly Interaction[], live: LiveOverlay): StreamInteraction[] {
230
+ const out = new Map<string, StreamInteraction>();
231
+ for (const item of seed) {
232
+ if (live.removed.has(item.interaction_id)) continue;
233
+ out.set(item.interaction_id, { ...item, answered: live.answeredAt.has(item.interaction_id) });
234
+ }
235
+ for (const [id, item] of live.adds) {
236
+ if (live.removed.has(id)) continue;
237
+ out.set(id, { ...item, answered: live.answeredAt.has(id) });
238
+ }
239
+ return Array.from(out.values());
240
+ }
241
+
242
+ /**
243
+ * The live pending tally: the door's `total` for the seed, plus live adds whose id
244
+ * is absent from the seed AND stamped at or after `countEpoch` (a new question not
245
+ * yet folded into `total`), minus one for every terminal id — answered OR removed —
246
+ * stamped at or after `countEpoch`, deduped per id so an id counts once however many
247
+ * terminal frames it drew. `countEpoch` is the epoch of the last resync whose refetch
248
+ * LANDED (a failed refetch never advances it): a delta stamped before it is already
249
+ * reflected in the fresh `total`, so it is NOT applied again — this is what stops a
250
+ * resync from re-subtracting a terminal it already dropped from `total`, while a
251
+ * failed refetch keeps the prior-epoch deltas applying against the prior total. One
252
+ * residual race remains: a frame arriving while a refetch is in flight may already be
253
+ * reflected in that refetch's `total`, so the raw tally can transiently dip below zero
254
+ * until the next resync reconciles. A pending tally below zero is meaningless, so the
255
+ * published count clamps at 0; the clamp covers only the negative symptom (a transient
256
+ * positive under-count reconciles on the next resync, not here).
257
+ */
258
+ export function pendingCount(
259
+ seed: readonly Interaction[],
260
+ total: number,
261
+ live: LiveOverlay,
262
+ countEpoch: number,
263
+ ): number {
264
+ const seedIds = new Set(seed.map((item) => item.interaction_id));
265
+ let count = total;
266
+ for (const id of live.adds.keys()) {
267
+ if (!seedIds.has(id) && (live.addEpoch.get(id) ?? 0) >= countEpoch) count += 1;
268
+ }
269
+ const gone = new Set<string>([...live.answeredAt.keys(), ...live.removed]);
270
+ for (const id of gone) {
271
+ if ((live.goneEpoch.get(id) ?? 0) >= countEpoch) count -= 1;
272
+ }
273
+ return Math.max(count, 0);
274
+ }
@@ -9,8 +9,8 @@
9
9
  * runtime dependency on @tai42/api-client (the SDK imports nothing internal at
10
10
  * runtime).
11
11
  */
12
- import { createContext, useContext } from 'react';
13
12
  import type { ApiClient } from '@tai42/api-client';
13
+ import { createContext, useContext } from 'react';
14
14
 
15
15
  const ApiContext = createContext<ApiClient | null>(null);
16
16
 
@@ -4,8 +4,8 @@
4
4
  * sessionStorage. localStorage is NEVER written: the README documents the
5
5
  * XSS trade-off and why localStorage persistence is not offered.
6
6
  */
7
- import { createContext, useCallback, useContext, useMemo, useState } from 'react';
8
7
  import type { ReactNode } from 'react';
8
+ import { createContext, useCallback, useContext, useMemo, useState } from 'react';
9
9
  import { createElement } from 'react';
10
10
 
11
11
  const SESSION_KEY = 'tai-studio.apiKey';
@@ -14,6 +14,8 @@
14
14
  * `ErrorState` with `retry` in the nav region — never an optimistic full nav on
15
15
  * error. The UI fails closed exactly as the server would.
16
16
  */
17
+ import type { MeProjection } from '@tai42/api-client';
18
+ import type { ReactNode } from 'react';
17
19
  import {
18
20
  createContext,
19
21
  useCallback,
@@ -23,9 +25,7 @@ import {
23
25
  useRef,
24
26
  useState,
25
27
  } from 'react';
26
- import type { ReactNode } from 'react';
27
28
  import { createElement } from 'react';
28
- import type { MeProjection } from '@tai42/api-client';
29
29
 
30
30
  import { useApi } from './useApi';
31
31
  import { useAuth } from './useAuth';
@@ -116,7 +116,7 @@ export function useCapabilities(): CapabilityContextValue {
116
116
 
117
117
  /**
118
118
  * Whether the projection is the synthetic TOTAL projection — the admin session
119
- * (the condition-free ownerless `"*"` discriminator) and the gate-off local-dev
119
+ * (the condition-free `"*"` discriminator) and the gate-off local-dev
120
120
  * projection alike. This is `projection.admin === true` and NOTHING else: a
121
121
  * seeded editor/viewer carries `["*"]` scopes PLUS a jq fence, so keying on `"*"`
122
122
  * scopes would hand every editor/viewer the unfiltered nav and a wall of 403s on