@anchrd/intel 0.59.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 (364) hide show
  1. package/README.md +496 -0
  2. package/bin/intel.mjs +134 -0
  3. package/dist/adapters/cloudflare/cloudflare-flow-workflow.d.ts +42 -0
  4. package/dist/adapters/cloudflare/cloudflare-flow-workflow.js +89 -0
  5. package/dist/adapters/cloudflare/cloudflare.d.ts +29 -0
  6. package/dist/adapters/cloudflare/cloudflare.js +416 -0
  7. package/dist/adapters/cloudflare/cloudflare.types.d.ts +31 -0
  8. package/dist/adapters/cloudflare/cloudflare.types.js +1 -0
  9. package/dist/adapters/content/content.d.ts +3 -0
  10. package/dist/adapters/content/content.js +128 -0
  11. package/dist/adapters/content/content.types.d.ts +38 -0
  12. package/dist/adapters/content/content.types.js +1 -0
  13. package/dist/adapters/db/db-audit.d.ts +62 -0
  14. package/dist/adapters/db/db-audit.js +150 -0
  15. package/dist/adapters/db/db-boards.d.ts +6 -0
  16. package/dist/adapters/db/db-boards.js +324 -0
  17. package/dist/adapters/db/db-feed.d.ts +30 -0
  18. package/dist/adapters/db/db-feed.js +276 -0
  19. package/dist/adapters/db/db-flows.d.ts +42 -0
  20. package/dist/adapters/db/db-flows.js +1311 -0
  21. package/dist/adapters/db/db-grants.d.ts +133 -0
  22. package/dist/adapters/db/db-grants.js +269 -0
  23. package/dist/adapters/db/db-indexing.d.ts +3 -0
  24. package/dist/adapters/db/db-indexing.js +200 -0
  25. package/dist/adapters/db/db-oauth.d.ts +3 -0
  26. package/dist/adapters/db/db-oauth.js +26 -0
  27. package/dist/adapters/db/db-prompts.d.ts +46 -0
  28. package/dist/adapters/db/db-prompts.js +144 -0
  29. package/dist/adapters/db/db.d.ts +6 -0
  30. package/dist/adapters/db/db.js +1687 -0
  31. package/dist/adapters/db/db.types.d.ts +16 -0
  32. package/dist/adapters/db/db.types.js +1 -0
  33. package/dist/adapters/document-converter/document-converter.d.ts +2 -0
  34. package/dist/adapters/document-converter/document-converter.js +26 -0
  35. package/dist/adapters/document-converter/document-converter.types.d.ts +17 -0
  36. package/dist/adapters/document-converter/document-converter.types.js +1 -0
  37. package/dist/adapters/flow-runtime/flow-runtime.d.ts +3 -0
  38. package/dist/adapters/flow-runtime/flow-runtime.js +22 -0
  39. package/dist/adapters/flow-runtime/flow-runtime.types.d.ts +19 -0
  40. package/dist/adapters/flow-runtime/flow-runtime.types.js +1 -0
  41. package/dist/adapters/index-queue/index-queue.d.ts +10 -0
  42. package/dist/adapters/index-queue/index-queue.js +12 -0
  43. package/dist/adapters/index-queue/index-queue.types.d.ts +11 -0
  44. package/dist/adapters/index-queue/index-queue.types.js +1 -0
  45. package/dist/adapters/json-schema/json-schema.d.ts +2 -0
  46. package/dist/adapters/json-schema/json-schema.js +77 -0
  47. package/dist/adapters/openid/openid.d.ts +5 -0
  48. package/dist/adapters/openid/openid.js +301 -0
  49. package/dist/adapters/portal-tokens/portal-tokens.d.ts +8 -0
  50. package/dist/adapters/portal-tokens/portal-tokens.js +79 -0
  51. package/dist/adapters/remote-tools/remote-tools.d.ts +5 -0
  52. package/dist/adapters/remote-tools/remote-tools.js +129 -0
  53. package/dist/adapters/semantic-index/semantic-index.d.ts +2 -0
  54. package/dist/adapters/semantic-index/semantic-index.js +139 -0
  55. package/dist/adapters/semantic-index/semantic-index.types.d.ts +56 -0
  56. package/dist/adapters/semantic-index/semantic-index.types.js +1 -0
  57. package/dist/adapters/session-cookie/session-cookie.d.ts +5 -0
  58. package/dist/adapters/session-cookie/session-cookie.js +87 -0
  59. package/dist/adapters/tool-source-policy/tool-source-policy.d.ts +1 -0
  60. package/dist/adapters/tool-source-policy/tool-source-policy.js +22 -0
  61. package/dist/audit/audit.d.ts +2 -0
  62. package/dist/audit/audit.js +87 -0
  63. package/dist/audit/audit.types.d.ts +23 -0
  64. package/dist/audit/audit.types.js +1 -0
  65. package/dist/auth/auth.d.ts +2 -0
  66. package/dist/auth/auth.js +284 -0
  67. package/dist/auth/auth.types.d.ts +108 -0
  68. package/dist/auth/auth.types.js +1 -0
  69. package/dist/boards/boards.d.ts +44 -0
  70. package/dist/boards/boards.js +447 -0
  71. package/dist/boards/boards.types.d.ts +117 -0
  72. package/dist/boards/boards.types.js +1 -0
  73. package/dist/build/build.d.ts +2 -0
  74. package/dist/build/build.js +192 -0
  75. package/dist/build/build.types.d.ts +17 -0
  76. package/dist/build/build.types.js +1 -0
  77. package/dist/bundle/bundle.d.ts +4 -0
  78. package/dist/bundle/bundle.js +1194 -0
  79. package/dist/bundle/bundle.types.d.ts +33 -0
  80. package/dist/bundle/bundle.types.js +1 -0
  81. package/dist/cli/cli.d.ts +4 -0
  82. package/dist/cli/cli.js +262 -0
  83. package/dist/cli/cli.types.d.ts +14 -0
  84. package/dist/cli/cli.types.js +1 -0
  85. package/dist/contract/audit.d.ts +50 -0
  86. package/dist/contract/audit.js +72 -0
  87. package/dist/contract/board.d.ts +150 -0
  88. package/dist/contract/board.js +262 -0
  89. package/dist/contract/bundle.d.ts +147 -0
  90. package/dist/contract/bundle.js +139 -0
  91. package/dist/contract/contract.d.ts +112 -0
  92. package/dist/contract/contract.js +143 -0
  93. package/dist/contract/feed.d.ts +126 -0
  94. package/dist/contract/feed.js +149 -0
  95. package/dist/contract/flow-run.d.ts +346 -0
  96. package/dist/contract/flow-run.js +194 -0
  97. package/dist/contract/flow.d.ts +1057 -0
  98. package/dist/contract/flow.js +552 -0
  99. package/dist/contract/node.d.ts +551 -0
  100. package/dist/contract/node.js +456 -0
  101. package/dist/contract/share.d.ts +216 -0
  102. package/dist/contract/share.js +134 -0
  103. package/dist/contract/table.d.ts +171 -0
  104. package/dist/contract/table.js +117 -0
  105. package/dist/contract/tool.d.ts +122 -0
  106. package/dist/contract/tool.js +172 -0
  107. package/dist/feed/feed.d.ts +2 -0
  108. package/dist/feed/feed.js +77 -0
  109. package/dist/feed/feed.types.d.ts +23 -0
  110. package/dist/feed/feed.types.js +1 -0
  111. package/dist/flows/flows.d.ts +41 -0
  112. package/dist/flows/flows.js +2216 -0
  113. package/dist/flows/flows.types.d.ts +339 -0
  114. package/dist/flows/flows.types.js +1 -0
  115. package/dist/http/http.d.ts +9 -0
  116. package/dist/http/http.js +874 -0
  117. package/dist/http/http.types.d.ts +22 -0
  118. package/dist/http/http.types.js +1 -0
  119. package/dist/indexing/indexing.d.ts +6 -0
  120. package/dist/indexing/indexing.js +239 -0
  121. package/dist/indexing/indexing.types.d.ts +11 -0
  122. package/dist/indexing/indexing.types.js +1 -0
  123. package/dist/intel/intel.d.ts +3 -0
  124. package/dist/intel/intel.js +194 -0
  125. package/dist/intel/intel.types.d.ts +24 -0
  126. package/dist/intel/intel.types.js +1 -0
  127. package/dist/mcp/mcp.d.ts +2 -0
  128. package/dist/mcp/mcp.js +1292 -0
  129. package/dist/mcp/mcp.types.d.ts +25 -0
  130. package/dist/mcp/mcp.types.js +1 -0
  131. package/dist/nodes/document-links/document-links.d.ts +20 -0
  132. package/dist/nodes/document-links/document-links.js +57 -0
  133. package/dist/nodes/nodes.d.ts +2 -0
  134. package/dist/nodes/nodes.js +1667 -0
  135. package/dist/nodes/nodes.types.d.ts +511 -0
  136. package/dist/nodes/nodes.types.js +1 -0
  137. package/dist/prepare/prepare.d.ts +4 -0
  138. package/dist/prepare/prepare.js +16 -0
  139. package/dist/prepare/prepare.types.d.ts +9 -0
  140. package/dist/prepare/prepare.types.js +1 -0
  141. package/dist/prompts/prompts.d.ts +2 -0
  142. package/dist/prompts/prompts.js +65 -0
  143. package/dist/prompts/prompts.types.d.ts +71 -0
  144. package/dist/prompts/prompts.types.js +1 -0
  145. package/dist/shared/callers/callers.d.ts +28 -0
  146. package/dist/shared/callers/callers.js +30 -0
  147. package/dist/shared/csv/csv.d.ts +13 -0
  148. package/dist/shared/csv/csv.js +85 -0
  149. package/dist/shared/document-text/document-text.d.ts +21 -0
  150. package/dist/shared/document-text/document-text.js +31 -0
  151. package/dist/shared/gate-authorization/gate-authorization.d.ts +5 -0
  152. package/dist/shared/gate-authorization/gate-authorization.js +26 -0
  153. package/dist/shared/grant-expiry/grant-expiry.d.ts +19 -0
  154. package/dist/shared/grant-expiry/grant-expiry.js +26 -0
  155. package/dist/shared/intel-error/intel-error.d.ts +22 -0
  156. package/dist/shared/intel-error/intel-error.js +27 -0
  157. package/dist/shared/plain-title/plain-title.d.ts +16 -0
  158. package/dist/shared/plain-title/plain-title.js +45 -0
  159. package/dist/shared/problem-details/problem-details.d.ts +30 -0
  160. package/dist/shared/problem-details/problem-details.js +18 -0
  161. package/dist/shared/report-unexpected-error/report-unexpected-error.d.ts +1 -0
  162. package/dist/shared/report-unexpected-error/report-unexpected-error.js +37 -0
  163. package/dist/shared/safe-return-path/safe-return-path.d.ts +2 -0
  164. package/dist/shared/safe-return-path/safe-return-path.js +11 -0
  165. package/dist/shared/sha256/sha256.d.ts +2 -0
  166. package/dist/shared/sha256/sha256.js +14 -0
  167. package/dist/tools/tool-servers/tool-servers.d.ts +46 -0
  168. package/dist/tools/tool-servers/tool-servers.js +177 -0
  169. package/dist/tools/tools.d.ts +2 -0
  170. package/dist/tools/tools.js +207 -0
  171. package/dist/tools/tools.types.d.ts +56 -0
  172. package/dist/tools/tools.types.js +1 -0
  173. package/examples/branding/favicon.svg +5 -0
  174. package/examples/branding/logo.svg +5 -0
  175. package/examples/branding/theme.css +16 -0
  176. package/examples/dev.vars.example +7 -0
  177. package/examples/intel.json +9 -0
  178. package/migrations/0000_intel_foundation.sql +203 -0
  179. package/migrations/0001_portal_native_tools.sql +21 -0
  180. package/migrations/0002_flows_in_the_knowledge_tree.sql +34 -0
  181. package/migrations/0003_folder_permissions.sql +211 -0
  182. package/migrations/0004_subflow_runs.sql +14 -0
  183. package/migrations/0005_flow_node_cleanup.sql +28 -0
  184. package/migrations/0005_tables_in_the_knowledge_tree.sql +68 -0
  185. package/migrations/0006_links_are_written_in_the_text.sql +20 -0
  186. package/migrations/0007_no_node_waits.sql +69 -0
  187. package/migrations/0008_three_layers.sql +130 -0
  188. package/migrations/0009_no_context_policy.sql +48 -0
  189. package/migrations/0010_no_run_waits_a_year.sql +27 -0
  190. package/migrations/0011_one_name_for_the_tree.sql +53 -0
  191. package/migrations/0012_table_snapshots.sql +29 -0
  192. package/migrations/0017_a_vector_per_card.sql +38 -0
  193. package/migrations/0018_no_context_policy_at_last.sql +97 -0
  194. package/migrations/0019_one_name_for_the_grants.sql +52 -0
  195. package/migrations/0020_cascade_purge_replay.sql +11 -0
  196. package/migrations/0021_a_flow_carries_its_own_grant.sql +44 -0
  197. package/migrations/0022_a_cursor_over_the_journal.sql +14 -0
  198. package/migrations/0023_a_board_and_its_tasks.sql +116 -0
  199. package/migrations/0024_the_archive_is_a_column.sql +33 -0
  200. package/migrations/0025_archiving_takes_the_subtree.sql +29 -0
  201. package/migrations/0026_one_feed_over_two_kinds.sql +23 -0
  202. package/migrations/0027_the_runs_of_every_flow.sql +27 -0
  203. package/migrations/0028_a_prompt_name_over_two_kinds.sql +70 -0
  204. package/package.json +118 -0
  205. package/src/contract/CLAUDE.md +152 -0
  206. package/src/contract/README.md +102 -0
  207. package/src/contract/audit.ts +85 -0
  208. package/src/contract/board.ts +306 -0
  209. package/src/contract/bundle.ts +157 -0
  210. package/src/contract/contract.ts +161 -0
  211. package/src/contract/feed.ts +165 -0
  212. package/src/contract/flow-run.ts +241 -0
  213. package/src/contract/flow.ts +675 -0
  214. package/src/contract/node.ts +583 -0
  215. package/src/contract/share.ts +196 -0
  216. package/src/contract/table.ts +178 -0
  217. package/src/contract/tool.ts +202 -0
  218. package/ui/README.md +110 -0
  219. package/ui/components.json +21 -0
  220. package/ui/index.html +13 -0
  221. package/ui/package.json +11 -0
  222. package/ui/scripts/lint-tokens.mjs +187 -0
  223. package/ui/src/access-summary/access-summary.tsx +385 -0
  224. package/ui/src/app/action-slot/action-slot.tsx +27 -0
  225. package/ui/src/app/app-sidebar/app-sidebar.tsx +71 -0
  226. package/ui/src/app/app-tree/app-tree.tsx +1175 -0
  227. package/ui/src/app/app.tsx +87 -0
  228. package/ui/src/app/header-search/header-search.tsx +294 -0
  229. package/ui/src/app/reindex-dialog/reindex-dialog.tsx +79 -0
  230. package/ui/src/app/settings-dialog/settings-dialog.tsx +135 -0
  231. package/ui/src/app/sidebar-preferences/sidebar-preferences.ts +68 -0
  232. package/ui/src/app/sidebar-preferences/sidebar-preferences.types.ts +15 -0
  233. package/ui/src/app/sidebar-resize-handle/sidebar-resize-handle.tsx +86 -0
  234. package/ui/src/app/tree-expansion/tree-expansion.ts +107 -0
  235. package/ui/src/app/tree-expansion/tree-expansion.types.ts +31 -0
  236. package/ui/src/app/tree-move/tree-move.tsx +427 -0
  237. package/ui/src/app/tree-reveal/tree-reveal.ts +64 -0
  238. package/ui/src/app/tree-reveal/tree-reveal.types.ts +16 -0
  239. package/ui/src/app/user-footer/user-footer.tsx +188 -0
  240. package/ui/src/app/view-menu/view-menu.tsx +115 -0
  241. package/ui/src/app-root/app-root.tsx +109 -0
  242. package/ui/src/app-root/app-root.types.ts +27 -0
  243. package/ui/src/archive/archive.tsx +417 -0
  244. package/ui/src/attachment-detail/attachment-detail.ts +52 -0
  245. package/ui/src/attachment-viewer/attachment-viewer.tsx +166 -0
  246. package/ui/src/blocknote-view/blocknote-view.tsx +30 -0
  247. package/ui/src/board/board-assignee/board-assignee-picker.tsx +132 -0
  248. package/ui/src/board/board-assignee/board-assignee.ts +48 -0
  249. package/ui/src/board/board-assignee/board-assignee.tsx +75 -0
  250. package/ui/src/board/board-chip/board-chip.tsx +46 -0
  251. package/ui/src/board/board-crumbs/board-crumbs.ts +56 -0
  252. package/ui/src/board/board-crumbs/board-crumbs.tsx +111 -0
  253. package/ui/src/board/board-data/board-data.ts +195 -0
  254. package/ui/src/board/board-data/board-data.types.ts +38 -0
  255. package/ui/src/board/board-dates/board-dates.ts +40 -0
  256. package/ui/src/board/board-dates/board-dates.tsx +105 -0
  257. package/ui/src/board/board-kanban/board-kanban.ts +162 -0
  258. package/ui/src/board/board-kanban/board-kanban.tsx +680 -0
  259. package/ui/src/board/board-open-task/board-open-task.ts +32 -0
  260. package/ui/src/board/board-panel/board-panel.tsx +259 -0
  261. package/ui/src/board/board-settings/board-settings.tsx +226 -0
  262. package/ui/src/board/board-status/board-status.tsx +60 -0
  263. package/ui/src/board/board-stripes/board-stripes.ts +128 -0
  264. package/ui/src/board/board-table/board-table.ts +53 -0
  265. package/ui/src/board/board-table/board-table.tsx +610 -0
  266. package/ui/src/board/board-task/board-task.ts +108 -0
  267. package/ui/src/board/board-task/board-task.tsx +335 -0
  268. package/ui/src/board/board-title-row/board-title-row.tsx +66 -0
  269. package/ui/src/branding/branding.tsx +17 -0
  270. package/ui/src/branding/custom-logo.ts +1 -0
  271. package/ui/src/branding/favicon.default.svg +5 -0
  272. package/ui/src/branding/favicon.svg +5 -0
  273. package/ui/src/components/ui/avatar.tsx +39 -0
  274. package/ui/src/components/ui/button.tsx +64 -0
  275. package/ui/src/components/ui/collapsible.tsx +20 -0
  276. package/ui/src/components/ui/command.tsx +160 -0
  277. package/ui/src/components/ui/dialog.tsx +143 -0
  278. package/ui/src/components/ui/dropdown-menu.tsx +187 -0
  279. package/ui/src/components/ui/input.tsx +21 -0
  280. package/ui/src/components/ui/popover.tsx +41 -0
  281. package/ui/src/components/ui/select.tsx +163 -0
  282. package/ui/src/components/ui/separator.tsx +26 -0
  283. package/ui/src/components/ui/sheet.tsx +136 -0
  284. package/ui/src/components/ui/sidebar.tsx +693 -0
  285. package/ui/src/components/ui/skeleton.tsx +13 -0
  286. package/ui/src/components/ui/switch.tsx +25 -0
  287. package/ui/src/components/ui/table.tsx +82 -0
  288. package/ui/src/components/ui/tabs.tsx +52 -0
  289. package/ui/src/components/ui/tooltip.tsx +51 -0
  290. package/ui/src/data/intel-data-provider/intel-data-provider.ts +685 -0
  291. package/ui/src/data/intel-data-provider/intel-data-provider.types.ts +266 -0
  292. package/ui/src/data/query-client.ts +18 -0
  293. package/ui/src/data/request-refusal/refusal-notice.tsx +36 -0
  294. package/ui/src/data/request-refusal/request-refusal.ts +51 -0
  295. package/ui/src/data/sign-in/sign-in.ts +97 -0
  296. package/ui/src/data/sign-in/sign-in.types.ts +15 -0
  297. package/ui/src/document-link/document-link.tsx +114 -0
  298. package/ui/src/document-markdown/document-markdown.ts +195 -0
  299. package/ui/src/editor-schema/editor-schema.ts +23 -0
  300. package/ui/src/entry-picker/entry-picker.tsx +238 -0
  301. package/ui/src/feed/feed.tsx +344 -0
  302. package/ui/src/file-preview/file-preview-view.tsx +496 -0
  303. package/ui/src/file-preview/file-preview.tsx +28 -0
  304. package/ui/src/file-preview/pdf-file-preview.tsx +5 -0
  305. package/ui/src/file-preview/presentation-file-preview.tsx +21 -0
  306. package/ui/src/file-preview/spreadsheet-file-preview.tsx +5 -0
  307. package/ui/src/file-preview/word-file-preview.tsx +5 -0
  308. package/ui/src/flow-runs/flow-runs.tsx +238 -0
  309. package/ui/src/flows/flows.tsx +1646 -0
  310. package/ui/src/flows/node-icon/node-icon.ts +34 -0
  311. package/ui/src/flows/node-palette/node-palette.tsx +214 -0
  312. package/ui/src/flows/node-palette/node-palette.types.ts +15 -0
  313. package/ui/src/folder-contents/folder-contents.tsx +321 -0
  314. package/ui/src/folder-index/folder-index.ts +55 -0
  315. package/ui/src/frontmatter/frontmatter.tsx +193 -0
  316. package/ui/src/graph-pane/graph-pane.tsx +53 -0
  317. package/ui/src/hooks/use-mobile.ts +19 -0
  318. package/ui/src/i18n/custom.json +4 -0
  319. package/ui/src/i18n/de.json +609 -0
  320. package/ui/src/i18n/en.json +609 -0
  321. package/ui/src/i18n/es.json +609 -0
  322. package/ui/src/i18n/i18n-context.tsx +65 -0
  323. package/ui/src/i18n/i18n-languages/i18n-languages.ts +83 -0
  324. package/ui/src/i18n/i18n.ts +32 -0
  325. package/ui/src/i18n/i18n.types.ts +49 -0
  326. package/ui/src/kind-icon.ts +97 -0
  327. package/ui/src/lib/utils.ts +6 -0
  328. package/ui/src/main.tsx +68 -0
  329. package/ui/src/modal/modal.tsx +53 -0
  330. package/ui/src/node-details/node-details.tsx +545 -0
  331. package/ui/src/node-editor/node-editor.tsx +304 -0
  332. package/ui/src/node-graph/graph-notice.tsx +21 -0
  333. package/ui/src/node-graph/node-graph.ts +214 -0
  334. package/ui/src/node-graph/node-graph.tsx +295 -0
  335. package/ui/src/node-import/node-import.tsx +418 -0
  336. package/ui/src/node-save-error.ts +46 -0
  337. package/ui/src/node-source/node-source.tsx +128 -0
  338. package/ui/src/node-table/node-table.tsx +107 -0
  339. package/ui/src/nodes/nodes.tsx +240 -0
  340. package/ui/src/rename-resource/rename-resource.ts +80 -0
  341. package/ui/src/resolved-names/resolved-names.tsx +67 -0
  342. package/ui/src/resource-error.ts +33 -0
  343. package/ui/src/resource-menu/resource-menu.tsx +1156 -0
  344. package/ui/src/router/router-context.ts +6 -0
  345. package/ui/src/router/router.tsx +67 -0
  346. package/ui/src/router/router.types.ts +8 -0
  347. package/ui/src/router/selection-search.ts +88 -0
  348. package/ui/src/save-button/save-button.tsx +94 -0
  349. package/ui/src/section-hint/section-hint.tsx +40 -0
  350. package/ui/src/sign-in-refused/sign-in-refused.tsx +35 -0
  351. package/ui/src/styles.css +189 -0
  352. package/ui/src/table-columns/table-columns.tsx +379 -0
  353. package/ui/src/theme/custom.css +1 -0
  354. package/ui/src/theme/theme-context.tsx +89 -0
  355. package/ui/src/theme/theme.ts +70 -0
  356. package/ui/src/time/relative-time.tsx +41 -0
  357. package/ui/src/time/time-context.tsx +78 -0
  358. package/ui/src/time/time.ts +165 -0
  359. package/ui/src/title-row/title-row.tsx +185 -0
  360. package/ui/src/tools/tools.tsx +546 -0
  361. package/ui/src/user-name/user-name.ts +61 -0
  362. package/ui/src/verbatim/verbatim.tsx +68 -0
  363. package/ui/tsconfig.json +17 -0
  364. package/ui/vite.config.ts +129 -0
