@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,545 @@
1
+ import type { Flow } from "@contract/flow.ts";
2
+ import type { Node } from "@contract/node.ts";
3
+ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
4
+ import { Link } from "@tanstack/react-router";
5
+ import { ChevronRight } from "lucide-react";
6
+ import { type ReactNode, useId, useLayoutEffect, useRef, useState } from "react";
7
+ import { allTreeLevelsKey, treeLevelKey } from "@/app/tree-move/tree-move.tsx";
8
+ import { attachmentPreviewKind, formatFileSize } from "@/attachment-detail/attachment-detail.ts";
9
+ import { boardKey } from "@/board/board-data/board-data.ts";
10
+ import { folderContentsOf, folderIndexOf } from "@/folder-index/folder-index.ts";
11
+ import { useI18n } from "@/i18n/i18n-context.tsx";
12
+ import { cn } from "@/lib/utils";
13
+ import { NodeEditor } from "@/node-editor/node-editor.tsx";
14
+ import { resourceErrorKey } from "@/resource-error.ts";
15
+ import type { ResourceTarget } from "@/resource-menu/resource-menu.tsx";
16
+ import { useIntelRouterContext } from "@/router/router-context.ts";
17
+ import { useDateTime } from "@/time/time-context.tsx";
18
+
19
+ /**
20
+ * The details of the open thing: its description, and the facts that belong to its kind (#819).
21
+ *
22
+ * ⚠️ **One field for every kind, and that is the whole point of the ticket.** A file had one since
23
+ * #470 and nothing else did, so the description of everything else stood as grey running text under
24
+ * the title. In a tree where everything is a node, a details field that knows one kind is the
25
+ * exception and one for all of them is the rule.
26
+ *
27
+ * ⚠️ **The panel is MOUNTED only while it is open**, never merely hidden. Every kind answers its
28
+ * facts from a query, and a hidden panel would ask for them on every screen the reader passes
29
+ * through: opening a folder would read its contents twice, once for the tree and once for a field
30
+ * nobody looked at. Mounting is also what keeps the queries free of an `enabled` flag per kind.
31
+ *
32
+ * ⚠️ **The fold is local state and never a stored field.** It is how the thing is being looked at,
33
+ * not what it says (the same line the head block draws in `frontmatter.tsx`, #657).
34
+ */
35
+ export function NodeDetails({ target }: { target: ResourceTarget }) {
36
+ const i18n = useI18n();
37
+ const [open, setOpen] = useState(false);
38
+ const panelId = useId();
39
+ return (
40
+ <div className="mt-1 min-w-0">
41
+ <button
42
+ type="button"
43
+ aria-expanded={open}
44
+ aria-controls={panelId}
45
+ onClick={() => setOpen((current) => !current)}
46
+ className="-ml-1 inline-flex items-center gap-1 rounded-md px-1 py-0.5 text-sm text-muted-foreground outline-none hover:text-foreground focus-visible:ring-2 focus-visible:ring-ring"
47
+ >
48
+ <ChevronRight
49
+ aria-hidden="true"
50
+ className={cn("size-3.5 transition-transform", open && "rotate-90")}
51
+ />
52
+ {i18n.t("details.title")}
53
+ </button>
54
+ {open ? (
55
+ <div id={panelId} className="mt-2 max-w-2xl space-y-3 rounded-md bg-muted/40 px-3 py-3">
56
+ <DescriptionField target={target} />
57
+ <Facts target={target} />
58
+ {target.type === "node" && target.node.kind === "folder" ? (
59
+ <FolderText folder={target.node} />
60
+ ) : null}
61
+ </div>
62
+ ) : null}
63
+ </div>
64
+ );
65
+ }
66
+
67
+ /**
68
+ * The description, readable and changeable in the same place.
69
+ *
70
+ * ⚠️ **`baseUpdatedAt` is not decoration**: the description takes the same conflict route as a
71
+ * rename, or two people writing at once end with one of them silently overwritten
72
+ * (`resource-menu.tsx`). The refusal is worded through `resourceErrorKey`, so a conflict says
73
+ * "somebody else changed it" rather than the general failure sentence.
74
+ */
75
+ function DescriptionField({ target }: { target: ResourceTarget }) {
76
+ const i18n = useI18n();
77
+ const { data } = useIntelRouterContext();
78
+ const queryClient = useQueryClient();
79
+ const description = target.type === "flow" ? target.flow.description : target.node.description;
80
+ const [editing, setEditing] = useState(false);
81
+ const [draft, setDraft] = useState(description ?? "");
82
+ const fieldId = useId();
83
+
84
+ const save = useMutation({
85
+ mutationFn: async (next: string) => {
86
+ // An empty field means "there is none", which the contract spells `null` rather than "".
87
+ const value = next.trim().length === 0 ? null : next.trim();
88
+ if (target.type === "flow") {
89
+ await data.updateFlow({
90
+ flowId: target.flow.id,
91
+ baseUpdatedAt: target.flow.updatedAt,
92
+ description: value,
93
+ idempotencyKey: crypto.randomUUID(),
94
+ });
95
+ return;
96
+ }
97
+ await data.updateNode({
98
+ nodeId: target.node.id,
99
+ baseUpdatedAt: target.node.updatedAt,
100
+ description: value,
101
+ idempotencyKey: crypto.randomUUID(),
102
+ });
103
+ },
104
+ onSuccess: async () => {
105
+ setEditing(false);
106
+ const id = target.type === "flow" ? target.flow.id : target.node.id;
107
+ await Promise.all([
108
+ queryClient.invalidateQueries({ queryKey: allTreeLevelsKey }),
109
+ queryClient.invalidateQueries({
110
+ queryKey: target.type === "flow" ? ["flow", id] : ["node", id],
111
+ }),
112
+ ]);
113
+ },
114
+ });
115
+
116
+ if (editing) {
117
+ return (
118
+ <div className="space-y-2">
119
+ <label htmlFor={fieldId} className="block text-sm font-medium">
120
+ {i18n.t("details.description")}
121
+ </label>
122
+ <textarea
123
+ id={fieldId}
124
+ rows={4}
125
+ maxLength={2_000}
126
+ value={draft}
127
+ onChange={(event) => setDraft(event.target.value)}
128
+ className="w-full rounded-md border bg-background px-3 py-2 text-sm outline-none focus-visible:ring-2 focus-visible:ring-ring"
129
+ />
130
+ {save.isError ? (
131
+ <p role="alert" className="text-sm text-destructive">
132
+ {i18n.t(resourceErrorKey(save.error))}
133
+ </p>
134
+ ) : null}
135
+ <div className="flex gap-2">
136
+ <button
137
+ type="button"
138
+ disabled={save.isPending}
139
+ onClick={() => save.mutate(draft)}
140
+ className="rounded-md border px-3 py-1 text-sm outline-none hover:bg-muted focus-visible:ring-2 focus-visible:ring-ring disabled:opacity-50"
141
+ >
142
+ {i18n.t("common.save")}
143
+ </button>
144
+ <button
145
+ type="button"
146
+ onClick={() => {
147
+ setDraft(description ?? "");
148
+ save.reset();
149
+ setEditing(false);
150
+ }}
151
+ className="rounded-md px-3 py-1 text-sm outline-none hover:bg-muted focus-visible:ring-2 focus-visible:ring-ring"
152
+ >
153
+ {i18n.t("common.cancel")}
154
+ </button>
155
+ </div>
156
+ </div>
157
+ );
158
+ }
159
+
160
+ return (
161
+ <div className="space-y-1">
162
+ <div className="flex items-baseline justify-between gap-3">
163
+ <span className="text-sm font-medium">{i18n.t("details.description")}</span>
164
+ <button
165
+ type="button"
166
+ onClick={() => {
167
+ setDraft(description ?? "");
168
+ setEditing(true);
169
+ }}
170
+ className="shrink-0 rounded-md px-1 text-sm font-medium outline-none hover:underline focus-visible:ring-2 focus-visible:ring-ring"
171
+ >
172
+ {i18n.t("details.descriptionEdit")}
173
+ </button>
174
+ </div>
175
+ {description ? (
176
+ <ClampedText text={description} />
177
+ ) : (
178
+ <p className="text-sm text-muted-foreground">{i18n.t("details.descriptionEmpty")}</p>
179
+ )}
180
+ </div>
181
+ );
182
+ }
183
+
184
+ /**
185
+ * A few lines by default, then "more".
186
+ *
187
+ * ⚠️ The button appears only when the text is actually cut off, and that is measured rather than
188
+ * guessed from the length: a clamp depends on the width the row happens to have, so a count of
189
+ * characters would offer "more" over a text that is fully visible and withhold it from one that is
190
+ * not. The observer covers the same case one resize later.
191
+ */
192
+ function ClampedText({ text }: { text: string }) {
193
+ const i18n = useI18n();
194
+ const textRef = useRef<HTMLParagraphElement>(null);
195
+ const textId = useId();
196
+ const [clipped, setClipped] = useState(false);
197
+ const [expanded, setExpanded] = useState(false);
198
+
199
+ useLayoutEffect(() => {
200
+ const element = textRef.current;
201
+ if (!element) return;
202
+ const update = () => {
203
+ // Measured against the CLAMPED box, so the check has to run while the clamp is on.
204
+ if (!expanded) setClipped(element.scrollHeight > element.clientHeight + 1);
205
+ };
206
+ update();
207
+ const observer = new ResizeObserver(update);
208
+ observer.observe(element);
209
+ return () => observer.disconnect();
210
+ }, [expanded]);
211
+
212
+ return (
213
+ <div className="min-w-0 text-sm">
214
+ <p
215
+ ref={textRef}
216
+ id={textId}
217
+ data-description=""
218
+ className={cn("whitespace-pre-wrap break-words", !expanded && "line-clamp-3")}
219
+ >
220
+ {text}
221
+ </p>
222
+ {clipped ? (
223
+ <button
224
+ type="button"
225
+ // ⚠️ The button says WHICH text it unfolds, not merely that something is expanded. The
226
+ // field can hold several paragraphs of its own, and `aria-expanded` alone leaves a reader
227
+ // to guess which one moved.
228
+ aria-controls={textId}
229
+ aria-expanded={expanded}
230
+ onClick={() => setExpanded((current) => !current)}
231
+ className="mt-1 rounded-md font-medium outline-none hover:underline focus-visible:ring-2 focus-visible:ring-ring"
232
+ >
233
+ {i18n.t(expanded ? "title.descriptionLess" : "title.descriptionMore")}
234
+ </button>
235
+ ) : null}
236
+ </div>
237
+ );
238
+ }
239
+
240
+ function Rows({ rows, pending }: { rows: [string, ReactNode][]; pending?: boolean }) {
241
+ const i18n = useI18n();
242
+ if (pending) return <p className="text-sm text-muted-foreground">{i18n.t("common.loading")}</p>;
243
+ if (rows.length === 0) return null;
244
+ return (
245
+ <dl className="grid grid-cols-[max-content_minmax(0,1fr)] gap-x-4 gap-y-1 text-sm">
246
+ {rows.map(([label, value]) => (
247
+ <div key={label} className="contents">
248
+ <dt className="text-muted-foreground">{label}</dt>
249
+ <dd className="break-words">{value}</dd>
250
+ </div>
251
+ ))}
252
+ </dl>
253
+ );
254
+ }
255
+
256
+ /**
257
+ * The facts that belong to the KIND, and only those.
258
+ *
259
+ * ⚠️ **A size in megabytes does not appear on a document** (#819). A document has a byte count like
260
+ * everything else in R2, and printing it would answer a question nobody asked with a number nobody
261
+ * can act on: what a reader wants to know about a text is how often it was written and when last.
262
+ */
263
+ function Facts({ target }: { target: ResourceTarget }) {
264
+ if (target.type === "flow") return <FlowFacts flow={target.flow} />;
265
+ const node = target.node;
266
+ switch (node.kind) {
267
+ case "folder":
268
+ return <FolderFacts node={node} />;
269
+ case "attachment":
270
+ return <FileFacts node={node} />;
271
+ case "table":
272
+ return <TableFacts node={node} />;
273
+ case "board":
274
+ return <BoardFacts node={node} />;
275
+ default:
276
+ // A document and a board card are the same thing here: a text with a history.
277
+ return <WrittenFacts node={node} />;
278
+ }
279
+ }
280
+
281
+ /**
282
+ * The folder's own text: the document called `index` that lives in it (#824).
283
+ *
284
+ * ⚠️ **The row is not hidden, it is moved.** The tree drops it from the folder's contents and it
285
+ * appears here instead, in the one place that is about this folder rather than beside the things
286
+ * the folder holds. Nothing about the document changes: it keeps its versions, its full text, its
287
+ * vectors and its own screen — and over MCP it was never hidden at all.
288
+ *
289
+ * ⚠️ **The same query the tree and the facts above already hold**, so a folder whose details are
290
+ * open costs no second read of its level.
291
+ *
292
+ * ⚠️ The document is fetched only once one is there. A folder without an `index` says nothing at
293
+ * all here rather than offering to make one: the convention is served where it exists, and a folder
294
+ * that does not follow it is not being told off.
295
+ */
296
+ function FolderText({ folder }: { folder: Node }) {
297
+ const i18n = useI18n();
298
+ const { data } = useIntelRouterContext();
299
+ const queryClient = useQueryClient();
300
+ const level = useQuery({
301
+ queryKey: treeLevelKey(folder.id),
302
+ queryFn: () => data.listTreeChildren(folder.id),
303
+ });
304
+ const index = folderIndexOf(folder.id, level.data ?? []);
305
+ const document = useQuery({
306
+ queryKey: ["node", index?.id],
307
+ queryFn: async () => await data.getNode(index?.id ?? ""),
308
+ enabled: index !== null,
309
+ });
310
+ if (index === null) return null;
311
+ return (
312
+ <section className="border-t pt-3">
313
+ <div className="mb-2 flex items-center justify-between gap-2">
314
+ <h3 className="text-sm font-medium">{i18n.t("details.folderText")}</h3>
315
+ {/* ⚠️ The way back, and the reason it is not optional: a row the tree does not draw can
316
+ only be moved, shared or archived from its own screen, and this is the only link left
317
+ to it (#824). */}
318
+ <Link
319
+ to="/nodes"
320
+ search={{ select: index.id }}
321
+ className="rounded-md px-1 py-0.5 text-sm text-muted-foreground underline outline-none hover:text-foreground focus-visible:ring-2 focus-visible:ring-ring"
322
+ >
323
+ {i18n.t("details.folderTextOpen")}
324
+ </Link>
325
+ </div>
326
+ {document.isPending ? (
327
+ <p className="text-sm text-muted-foreground">{i18n.t("common.loading")}</p>
328
+ ) : document.isError || !document.data ? (
329
+ // ⚠️ Its own sentence, not the folder's. The list came back — it is where this document was
330
+ // found — and only the document behind it did not: its access may have changed on its own,
331
+ // or it was archived a moment ago. "This folder could not be read" would send the reader
332
+ // after something that was read perfectly well (found in review of #843).
333
+ <p role="alert" className="text-sm text-destructive">
334
+ {i18n.t("details.folderTextFailed")}
335
+ </p>
336
+ ) : (
337
+ /* Readable and writable in the same place, through the editor the document has anywhere
338
+ else — a second, smaller editor here would be a second set of rules about one text. */
339
+ <NodeEditor
340
+ data={data}
341
+ document={document.data}
342
+ i18n={i18n}
343
+ onSaved={(saved) => {
344
+ queryClient.setQueryData(["node", index.id], saved);
345
+ }}
346
+ />
347
+ )}
348
+ </section>
349
+ );
350
+ }
351
+
352
+ function FolderFacts({ node }: { node: Node }) {
353
+ const i18n = useI18n();
354
+ const { data } = useIntelRouterContext();
355
+ // ⚠️ The key the sidebar already holds for this level, so an open folder costs no second request.
356
+ const level = useQuery({
357
+ queryKey: treeLevelKey(node.id),
358
+ queryFn: () => data.listTreeChildren(node.id),
359
+ });
360
+ // ⚠️ What the folder SHOWS, which since #824 is one row fewer than what it holds: its own text
361
+ // stands below this line rather than among its contents, and a count that included it would
362
+ // disagree with every list on the screen.
363
+ //
364
+ // ⚠️ This is a count for a reader and nothing else. What decides whether a folder may be deleted
365
+ // is asked on the server, over every table that points at the container — knowledge AND flows
366
+ // (`.claude/rules/destructive.md`, anchrd/intel#457) — and nothing here is part of that answer.
367
+ return (
368
+ <Rows
369
+ pending={level.isPending}
370
+ rows={[
371
+ [i18n.t("details.contents"), String(folderContentsOf(node.id, level.data ?? []).length)],
372
+ ]}
373
+ />
374
+ );
375
+ }
376
+
377
+ function WrittenFacts({ node }: { node: Node }) {
378
+ const i18n = useI18n();
379
+ const dateTime = useDateTime();
380
+ const { data } = useIntelRouterContext();
381
+ const versions = useQuery({
382
+ queryKey: ["node-versions", node.id],
383
+ queryFn: () => data.listNodeVersions(node.id),
384
+ });
385
+ const items = versions.data?.items ?? [];
386
+ // The list arrives newest first (`ORDER BY sequence DESC`), so the head of it is the last write.
387
+ const newest = items[0];
388
+ return (
389
+ <Rows
390
+ pending={versions.isPending}
391
+ rows={
392
+ newest === undefined
393
+ ? [[i18n.t("details.versions"), "0"]]
394
+ : [
395
+ [i18n.t("details.versions"), String(items.length)],
396
+ [i18n.t("details.lastWritten"), dateTime.at(newest.createdAt)],
397
+ ]
398
+ }
399
+ />
400
+ );
401
+ }
402
+
403
+ function TableFacts({ node }: { node: Node }) {
404
+ const i18n = useI18n();
405
+ const { data } = useIntelRouterContext();
406
+ // The key the table panel already uses.
407
+ const table = useQuery({
408
+ queryKey: ["node-table", node.id],
409
+ queryFn: () => data.getNodeTable(node.id),
410
+ });
411
+ return (
412
+ <Rows
413
+ pending={table.isPending}
414
+ rows={[
415
+ [i18n.t("details.columns"), String(table.data?.columns.length ?? 0)],
416
+ [i18n.t("details.rows"), String(table.data?.rows.length ?? 0)],
417
+ ]}
418
+ />
419
+ );
420
+ }
421
+
422
+ function BoardFacts({ node }: { node: Node }) {
423
+ const i18n = useI18n();
424
+ const { data } = useIntelRouterContext();
425
+ // ⚠️ The UNFILTERED entry, which is the one the board panel writes its answers into
426
+ // (`board-data.ts`). Asking under a filter would count the cards somebody is looking at rather
427
+ // than the cards there are.
428
+ const board = useQuery({
429
+ queryKey: [...boardKey(node.id), {}],
430
+ queryFn: () => data.getBoard({ boardId: node.id, includeArchived: false }),
431
+ });
432
+ return (
433
+ <Rows
434
+ pending={board.isPending}
435
+ rows={[
436
+ [i18n.t("details.columns"), String(board.data?.columns.length ?? 0)],
437
+ [i18n.t("details.cards"), String(board.data?.tasks.length ?? 0)],
438
+ ]}
439
+ />
440
+ );
441
+ }
442
+
443
+ function FlowFacts({ flow }: { flow: Flow }) {
444
+ const i18n = useI18n();
445
+ const dateTime = useDateTime();
446
+ const { data } = useIntelRouterContext();
447
+ const versions = useQuery({
448
+ queryKey: ["flow-versions", flow.id],
449
+ queryFn: () => data.listFlowVersions(flow.id),
450
+ });
451
+ const items = versions.data?.items ?? [];
452
+ const newest = items[0];
453
+ return (
454
+ <Rows
455
+ pending={versions.isPending}
456
+ rows={
457
+ newest === undefined
458
+ ? [[i18n.t("details.versions"), "0"]]
459
+ : [
460
+ [i18n.t("details.versions"), String(items.length)],
461
+ [i18n.t("details.lastWritten"), dateTime.at(newest.createdAt)],
462
+ ]
463
+ }
464
+ />
465
+ );
466
+ }
467
+
468
+ /**
469
+ * A file, with what #470 already showed beside its preview: size, format, and what the format
470
+ * itself can say.
471
+ *
472
+ * ⚠️ **This IS the file panel, moved rather than copied.** The viewer's own `Info` button is gone
473
+ * with #819: two details fields on one kind would be the state the ticket exists to end, and the
474
+ * one that lost would be the one every other kind now has.
475
+ */
476
+ function FileFacts({ node }: { node: Node }) {
477
+ const i18n = useI18n();
478
+ const dateTime = useDateTime();
479
+ const { data } = useIntelRouterContext();
480
+ const versions = useQuery({
481
+ queryKey: ["node-versions", node.id],
482
+ queryFn: () => data.listNodeVersions(node.id),
483
+ });
484
+ const version = versions.data?.items[0];
485
+ const kind = version ? attachmentPreviewKind(node.title, version.mediaType) : "unsupported";
486
+ // ⚠️ The same key the viewer loads the file under, so an open preview pays for it once. It is a
487
+ // download, which is why it hangs on the panel being open rather than on the screen being shown.
488
+ const file = useQuery({
489
+ queryKey: ["attachment", node.id, version?.id],
490
+ queryFn: () => data.getNodeAttachment(node.id),
491
+ enabled: version !== undefined,
492
+ retry: false,
493
+ });
494
+ const formatDetail = useQuery({
495
+ queryKey: ["attachment-format-detail", node.id, version?.id],
496
+ // ⚠️ Reached through `import()` rather than an import at the top, and that is the second half
497
+ // of the split in `attachment-detail.ts`: this function unpacks the file with `fflate`, and a
498
+ // static import would put that in the main chunk for the sake of one kind. The module is the
499
+ // viewer's own, which is already mounted whenever there is a file to ask about, so the chunk
500
+ // is paid for either way.
501
+ queryFn: async () => {
502
+ if (!file.data || !version) return null;
503
+ const { attachmentFormatDetail } = await import("@/attachment-viewer/attachment-viewer.tsx");
504
+ return await attachmentFormatDetail(
505
+ new File([file.data], node.title, { type: version.mediaType }),
506
+ kind,
507
+ );
508
+ },
509
+ // Neither `audio` nor `video` ever yields a result here (#815, same as `unsupported`) — gated
510
+ // out rather than left to resolve to `null` so a large media file is not read into memory for
511
+ // a question this function never answers for it.
512
+ enabled:
513
+ file.data !== undefined && kind !== "unsupported" && kind !== "audio" && kind !== "video",
514
+ retry: false,
515
+ });
516
+
517
+ if (version === undefined) {
518
+ return (
519
+ <Rows pending={versions.isPending} rows={[[i18n.t("attachment.details.size"), "0 B"]]} />
520
+ );
521
+ }
522
+ const rows: [string, ReactNode][] = [
523
+ [i18n.t("attachment.details.size"), formatFileSize(version.size)],
524
+ [
525
+ i18n.t("attachment.details.format"),
526
+ node.title.split(".").at(-1)?.toUpperCase() ?? i18n.t("attachment.kind.unsupported"),
527
+ ],
528
+ [i18n.t("attachment.details.type"), i18n.t(`attachment.kind.${kind}`)],
529
+ [i18n.t("attachment.details.mimeType"), version.mediaType],
530
+ [i18n.t("attachment.details.uploaded"), dateTime.at(version.createdAt)],
531
+ [i18n.t("attachment.details.uploadedBy"), version.createdBy],
532
+ [i18n.t("attachment.details.version"), String(version.sequence)],
533
+ ];
534
+ if (formatDetail.data) {
535
+ rows.push([i18n.t(`attachment.details.${formatDetail.data.label}`), formatDetail.data.value]);
536
+ }
537
+ rows.push([i18n.t("attachment.details.storage"), i18n.t("attachment.details.private")]);
538
+ rows.push([
539
+ i18n.t("attachment.details.checksum"),
540
+ <span key="checksum" className="font-mono text-xs">
541
+ {`${version.contentHash.slice(0, 8)}…${version.contentHash.slice(-4)}`}
542
+ </span>,
543
+ ]);
544
+ return <Rows pending={versions.isPending} rows={rows} />;
545
+ }