@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,344 @@
1
+ import { type FeedEvent, feedKindOf } from "@contract/feed.ts";
2
+ import { useInfiniteQuery, useQueries } from "@tanstack/react-query";
3
+ import { useNavigate } from "@tanstack/react-router";
4
+ import { Check, Filter } from "lucide-react";
5
+ import { useEffect, useMemo, useRef, useState } from "react";
6
+ import {
7
+ DropdownMenu,
8
+ DropdownMenuContent,
9
+ DropdownMenuItem,
10
+ DropdownMenuLabel,
11
+ DropdownMenuTrigger,
12
+ } from "@/components/ui/dropdown-menu";
13
+ import { useI18n } from "@/i18n/i18n-context.tsx";
14
+ import { cn } from "@/lib/utils.ts";
15
+ import { useIntelRouterContext } from "@/router/router-context.ts";
16
+ import { RelativeTime } from "@/time/relative-time.tsx";
17
+ import { initials, useSessionUser } from "@/user-name/user-name.ts";
18
+
19
+ const PageSize = 30;
20
+
21
+ // How many steps of the trail a card shows.
22
+ //
23
+ // ⚠️ The LAST ones, not the first, and that is the whole decision (#758). A path answers "where does
24
+ // this sit", and the answer is at its end: `… / system / Kundenwissen` says where `Kunde A` lives,
25
+ // `Agenten / Anton Anchrd / sys…` says where the tree begins, which every card shares. Cutting from
26
+ // the front is what `truncate` does on its own, so this has to be decided here rather than left to
27
+ // CSS.
28
+ const TrailSteps = 2;
29
+
30
+ /** The tail of a trail, with a leading ellipsis where something was cut. */
31
+ export function shortTrail(titles: readonly string[]): string {
32
+ if (titles.length <= TrailSteps) return titles.join(" / ");
33
+ return `… / ${titles.slice(-TrailSteps).join(" / ")}`;
34
+ }
35
+
36
+ // Who to show. Only the first two reach the server; `people` is applied here because whether an
37
+ // actor is a machine is Gate's answer and arrives with the names, not with the events.
38
+ type Audience = "all" | "mine" | "people";
39
+
40
+ /**
41
+ * What happened, newest first, one card per event.
42
+ *
43
+ * ⚠️ **The shape is the archive's** — a header with a rule that appears on scroll, then the
44
+ * scrolling area — because this is a page of the same kind and a second layout for it would be a
45
+ * second set of answers to "where does the title go". What differs is inside: the list is capped
46
+ * and centred rather than full width.
47
+ */
48
+ export function Feed() {
49
+ const { data } = useIntelRouterContext();
50
+ const i18n = useI18n();
51
+ const me = useSessionUser();
52
+ const navigate = useNavigate();
53
+ const [audience, setAudience] = useState<Audience>("all");
54
+ const [scrolled, setScrolled] = useState(false);
55
+
56
+ // ⚠️ The audience belongs in the key. "Only mine" is a different question with a different
57
+ // answer, and sharing one cache entry would show the wrong list for a frame after the switch.
58
+ //
59
+ // ⚠️ `mine` reaches the server, `people` does not: the server has no idea which actor is a
60
+ // machine. Putting `people` in the key anyway would split the cache for a filter that changes
61
+ // nothing about the request — so it is deliberately absent from what travels.
62
+ const actor = audience === "mine" ? (me?.id ?? null) : null;
63
+ const feed = useInfiniteQuery({
64
+ queryKey: ["feed", actor],
65
+ queryFn: ({ pageParam }) =>
66
+ data.listFeed({
67
+ limit: PageSize,
68
+ ...(pageParam ? { before: pageParam } : {}),
69
+ ...(actor ? { actor } : {}),
70
+ }),
71
+ initialPageParam: null as string | null,
72
+ getNextPageParam: (last) => last.nextCursor,
73
+ // Asking with `actor: null` for "only mine" would be the whole feed, which is the opposite of
74
+ // what was asked for. Better to render nothing for the instant before the session arrives.
75
+ enabled: audience !== "mine" || me !== null,
76
+ });
77
+
78
+ const events = useMemo(() => feed.data?.pages.flatMap((page) => page.events) ?? [], [feed.data]);
79
+
80
+ // ⚠️ One resolve per PAGE, and PER page rather than over everything loaded so far. A page is
81
+ // thirty cards and often three people, so resolving a single id per card would open thirty
82
+ // requests for three names — that much is obvious. The half that is not: asking again for the
83
+ // GROWING union on every page walks straight into the contract's `max(100)` and takes the endless
84
+ // scroll down with a Zod error, on exactly the busy folder the feed is for.
85
+ //
86
+ // One query per page keeps every ask at most a page wide, and TanStack keeps the earlier answers
87
+ // rather than re-fetching them.
88
+ const nameQueries = useQueries({
89
+ queries: (feed.data?.pages ?? []).map((page) => {
90
+ const ids = [...new Set(page.events.map((entry) => entry.actorId))].sort();
91
+ return {
92
+ queryKey: ["feed-actor-names", ids],
93
+ // ⚠️ Never reached with an empty list: the contract refuses it (`min(1)`), and the refusal
94
+ // would surface as a broken page rather than as the empty answer it means.
95
+ enabled: ids.length > 0,
96
+ queryFn: () => data.resolveNames(ids),
97
+ staleTime: 5 * 60 * 1000,
98
+ };
99
+ }),
100
+ });
101
+ // ⚠️ Built on every render rather than memoised, and the first attempt at this is why it says
102
+ // so: keyed on `feed.data` the map was never rebuilt when the NAMES arrived, because the pages
103
+ // had not changed — every card showed "a user account" for good. A few dozen entries cost
104
+ // nothing to rebuild, and a dependency that has to be exactly right to be correct is a worse
105
+ // trade than the work it saves.
106
+ const people = new Map<string, { name: string; isMachine: boolean }>();
107
+ for (const query of nameQueries) {
108
+ for (const person of query.data?.items ?? []) {
109
+ people.set(person.id, { name: person.name, isMachine: person.isMachine });
110
+ }
111
+ }
112
+
113
+ // ⚠️ Filtering people out here can empty a whole page while older ones still hold cards, so the
114
+ // sentinel below has to keep asking rather than stopping at the first empty result.
115
+ const shown =
116
+ audience === "people"
117
+ ? events.filter((event) => people.get(event.actorId)?.isMachine !== true)
118
+ : events;
119
+
120
+ const sentinel = useRef<HTMLDivElement | null>(null);
121
+ useEffect(() => {
122
+ const element = sentinel.current;
123
+ if (!element) return;
124
+ const observer = new IntersectionObserver((entries) => {
125
+ // `hasNextPage` and `isFetchingNextPage` are read inside the callback rather than closed over
126
+ // at setup: the observer outlives several renders, and a stale `false` would end the scroll
127
+ // silently at whatever page happened to be loaded when it was created.
128
+ if (entries.some((entry) => entry.isIntersecting)) void feed.fetchNextPage();
129
+ });
130
+ observer.observe(element);
131
+ return () => observer.disconnect();
132
+ }, [feed.fetchNextPage]);
133
+
134
+ return (
135
+ <div className="flex min-h-0 flex-1 flex-col">
136
+ {/* The rule belongs to the scroll state, and the width is set from the start so that showing
137
+ it moves nothing (#741). */}
138
+ <div
139
+ data-slot="pane-header"
140
+ className={cn(
141
+ "flex items-start justify-between gap-4 border-b border-transparent px-6 py-4 transition-colors motion-reduce:transition-none",
142
+ scrolled && "border-border",
143
+ )}
144
+ >
145
+ <div>
146
+ <h1 className="text-lg font-medium text-foreground">{i18n.t("feed.title")}</h1>
147
+ <p className="mt-1 max-w-prose text-sm text-muted-foreground">
148
+ {i18n.t("feed.description")}
149
+ </p>
150
+ </div>
151
+ <DropdownMenu>
152
+ <DropdownMenuTrigger
153
+ aria-label={i18n.t("feed.filter")}
154
+ className="rounded-md p-1 text-muted-foreground outline-none hover:text-foreground focus-visible:ring-2 focus-visible:ring-ring"
155
+ >
156
+ <Filter aria-hidden="true" className="size-4" />
157
+ </DropdownMenuTrigger>
158
+ <DropdownMenuContent align="end">
159
+ <DropdownMenuLabel>{i18n.t("feed.filter")}</DropdownMenuLabel>
160
+ {(["all", "mine", "people"] as const).map((option) => (
161
+ <DropdownMenuItem
162
+ key={option}
163
+ onSelect={() => setAudience(option)}
164
+ className="gap-2"
165
+ data-slot={`feed-audience-${option}`}
166
+ aria-current={audience === option}
167
+ >
168
+ <Check
169
+ aria-hidden="true"
170
+ className={cn("size-4", audience !== option && "invisible")}
171
+ />
172
+ {i18n.t(`feed.audience.${option}`)}
173
+ </DropdownMenuItem>
174
+ ))}
175
+ </DropdownMenuContent>
176
+ </DropdownMenu>
177
+ </div>
178
+
179
+ <div
180
+ data-slot="pane-body"
181
+ onScroll={(event) => setScrolled(event.currentTarget.scrollTop > 0)}
182
+ className="min-h-0 flex-1 overflow-y-auto px-6 py-4"
183
+ >
184
+ <div className="mx-auto max-w-3xl">
185
+ {feed.isPending && (
186
+ <p className="text-sm text-muted-foreground">{i18n.t("common.loading")}</p>
187
+ )}
188
+ {feed.isError && (
189
+ <div role="alert" className="space-y-3 text-sm">
190
+ <p className="text-destructive">{i18n.t("feed.failed")}</p>
191
+ <button
192
+ type="button"
193
+ onClick={() => void feed.refetch()}
194
+ className="rounded-md border px-3 py-2 outline-none hover:bg-muted focus-visible:ring-2 focus-visible:ring-ring"
195
+ >
196
+ {i18n.t("common.retry")}
197
+ </button>
198
+ </div>
199
+ )}
200
+ {feed.data && shown.length === 0 && (
201
+ <div className="space-y-2 text-sm text-muted-foreground">
202
+ <p>{i18n.t("feed.empty")}</p>
203
+ {/* ⚠️ Two states look identical and are not, so the second one is said out loud. A
204
+ feed showing only yourself is what sharing nothing with anybody looks like, and a
205
+ reader who takes it for a fault will go looking for one that is not there. */}
206
+ <p>{i18n.t("feed.emptyWhy")}</p>
207
+ {/* ⚠️ The SECOND state the ticket names, and it is not an empty list: it is a gap
208
+ inside a list that looks complete. Somebody whose read access to a folder was
209
+ withdrawn loses their OWN older entries from it, and without this sentence the
210
+ next reader goes looking for a fault that is not there — or loosens the filter
211
+ and opens the authorization while "repairing" it. */}
212
+ <p>{i18n.t("feed.gapWhy")}</p>
213
+ </div>
214
+ )}
215
+ {shown.length > 0 && (
216
+ <ul className="flex flex-col gap-2">
217
+ {shown.map((event) => (
218
+ <FeedCard
219
+ key={event.id}
220
+ event={event}
221
+ person={people.get(event.actorId) ?? null}
222
+ isMe={event.actorId === me?.id}
223
+ // ⚠️ Which surface the card leads to is decided by the ACTION, through
224
+ // `feedKindOf`, and not by a field on the event. A flow does not open in the
225
+ // knowledge tree — sending one there lands on a selection nothing matches, an
226
+ // empty pane with no error, which is the worst of the three possible outcomes.
227
+ open={(resourceId) =>
228
+ void navigate({
229
+ to: feedKindOf(event.action) === "flow" ? "/flows" : "/nodes",
230
+ search: { select: resourceId },
231
+ })
232
+ }
233
+ />
234
+ ))}
235
+ </ul>
236
+ )}
237
+ {/* The end of the list, and what keeps it going. There is no button: the answer to "is
238
+ there more" is the cursor, and the observer above asks for it. */}
239
+ <div ref={sentinel} aria-hidden="true" className="h-8" />
240
+ {feed.isFetchingNextPage && (
241
+ <p className="py-2 text-center text-xs text-muted-foreground">
242
+ {i18n.t("common.loading")}
243
+ </p>
244
+ )}
245
+ {feed.data && !feed.hasNextPage && shown.length > 0 && (
246
+ <div className="space-y-1 py-2 text-center text-xs text-muted-foreground">
247
+ <p>{i18n.t("feed.end")}</p>
248
+ {/* The same sentence at the other end: this is where somebody notices that an older
249
+ entry of their own is missing, and where the reason belongs. */}
250
+ <p>{i18n.t("feed.gapWhy")}</p>
251
+ </div>
252
+ )}
253
+ </div>
254
+ </div>
255
+ </div>
256
+ );
257
+ }
258
+
259
+ function FeedCard({
260
+ event,
261
+ person,
262
+ isMe,
263
+ open,
264
+ }: {
265
+ event: FeedEvent;
266
+ person: { name: string; isMachine: boolean } | null;
267
+ isMe: boolean;
268
+ open(resourceId: string): void;
269
+ }) {
270
+ const i18n = useI18n();
271
+ // ⚠️ Never the raw id. A person we cannot name is "a user account", which is the wording every
272
+ // other surface uses for the same gap (#258) — an id under a name is the question again in
273
+ // smaller type.
274
+ const who = isMe ? i18n.t("feed.you") : (person?.name ?? i18n.t("node.someUser"));
275
+ // What the button on this card says, and it is not always a version. A `node.save` records
276
+ // `{versionId, sequence}`, a `node.share` records `{principalType, verb}` — so a sharing card
277
+ // names the RIGHT that was granted. Both lead to the node: there is no route to a single version
278
+ // in this interface, and the access summary lives on the node itself (#742).
279
+ const sequence = typeof event.metadata.sequence === "number" ? event.metadata.sequence : null;
280
+ const verb = typeof event.metadata.verb === "string" ? event.metadata.verb : null;
281
+ const mark =
282
+ sequence !== null
283
+ ? i18n.t("feed.version", { sequence: String(sequence) })
284
+ : verb !== null
285
+ ? i18n.t(`node.verb.${verb}`)
286
+ : null;
287
+
288
+ return (
289
+ <li className="flex items-start gap-3 rounded-lg bg-muted/40 px-4 py-3">
290
+ <span
291
+ aria-hidden="true"
292
+ className={cn(
293
+ "grid size-8 shrink-0 place-items-center text-xs font-medium",
294
+ // A square for an agent, a circle for a person: the two are told apart before either
295
+ // name is read.
296
+ person?.isMachine
297
+ ? "rounded-md bg-accent text-accent-foreground"
298
+ : "rounded-full bg-secondary text-secondary-foreground",
299
+ )}
300
+ >
301
+ {initials(person?.name ?? who)}
302
+ </span>
303
+ <span className="grid min-w-0 flex-1 gap-1">
304
+ <span className="text-sm leading-snug">
305
+ {i18n.t(`feed.action.${event.action}`, { who, title: event.resourceTitle })}
306
+ </span>
307
+ {/* ⚠️ `min-w-0` is not decoration. A grid item defaults to `min-width: auto`, so without it
308
+ this row grows to whatever the path needs and pushes the button PAST the card's edge —
309
+ which is what a four-step path did on a phone. `truncate` below cannot save it either: it
310
+ shortens against the width it is given, and its parent was already too wide. */}
311
+ <span className="flex min-w-0 items-center justify-between gap-3">
312
+ <span
313
+ className="truncate text-xs text-muted-foreground"
314
+ // The whole path stays reachable where there is a pointer to rest.
315
+ title={event.path.map((step) => step.title).join(" / ")}
316
+ >
317
+ {shortTrail(event.path.map((step) => step.title))}
318
+ </span>
319
+ {/* ⚠️ A button, not a label. The whole point of the mark is getting there, and a span
320
+ carries no focus — so the keyboard requirement could not be met by a surface that had
321
+ nothing to focus at all.
322
+
323
+ ⚠️ It opens the NODE in both cases. There is no route to a single version here, and
324
+ the access summary lives on the node; the mark still says WHICH version or WHICH
325
+ right the card was about, which is the part the reader came for. */}
326
+ {mark !== null && (
327
+ <button
328
+ type="button"
329
+ onClick={() => open(event.resourceId)}
330
+ aria-label={i18n.t("feed.open", { title: event.resourceTitle, mark })}
331
+ className="shrink-0 rounded bg-muted px-2 py-0.5 font-mono text-[11px] text-muted-foreground outline-none hover:bg-accent hover:text-accent-foreground focus-visible:ring-2 focus-visible:ring-ring"
332
+ >
333
+ {mark}
334
+ </button>
335
+ )}
336
+ </span>
337
+ {/* The shared component, not a second way of saying the same thing: it carries the exact
338
+ moment in a tooltip that is reachable by keyboard, and "19 hours ago" is not something
339
+ anybody translates back into a time of day. */}
340
+ <RelativeTime value={event.occurredAt} className="text-xs text-muted-foreground" />
341
+ </span>
342
+ </li>
343
+ );
344
+ }