@@ -0,0 +1,385 @@
1
+ import type { ResourceGrant } from "@contract/share.ts";
2
+ import { useQuery } from "@tanstack/react-query";
3
+ import { useId, useState } from "react";
4
+ import { createPortal } from "react-dom";
5
+ import { AppLogo } from "@/branding/branding.tsx";
6
+ import {
7
+ Tooltip,
8
+ TooltipContent,
9
+ TooltipProvider,
10
+ TooltipTrigger,
11
+ } from "@/components/ui/tooltip.tsx";
12
+ import type { IntelDataProvider } from "@/data/intel-data-provider/intel-data-provider.types.ts";
13
+ import { useI18n } from "@/i18n/i18n-context.tsx";
14
+ import { ResolvedNamesProvider, useResolvedName } from "@/resolved-names/resolved-names.tsx";
15
+ import { useIntelRouterContext } from "@/router/router-context.ts";
16
+ import { initials, useUserName } from "@/user-name/user-name.ts";
17
+
18
+ type Principal = ResourceGrant["principal"];
19
+
20
+ type GrantGroup = {
21
+ key: string;
22
+ principal: Principal;
23
+ grants: ResourceGrant[];
24
+ owner: boolean;
25
+ };
26
+
27
+ function principalKey(principal: Principal): string {
28
+ if (principal.type === "organization") return "organization";
29
+ if (principal.type === "email") return `email:${principal.email.toLowerCase()}`;
30
+ return `user:${principal.id}`;
31
+ }
32
+
33
+ function groupGrants(grants: ResourceGrant[], ownerIds: string[] = []): GrantGroup[] {
34
+ const grouped = new Map<string, GrantGroup>();
35
+ for (const ownerId of ownerIds) {
36
+ grouped.set(`user:${ownerId}`, {
37
+ key: `user:${ownerId}`,
38
+ principal: { type: "user", id: ownerId },
39
+ grants: [],
40
+ owner: true,
41
+ });
42
+ }
43
+ for (const grant of grants) {
44
+ const key = principalKey(grant.principal);
45
+ const group = grouped.get(key);
46
+ if (group) group.grants.push(grant);
47
+ else grouped.set(key, { key, principal: grant.principal, grants: [grant], owner: false });
48
+ }
49
+ return [...grouped.values()];
50
+ }
51
+
52
+ /**
53
+ * What to call whoever a grant is for.
54
+ *
55
+ * ⚠️ **TWO sources for a user, and the order matters** — the same rule `useAssigneeLabel` follows,
56
+ * because a person must not be called one thing on a card and another on the share screen.
57
+ * `useUserName` answers for the reader themselves out of the session the shell already holds,
58
+ * without a request; the resolved map answers for everybody else. Asking the map first would make
59
+ * the reader's own name depend on a round trip that has no reason to exist, and would leave them
60
+ * nameless wherever no provider stands.
61
+ *
62
+ * ⚠️ An `email` principal is NOT resolved to a name. The address is what was granted, and it is
63
+ * what can be withdrawn — a name over it would hide which of two spellings the grant actually
64
+ * carries (#431).
65
+ */
66
+ function usePrincipalLabel(principal: Principal): string {
67
+ const i18n = useI18n();
68
+ const userId = principal.type === "user" ? principal.id : null;
69
+ const own = useUserName(userId);
70
+ const resolved = useResolvedName(userId);
71
+ if (principal.type === "organization") return i18n.t("node.organization");
72
+ if (principal.type === "email") return principal.email;
73
+ // ⚠️ The fallback stays what it was: a person nobody can name is "a user account", never the
74
+ // principal id the row carries (#258).
75
+ return own ?? resolved ?? i18n.t("node.someUser");
76
+ }
77
+
78
+ const circleTones = [
79
+ "bg-muted text-muted-foreground",
80
+ "bg-accent text-accent-foreground",
81
+ "bg-secondary text-secondary-foreground",
82
+ ] as const;
83
+
84
+ function AccessCircle({
85
+ group,
86
+ position,
87
+ count,
88
+ expanded,
89
+ controls,
90
+ activate,
91
+ }: {
92
+ group: GrantGroup;
93
+ position: number;
94
+ count: number;
95
+ expanded: boolean;
96
+ controls: string;
97
+ activate(element: HTMLButtonElement): void;
98
+ }) {
99
+ const i18n = useI18n();
100
+ const label = usePrincipalLabel(group.principal);
101
+ const circle = (
102
+ <button
103
+ type="button"
104
+ aria-label={position === 0 ? `${i18n.t("node.accessDetails", { count })}: ${label}` : label}
105
+ aria-expanded={expanded}
106
+ aria-controls={controls}
107
+ data-organization-access={group.principal.type === "organization" ? "" : undefined}
108
+ onClick={(event) => activate(event.currentTarget)}
109
+ className={`relative flex size-7 items-center justify-center rounded-full text-xs font-medium ring-2 ring-background outline-none transition hover:z-10 hover:scale-105 focus-visible:z-10 focus-visible:ring-ring ${circleTones[position % circleTones.length]}`}
110
+ >
111
+ {group.principal.type === "organization" ? (
112
+ <AppLogo className="size-4 rounded-none bg-transparent text-current [&_svg]:size-3" />
113
+ ) : (
114
+ initials(label)
115
+ )}
116
+ </button>
117
+ );
118
+ return (
119
+ <TooltipProvider delayDuration={300}>
120
+ <Tooltip>
121
+ <TooltipTrigger asChild>{circle}</TooltipTrigger>
122
+ <TooltipContent>
123
+ {group.principal.type === "organization" ? i18n.t("node.organizationAccess") : label}
124
+ </TooltipContent>
125
+ </Tooltip>
126
+ </TooltipProvider>
127
+ );
128
+ }
129
+
130
+ function AccessDetail({
131
+ group,
132
+ revocableOn,
133
+ sources,
134
+ onRevoke,
135
+ }: {
136
+ group: GrantGroup;
137
+ // ⚠️ The node whose grants this screen may withdraw. A grant that sits on a folder ABOVE is in
138
+ // force here but is not this screen's to take away — the reader may not even be allowed to manage
139
+ // that folder. It is shown, and it is shown as inherited, and no button is offered for it (#803).
140
+ revocableOn?: string;
141
+ // The folders the inherited grants sit on, for the ones this reader may see. A grant whose folder
142
+ // is missing here is still shown as inherited — just without a name to go to.
143
+ sources?: ReadonlyArray<{ resourceId: string; title: string }>;
144
+ onRevoke?(grantId: string): void;
145
+ }) {
146
+ const i18n = useI18n();
147
+ const label = usePrincipalLabel(group.principal);
148
+ const inherited = group.grants.filter((grant) => grant.resourceId !== revocableOn);
149
+ const here = group.grants.filter((grant) => grant.resourceId === revocableOn);
150
+ const from = [
151
+ ...new Set(
152
+ inherited.flatMap((grant) => {
153
+ const source = sources?.find((entry) => entry.resourceId === grant.resourceId);
154
+ return source ? [source.title] : [];
155
+ }),
156
+ ),
157
+ ];
158
+ return (
159
+ <li className="flex items-start justify-between gap-3 text-sm">
160
+ <span className="min-w-0">
161
+ <span className="block truncate font-medium">{label}</span>
162
+ <span className="text-xs text-muted-foreground">
163
+ {[
164
+ ...(group.owner ? [i18n.t("node.owner")] : []),
165
+ ...group.grants.map((grant) => i18n.t(`node.verb.${grant.verb}`)),
166
+ ].join(", ")}
167
+ </span>
168
+ {/* Said once for the whole person, not once per verb: three inherited verbs are one fact
169
+ about where the access lives, and three copies of it would read as three problems. */}
170
+ {revocableOn !== undefined && inherited.length > 0 ? (
171
+ <span className="mt-0.5 block text-xs text-muted-foreground">
172
+ {from.length > 0
173
+ ? i18n.t("node.accessInheritedFrom", { titles: from.join(", ") })
174
+ : i18n.t("node.accessInherited")}
175
+ </span>
176
+ ) : null}
177
+ </span>
178
+ {onRevoke && here.length > 0 ? (
179
+ <span className="flex flex-wrap justify-end gap-1">
180
+ {here.map((grant) => (
181
+ <button
182
+ key={grant.id}
183
+ type="button"
184
+ onClick={() => onRevoke(grant.id)}
185
+ aria-label={i18n.t("node.revokeVerbFor", {
186
+ verb: i18n.t(`node.verb.${grant.verb}`),
187
+ recipient: label,
188
+ })}
189
+ className="rounded px-2 py-1 text-xs text-destructive outline-none hover:bg-muted focus-visible:ring-2 focus-visible:ring-ring"
190
+ >
191
+ {i18n.t("node.revokeVerb", { verb: i18n.t(`node.verb.${grant.verb}`) })}
192
+ </button>
193
+ ))}
194
+ </span>
195
+ ) : null}
196
+ </li>
197
+ );
198
+ }
199
+
200
+ export function AccessSummary({
201
+ grants,
202
+ ownerId,
203
+ ownerIds,
204
+ revocableOn,
205
+ sources,
206
+ onRevoke,
207
+ }: {
208
+ grants: ResourceGrant[];
209
+ ownerId?: string;
210
+ ownerIds?: string[];
211
+ sources?: ReadonlyArray<{ resourceId: string; title: string }>;
212
+ // Where withdrawal is possible — see `AccessDetail`. Absent on the read-only summary, where no
213
+ // row carries a button and the distinction has nothing to change.
214
+ revocableOn?: string;
215
+ onRevoke?(grantId: string): void;
216
+ }) {
217
+ const i18n = useI18n();
218
+ const groups = groupGrants(grants, ownerIds ?? (ownerId ? [ownerId] : []));
219
+ const [open, setOpen] = useState(false);
220
+ const [position, setPosition] = useState({ top: 8, left: 8 });
221
+ const detailsId = useId();
222
+ const organizationDescriptionId = useId();
223
+ if (groups.length === 0) return null;
224
+ const visible = groups.slice(0, 3);
225
+ const overflow = groups.length - visible.length;
226
+ const toggle = (element: HTMLButtonElement) => {
227
+ setOpen((current) => {
228
+ if (!current) {
229
+ const rect = element.getBoundingClientRect();
230
+ const panelHeight = Math.min(320, window.innerHeight - 16);
231
+ const below = window.innerHeight - rect.bottom;
232
+ setPosition({
233
+ top: below >= panelHeight ? rect.bottom + 4 : Math.max(8, rect.top - panelHeight - 4),
234
+ left: Math.max(8, Math.min(rect.left, window.innerWidth - 328)),
235
+ });
236
+ }
237
+ return !current;
238
+ });
239
+ };
240
+
241
+ // ⚠️ ONE request for the whole summary, not one per circle: every person this component draws is
242
+ // asked for together, and `usePrincipalLabel` reads the answer out of the context rather than
243
+ // fetching. The circles and the detail list share the provider, so opening the panel adds no
244
+ // request (#803).
245
+ //
246
+ // ⚠️ Only `user` principals travel. An organization has no name to resolve, and an `email` grant
247
+ // is shown as the address it was granted to — resolving it would hide which spelling is in force.
248
+ const personIds = groups.flatMap((group) =>
249
+ group.principal.type === "user" ? [group.principal.id] : [],
250
+ );
251
+
252
+ return (
253
+ <ResolvedNamesProvider ids={personIds}>
254
+ <div className="relative flex w-fit items-center -space-x-2" data-access-summary="">
255
+ {visible.map((group, position) => (
256
+ <AccessCircle
257
+ key={group.key}
258
+ group={group}
259
+ position={position}
260
+ count={groups.length}
261
+ expanded={open}
262
+ controls={detailsId}
263
+ activate={toggle}
264
+ />
265
+ ))}
266
+ {overflow > 0 ? (
267
+ <button
268
+ type="button"
269
+ aria-label={i18n.t("node.accessDetails", { count: groups.length })}
270
+ aria-expanded={open}
271
+ aria-controls={detailsId}
272
+ onClick={(event) => toggle(event.currentTarget)}
273
+ className="relative flex size-7 items-center justify-center rounded-full bg-muted text-xs font-medium text-muted-foreground ring-2 ring-background outline-none transition hover:z-10 hover:scale-105 focus-visible:z-10 focus-visible:ring-ring"
274
+ >
275
+ +{overflow}
276
+ </button>
277
+ ) : null}
278
+ {groups.some((group) => group.principal.type === "organization") ? (
279
+ <span id={organizationDescriptionId} className="sr-only">
280
+ {i18n.t("node.organizationAccess")}
281
+ </span>
282
+ ) : null}
283
+ {open
284
+ ? createPortal(
285
+ <div
286
+ id={detailsId}
287
+ data-access-details=""
288
+ className="fixed z-50 max-h-[calc(100dvh-1rem)] w-80 overflow-y-auto rounded-md border bg-popover p-4 text-popover-foreground shadow-md"
289
+ style={position}
290
+ >
291
+ <ul className="space-y-3">
292
+ {groups.map((group) => (
293
+ <AccessDetail
294
+ key={group.key}
295
+ group={group}
296
+ {...(revocableOn !== undefined ? { revocableOn } : {})}
297
+ {...(sources ? { sources } : {})}
298
+ {...(onRevoke ? { onRevoke } : {})}
299
+ />
300
+ ))}
301
+ </ul>
302
+ </div>,
303
+ document.querySelector('[role="dialog"]') ?? document.body,
304
+ )
305
+ : null}
306
+ </div>
307
+ </ResolvedNamesProvider>
308
+ );
309
+ }
310
+
311
+ /**
312
+ * The ONE definition of the effective-access query (#817).
313
+ *
314
+ * ⚠️ It is a function rather than a call so that a second reader can watch the same answers without
315
+ * asking for them a second time. A folder table waits until every one of its rows has been decided
316
+ * before it draws itself, and it does that by observing these exact keys — same key, same cache
317
+ * entry, one request per resource. A copy of the options over there would share the key and drift
318
+ * on everything else, which is the quietest way to end up with two different `retry` settings.
319
+ *
320
+ * ⚠️ Both kinds share the `effective-node-grants` prefix on purpose: a share mutation invalidates
321
+ * that prefix, and a flow grant changes what a folder summary above it shows just as a folder grant
322
+ * changes what the flow below it shows. Two prefixes would mean one of the two keeps drawing an
323
+ * access picture that is no longer in force.
324
+ *
325
+ * ⚠️ `retry: false` is part of the contract this query has with its readers, not a tuning knob. A
326
+ * refusal here is an answer (#484), and a reader waiting for "every row decided" needs the failing
327
+ * ones to arrive as failures rather than to be tried again behind its back.
328
+ *
329
+ * ⚠️ `retryOnMount: false` says the same thing about a SECOND reader, and without it the two
330
+ * readers feed each other. `staleTime` governs data, not errors, so a failed query is refetched
331
+ * every time a fresh observer mounts on it — and the folder table mounts one the instant the
332
+ * failure lets it draw its rows. Measured (#817): error, rows, mount, refetch, pending, rows gone,
333
+ * unmount, error again — 285 transitions and still going, none of them visible as anything but a
334
+ * table that would not appear. A failure that is final for the first reader is final for the next.
335
+ */
336
+ export function effectiveAccessQuery(
337
+ data: IntelDataProvider,
338
+ input: { resourceId: string; kind: "node" | "flow" },
339
+ ) {
340
+ return {
341
+ queryKey: ["effective-node-grants", input.kind, input.resourceId],
342
+ queryFn: () =>
343
+ input.kind === "flow"
344
+ ? data.listEffectiveFlowAccess(input.resourceId)
345
+ : data.listEffectiveAccess(input.resourceId),
346
+ retry: false,
347
+ retryOnMount: false,
348
+ // Time cannot change this answer locally. Share mutations invalidate the whole effective prefix
349
+ // because changing one folder also changes every descendant summary already on screen.
350
+ staleTime: Number.POSITIVE_INFINITY,
351
+ };
352
+ }
353
+
354
+ /**
355
+ * The compact access view for a resource the reader can manage.
356
+ *
357
+ * `node_grant_list` is itself authorized: a reader who may see the node but may not inspect its
358
+ * grants gets no names here. Treating that refusal as an empty summary is deliberate; rendering an
359
+ * error or retry would disclose that there are grants to ask about (#484).
360
+ */
361
+ export function ResourceAccessSummary({
362
+ resourceId,
363
+ ownerId,
364
+ kind = "node",
365
+ }: {
366
+ resourceId: string;
367
+ ownerId: string;
368
+ // Which side of the tree the id belongs to (#530). A flow is not a node, so it is a different
369
+ // route — but the same answer, which is why one component draws both.
370
+ kind?: "node" | "flow";
371
+ }) {
372
+ const { data } = useIntelRouterContext();
373
+ const grants = useQuery(effectiveAccessQuery(data, { resourceId, kind }));
374
+ if (!grants.data) return null;
375
+ return (
376
+ <AccessSummary
377
+ grants={grants.data.items}
378
+ ownerIds={
379
+ grants.data.ownerIds.includes(ownerId)
380
+ ? grants.data.ownerIds
381
+ : [ownerId, ...grants.data.ownerIds]
382
+ }
383
+ />
384
+ );
385
+ }
@@ -0,0 +1,27 @@
1
+ import type * as React from "react";
2
+ import { useEffect, useState } from "react";
3
+ import { createPortal } from "react-dom";
4
+
5
+ // A bar owns a place for actions, and whoever owns the action renders into it from wherever its
6
+ // state happens to live. Two such places exist: `header-actions` in the shell, beside the
7
+ // breadcrumb, for what belongs to the area; and `title-actions` in the title line of an open
8
+ // document, for what belongs to that one document (#24).
9
+ //
10
+ // ⚠️ The slot lives above the component that fills it, so it is not in the document while that
11
+ // component first renders. Looking it up in an effect costs one extra render and is the only order
12
+ // that works; reading it during render finds nothing on the first paint.
13
+ //
14
+ // ⚠️ A portal only ever appends, so the slot decides where its content sits and mount order decides
15
+ // nothing. That is why the shell gives the screens a box of their own next to the search rather
16
+ // than one shared list (see `app.tsx`, #56).
17
+ export function ActionSlot({
18
+ name = "header-actions",
19
+ children,
20
+ }: {
21
+ name?: string;
22
+ children: React.ReactNode;
23
+ }) {
24
+ const [slot, setSlot] = useState<Element | null>(null);
25
+ useEffect(() => setSlot(document.querySelector(`[data-slot="${name}"]`)), [name]);
26
+ return slot ? createPortal(children, slot) : null;
27
+ }
@@ -0,0 +1,71 @@
1
+ import { Link } from "@tanstack/react-router";
2
+ import { AppTree } from "@/app/app-tree/app-tree.tsx";
3
+ import { SidebarResizeHandle } from "@/app/sidebar-resize-handle/sidebar-resize-handle.tsx";
4
+ import { UserFooter } from "@/app/user-footer/user-footer.tsx";
5
+ import { AppLogo } from "@/branding/branding.tsx";
6
+ import {
7
+ Sidebar,
8
+ SidebarContent,
9
+ SidebarFooter,
10
+ SidebarHeader,
11
+ SidebarMenu,
12
+ SidebarMenuButton,
13
+ SidebarMenuItem,
14
+ useSidebar,
15
+ } from "@/components/ui/sidebar";
16
+ import { useI18n } from "@/i18n/i18n-context.tsx";
17
+
18
+ // One tree is the whole navigation: documents and flows side by side in the folder they share, with
19
+ // no Intelligence or Flows entry above them (ADR-0004). Tools stay below, next to the user, because
20
+ // they come live from the portal and have no tree.
21
+ export function AppSidebar({
22
+ width,
23
+ onWidthChange,
24
+ }: {
25
+ width: number;
26
+ onWidthChange(width: number): void;
27
+ }) {
28
+ const i18n = useI18n();
29
+ // `offcanvas` rather than `icon`: the icon rail is the mode for a list of equal entries with one
30
+ // icon each, and a tree says what it says through indentation, disclosure arrows and nesting —
31
+ // none of which survives 48px. Collapsing exists to free the canvas, so the sidebar goes.
32
+ //
33
+ // ⚠️ Offcanvas only moves the panel out of sight; it stays in the document and keeps its focus
34
+ // order, so a keyboard would still walk through an invisible tree. The shell therefore unmounts
35
+ // the panel's content itself — the registry file is off limits. On a narrow screen the sidebar is
36
+ // an overlay driven by `openMobile`, and `state` says nothing about it, hence the `isMobile` arm.
37
+ const { state, isMobile } = useSidebar();
38
+ const showPanel = isMobile || state === "expanded";
39
+
40
+ return (
41
+ <Sidebar collapsible="offcanvas" variant="inset">
42
+ {!showPanel ? null : (
43
+ <>
44
+ <SidebarHeader>
45
+ <SidebarMenu>
46
+ <SidebarMenuItem>
47
+ <SidebarMenuButton size="lg" asChild>
48
+ <Link to="/nodes">
49
+ <AppLogo className="size-8" />
50
+ <span className="font-semibold">{i18n.t("app.name")}</span>
51
+ </Link>
52
+ </SidebarMenuButton>
53
+ </SidebarMenuItem>
54
+ </SidebarMenu>
55
+ </SidebarHeader>
56
+ <SidebarContent aria-label={i18n.t("nav.primary")}>
57
+ <AppTree />
58
+ </SidebarContent>
59
+ <SidebarFooter>
60
+ <UserFooter />
61
+ </SidebarFooter>
62
+ <SidebarResizeHandle
63
+ width={width}
64
+ label={i18n.t("shell.resizeSidebar")}
65
+ onWidthChange={onWidthChange}
66
+ />
67
+ </>
68
+ )}
69
+ </Sidebar>
70
+ );
71
+ }