cli-jaw 2.0.1 → 2.0.2

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 (607) hide show
  1. package/README.ko.md +3 -2
  2. package/README.md +41 -12
  3. package/dist/bin/_http-client.js +34 -0
  4. package/dist/bin/_http-client.js.map +1 -0
  5. package/dist/bin/cli-jaw.js +10 -5
  6. package/dist/bin/cli-jaw.js.map +1 -1
  7. package/dist/bin/commands/browser-web-ai.js +155 -38
  8. package/dist/bin/commands/browser-web-ai.js.map +1 -1
  9. package/dist/bin/commands/browser.js +265 -37
  10. package/dist/bin/commands/browser.js.map +1 -1
  11. package/dist/bin/commands/chat.js +18 -14
  12. package/dist/bin/commands/chat.js.map +1 -1
  13. package/dist/bin/commands/clone.js +3 -2
  14. package/dist/bin/commands/clone.js.map +1 -1
  15. package/dist/bin/commands/dashboard.js +22 -18
  16. package/dist/bin/commands/dashboard.js.map +1 -1
  17. package/dist/bin/commands/dispatch.js +9 -11
  18. package/dist/bin/commands/dispatch.js.map +1 -1
  19. package/dist/bin/commands/doctor.js +26 -14
  20. package/dist/bin/commands/doctor.js.map +1 -1
  21. package/dist/bin/commands/employee.js +12 -7
  22. package/dist/bin/commands/employee.js.map +1 -1
  23. package/dist/bin/commands/init.js +9 -9
  24. package/dist/bin/commands/init.js.map +1 -1
  25. package/dist/bin/commands/launchd.js +17 -7
  26. package/dist/bin/commands/launchd.js.map +1 -1
  27. package/dist/bin/commands/mcp.js +7 -4
  28. package/dist/bin/commands/mcp.js.map +1 -1
  29. package/dist/bin/commands/memory.js +7 -5
  30. package/dist/bin/commands/memory.js.map +1 -1
  31. package/dist/bin/commands/orchestrate.js +6 -3
  32. package/dist/bin/commands/orchestrate.js.map +1 -1
  33. package/dist/bin/commands/reset.js +4 -3
  34. package/dist/bin/commands/reset.js.map +1 -1
  35. package/dist/bin/commands/serve.js +3 -2
  36. package/dist/bin/commands/serve.js.map +1 -1
  37. package/dist/bin/commands/service.js +2 -2
  38. package/dist/bin/commands/service.js.map +1 -1
  39. package/dist/bin/commands/status.js +10 -9
  40. package/dist/bin/commands/status.js.map +1 -1
  41. package/dist/bin/commands/tui/api.js +26 -18
  42. package/dist/bin/commands/tui/api.js.map +1 -1
  43. package/dist/bin/commands/tui/overlays.js +3 -1
  44. package/dist/bin/commands/tui/overlays.js.map +1 -1
  45. package/dist/bin/commands/tui/simple-mode.js +1 -1
  46. package/dist/bin/commands/tui/simple-mode.js.map +1 -1
  47. package/dist/bin/commands/tui/types.js.map +1 -1
  48. package/dist/bin/postinstall.js +153 -65
  49. package/dist/bin/postinstall.js.map +1 -1
  50. package/dist/bin/star-prompt.js +4 -3
  51. package/dist/bin/star-prompt.js.map +1 -1
  52. package/dist/lib/mcp/format-converters.js +15 -10
  53. package/dist/lib/mcp/format-converters.js.map +1 -1
  54. package/dist/lib/mcp/mcp-install.js +2 -2
  55. package/dist/lib/mcp/mcp-install.js.map +1 -1
  56. package/dist/lib/mcp/skills-distribution.js +2 -2
  57. package/dist/lib/mcp/skills-distribution.js.map +1 -1
  58. package/dist/lib/mcp/skills-reset.js +5 -4
  59. package/dist/lib/mcp/skills-reset.js.map +1 -1
  60. package/dist/lib/mcp/skills-symlinks.js +9 -9
  61. package/dist/lib/mcp/skills-symlinks.js.map +1 -1
  62. package/dist/lib/mcp/skills-utils.js +5 -4
  63. package/dist/lib/mcp/skills-utils.js.map +1 -1
  64. package/dist/lib/mcp/unified-config.js +3 -2
  65. package/dist/lib/mcp/unified-config.js.map +1 -1
  66. package/dist/lib/mime-detect.js +65 -0
  67. package/dist/lib/mime-detect.js.map +1 -0
  68. package/dist/lib/quota-copilot.js +12 -10
  69. package/dist/lib/quota-copilot.js.map +1 -1
  70. package/dist/lib/stt.js +3 -3
  71. package/dist/lib/stt.js.map +1 -1
  72. package/dist/lib/upload.js +14 -5
  73. package/dist/lib/upload.js.map +1 -1
  74. package/dist/scripts/i18n-registry.js +4 -4
  75. package/dist/scripts/i18n-registry.js.map +1 -1
  76. package/dist/server.js +61 -57
  77. package/dist/server.js.map +1 -1
  78. package/dist/src/agent/alert-escalation.js +61 -0
  79. package/dist/src/agent/alert-escalation.js.map +1 -0
  80. package/dist/src/agent/args.js +71 -2
  81. package/dist/src/agent/args.js.map +1 -1
  82. package/dist/src/agent/error-classifier.js +9 -4
  83. package/dist/src/agent/error-classifier.js.map +1 -1
  84. package/dist/src/agent/events.js +130 -101
  85. package/dist/src/agent/events.js.map +1 -1
  86. package/dist/src/agent/lifecycle-handler.js +44 -19
  87. package/dist/src/agent/lifecycle-handler.js.map +1 -1
  88. package/dist/src/agent/live-run-state.js +7 -3
  89. package/dist/src/agent/live-run-state.js.map +1 -1
  90. package/dist/src/agent/memory-flush-controller.js +6 -4
  91. package/dist/src/agent/memory-flush-controller.js.map +1 -1
  92. package/dist/src/agent/opencode-diagnostics.js +7 -6
  93. package/dist/src/agent/opencode-diagnostics.js.map +1 -1
  94. package/dist/src/agent/session-persistence.js +1 -1
  95. package/dist/src/agent/session-persistence.js.map +1 -1
  96. package/dist/src/agent/spawn-env.js +33 -16
  97. package/dist/src/agent/spawn-env.js.map +1 -1
  98. package/dist/src/agent/spawn.js +176 -110
  99. package/dist/src/agent/spawn.js.map +1 -1
  100. package/dist/src/agent/watchdog.js +56 -0
  101. package/dist/src/agent/watchdog.js.map +1 -0
  102. package/dist/src/browser/actions.js +179 -70
  103. package/dist/src/browser/actions.js.map +1 -1
  104. package/dist/src/browser/connection.js +296 -19
  105. package/dist/src/browser/connection.js.map +1 -1
  106. package/dist/src/browser/index.js +4 -1
  107. package/dist/src/browser/index.js.map +1 -1
  108. package/dist/src/browser/launch-policy.js +1 -1
  109. package/dist/src/browser/launch-policy.js.map +1 -1
  110. package/dist/src/browser/primitives.js +11 -7
  111. package/dist/src/browser/primitives.js.map +1 -1
  112. package/dist/src/browser/runtime-diagnostics.js +45 -0
  113. package/dist/src/browser/runtime-diagnostics.js.map +1 -0
  114. package/dist/src/browser/runtime-orphans.js +127 -0
  115. package/dist/src/browser/runtime-orphans.js.map +1 -0
  116. package/dist/src/browser/runtime-owner-store.js +51 -0
  117. package/dist/src/browser/runtime-owner-store.js.map +1 -0
  118. package/dist/src/browser/runtime-owner.js +2 -2
  119. package/dist/src/browser/runtime-owner.js.map +1 -1
  120. package/dist/src/browser/tab-lifecycle.js +157 -0
  121. package/dist/src/browser/tab-lifecycle.js.map +1 -0
  122. package/dist/src/browser/vision.js +35 -11
  123. package/dist/src/browser/vision.js.map +1 -1
  124. package/dist/src/browser/web-ai/action-breadth.js +56 -0
  125. package/dist/src/browser/web-ai/action-breadth.js.map +1 -0
  126. package/dist/src/browser/web-ai/action-cache.js +9 -8
  127. package/dist/src/browser/web-ai/action-cache.js.map +1 -1
  128. package/dist/src/browser/web-ai/action-intent.js +64 -0
  129. package/dist/src/browser/web-ai/action-intent.js.map +1 -0
  130. package/dist/src/browser/web-ai/action-memory.js +82 -0
  131. package/dist/src/browser/web-ai/action-memory.js.map +1 -0
  132. package/dist/src/browser/web-ai/action-trace.js +11 -6
  133. package/dist/src/browser/web-ai/action-trace.js.map +1 -1
  134. package/dist/src/browser/web-ai/annotated-screenshot.js +2 -1
  135. package/dist/src/browser/web-ai/annotated-screenshot.js.map +1 -1
  136. package/dist/src/browser/web-ai/answer-artifact.js +97 -0
  137. package/dist/src/browser/web-ai/answer-artifact.js.map +1 -0
  138. package/dist/src/browser/web-ai/ax-snapshot.js +3 -2
  139. package/dist/src/browser/web-ai/ax-snapshot.js.map +1 -1
  140. package/dist/src/browser/web-ai/browser-primitives.js +10 -7
  141. package/dist/src/browser/web-ai/browser-primitives.js.map +1 -1
  142. package/dist/src/browser/web-ai/capability-registry.js +1 -1
  143. package/dist/src/browser/web-ai/capability-registry.js.map +1 -1
  144. package/dist/src/browser/web-ai/chatgpt-composer.js +12 -12
  145. package/dist/src/browser/web-ai/chatgpt-composer.js.map +1 -1
  146. package/dist/src/browser/web-ai/chatgpt-model.js +469 -50
  147. package/dist/src/browser/web-ai/chatgpt-model.js.map +1 -1
  148. package/dist/src/browser/web-ai/chatgpt-response.js +82 -10
  149. package/dist/src/browser/web-ai/chatgpt-response.js.map +1 -1
  150. package/dist/src/browser/web-ai/chatgpt.js +325 -49
  151. package/dist/src/browser/web-ai/chatgpt.js.map +1 -1
  152. package/dist/src/browser/web-ai/churn-log.js +1 -1
  153. package/dist/src/browser/web-ai/churn-log.js.map +1 -1
  154. package/dist/src/browser/web-ai/cli-sessions.js +42 -26
  155. package/dist/src/browser/web-ai/cli-sessions.js.map +1 -1
  156. package/dist/src/browser/web-ai/context-pack/builder.js +1 -1
  157. package/dist/src/browser/web-ai/context-pack/builder.js.map +1 -1
  158. package/dist/src/browser/web-ai/context-pack/file-selector.js +2 -2
  159. package/dist/src/browser/web-ai/context-pack/file-selector.js.map +1 -1
  160. package/dist/src/browser/web-ai/context-pack/report.js +1 -1
  161. package/dist/src/browser/web-ai/context-pack/report.js.map +1 -1
  162. package/dist/src/browser/web-ai/copy-markdown.js +25 -5
  163. package/dist/src/browser/web-ai/copy-markdown.js.map +1 -1
  164. package/dist/src/browser/web-ai/diagnostics.js +17 -14
  165. package/dist/src/browser/web-ai/diagnostics.js.map +1 -1
  166. package/dist/src/browser/web-ai/doctor.js +10 -6
  167. package/dist/src/browser/web-ai/doctor.js.map +1 -1
  168. package/dist/src/browser/web-ai/dom-hash.js.map +1 -1
  169. package/dist/src/browser/web-ai/errors.js +5 -22
  170. package/dist/src/browser/web-ai/errors.js.map +1 -1
  171. package/dist/src/browser/web-ai/gemini-live.js +7 -6
  172. package/dist/src/browser/web-ai/gemini-live.js.map +1 -1
  173. package/dist/src/browser/web-ai/grok-live.js +9 -5
  174. package/dist/src/browser/web-ai/grok-live.js.map +1 -1
  175. package/dist/src/browser/web-ai/index.js +5 -0
  176. package/dist/src/browser/web-ai/index.js.map +1 -1
  177. package/dist/src/browser/web-ai/observation-bundle.js +90 -0
  178. package/dist/src/browser/web-ai/observation-bundle.js.map +1 -0
  179. package/dist/src/browser/web-ai/observe-actions.js +186 -0
  180. package/dist/src/browser/web-ai/observe-actions.js.map +1 -0
  181. package/dist/src/browser/web-ai/observe-targets.js +6 -1
  182. package/dist/src/browser/web-ai/observe-targets.js.map +1 -1
  183. package/dist/src/browser/web-ai/planner-contract.js +18 -0
  184. package/dist/src/browser/web-ai/planner-contract.js.map +1 -0
  185. package/dist/src/browser/web-ai/post-action-assert.js +8 -7
  186. package/dist/src/browser/web-ai/post-action-assert.js.map +1 -1
  187. package/dist/src/browser/web-ai/product-surfaces.js +2 -1
  188. package/dist/src/browser/web-ai/product-surfaces.js.map +1 -1
  189. package/dist/src/browser/web-ai/provider-adapter.js.map +1 -1
  190. package/dist/src/browser/web-ai/question.js +14 -7
  191. package/dist/src/browser/web-ai/question.js.map +1 -1
  192. package/dist/src/browser/web-ai/ref-registry.js.map +1 -1
  193. package/dist/src/browser/web-ai/self-heal.js +20 -14
  194. package/dist/src/browser/web-ai/self-heal.js.map +1 -1
  195. package/dist/src/browser/web-ai/session-store.js +61 -1
  196. package/dist/src/browser/web-ai/session-store.js.map +1 -1
  197. package/dist/src/browser/web-ai/session.js +43 -2
  198. package/dist/src/browser/web-ai/session.js.map +1 -1
  199. package/dist/src/browser/web-ai/source-audit.js +116 -0
  200. package/dist/src/browser/web-ai/source-audit.js.map +1 -0
  201. package/dist/src/browser/web-ai/tab-finalizer.js +18 -0
  202. package/dist/src/browser/web-ai/tab-finalizer.js.map +1 -0
  203. package/dist/src/browser/web-ai/tab-lease-store.js +390 -0
  204. package/dist/src/browser/web-ai/tab-lease-store.js.map +1 -0
  205. package/dist/src/browser/web-ai/tab-pool.js +44 -0
  206. package/dist/src/browser/web-ai/tab-pool.js.map +1 -0
  207. package/dist/src/browser/web-ai/target-resolver.js +31 -0
  208. package/dist/src/browser/web-ai/target-resolver.js.map +1 -0
  209. package/dist/src/browser/web-ai/trace-persistence.js +4 -2
  210. package/dist/src/browser/web-ai/trace-persistence.js.map +1 -1
  211. package/dist/src/browser/web-ai/vendor-editor-contract.js.map +1 -1
  212. package/dist/src/browser/web-ai/watcher.js +8 -7
  213. package/dist/src/browser/web-ai/watcher.js.map +1 -1
  214. package/dist/src/cli/acp-client.js +40 -26
  215. package/dist/src/cli/acp-client.js.map +1 -1
  216. package/dist/src/cli/command-context.js +3 -3
  217. package/dist/src/cli/command-context.js.map +1 -1
  218. package/dist/src/cli/commands.js +25 -16
  219. package/dist/src/cli/commands.js.map +1 -1
  220. package/dist/src/cli/compact.js.map +1 -1
  221. package/dist/src/cli/handlers-completions.js +6 -4
  222. package/dist/src/cli/handlers-completions.js.map +1 -1
  223. package/dist/src/cli/handlers-runtime.js +45 -38
  224. package/dist/src/cli/handlers-runtime.js.map +1 -1
  225. package/dist/src/cli/handlers.js +44 -36
  226. package/dist/src/cli/handlers.js.map +1 -1
  227. package/dist/src/cli/readiness.js +3 -1
  228. package/dist/src/cli/readiness.js.map +1 -1
  229. package/dist/src/cli/registry.js +1 -1
  230. package/dist/src/cli/registry.js.map +1 -1
  231. package/dist/src/cli/tui/overlay.js +4 -1
  232. package/dist/src/cli/tui/overlay.js.map +1 -1
  233. package/dist/src/cli/types.js +4 -0
  234. package/dist/src/cli/types.js.map +1 -0
  235. package/dist/src/command-contract/catalog.js +1 -1
  236. package/dist/src/command-contract/catalog.js.map +1 -1
  237. package/dist/src/command-contract/help-renderer.js +1 -1
  238. package/dist/src/command-contract/help-renderer.js.map +1 -1
  239. package/dist/src/core/browser-open-default.js +13 -0
  240. package/dist/src/core/browser-open-default.js.map +1 -0
  241. package/dist/src/core/browser-open.js +41 -0
  242. package/dist/src/core/browser-open.js.map +1 -0
  243. package/dist/src/core/bus.js +13 -2
  244. package/dist/src/core/bus.js.map +1 -1
  245. package/dist/src/core/compact.js +5 -4
  246. package/dist/src/core/compact.js.map +1 -1
  247. package/dist/src/core/config.js +51 -52
  248. package/dist/src/core/config.js.map +1 -1
  249. package/dist/src/core/db.js +48 -6
  250. package/dist/src/core/db.js.map +1 -1
  251. package/dist/src/core/employees.js +9 -8
  252. package/dist/src/core/employees.js.map +1 -1
  253. package/dist/src/core/instance.js +7 -6
  254. package/dist/src/core/instance.js.map +1 -1
  255. package/dist/src/core/logger.js +1 -1
  256. package/dist/src/core/logger.js.map +1 -1
  257. package/dist/src/core/main-session.js +7 -7
  258. package/dist/src/core/main-session.js.map +1 -1
  259. package/dist/src/core/path-expand.js +10 -0
  260. package/dist/src/core/path-expand.js.map +1 -0
  261. package/dist/src/core/runtime-path.js +1 -1
  262. package/dist/src/core/runtime-path.js.map +1 -1
  263. package/dist/src/core/runtime-settings.js +13 -13
  264. package/dist/src/core/runtime-settings.js.map +1 -1
  265. package/dist/src/core/settings-merge.js +14 -14
  266. package/dist/src/core/settings-merge.js.map +1 -1
  267. package/dist/src/core/strip-undefined.js +13 -0
  268. package/dist/src/core/strip-undefined.js.map +1 -0
  269. package/dist/src/core/tcc.js +1 -1
  270. package/dist/src/core/tcc.js.map +1 -1
  271. package/dist/src/discord/bot.js +19 -18
  272. package/dist/src/discord/bot.js.map +1 -1
  273. package/dist/src/discord/channel-types.js +14 -0
  274. package/dist/src/discord/channel-types.js.map +1 -0
  275. package/dist/src/discord/commands.js +7 -6
  276. package/dist/src/discord/commands.js.map +1 -1
  277. package/dist/src/discord/discord-file.js.map +1 -1
  278. package/dist/src/discord/forwarder.js +3 -3
  279. package/dist/src/discord/forwarder.js.map +1 -1
  280. package/dist/src/http/error-middleware.js +3 -3
  281. package/dist/src/http/error-middleware.js.map +1 -1
  282. package/dist/src/http/response.js +2 -2
  283. package/dist/src/http/response.js.map +1 -1
  284. package/dist/src/ide/diff.js +7 -7
  285. package/dist/src/ide/diff.js.map +1 -1
  286. package/dist/src/manager/board/routes.js +28 -27
  287. package/dist/src/manager/board/routes.js.map +1 -1
  288. package/dist/src/manager/dashboard-home.js +3 -5
  289. package/dist/src/manager/dashboard-home.js.map +1 -1
  290. package/dist/src/manager/dashboard-service.js +2 -2
  291. package/dist/src/manager/dashboard-service.js.map +1 -1
  292. package/dist/src/manager/health-history.js +10 -9
  293. package/dist/src/manager/health-history.js.map +1 -1
  294. package/dist/src/manager/launchd-service.js +2 -2
  295. package/dist/src/manager/launchd-service.js.map +1 -1
  296. package/dist/src/manager/lifecycle-helpers.js +7 -6
  297. package/dist/src/manager/lifecycle-helpers.js.map +1 -1
  298. package/dist/src/manager/lifecycle.js +7 -6
  299. package/dist/src/manager/lifecycle.js.map +1 -1
  300. package/dist/src/manager/logs.js +18 -18
  301. package/dist/src/manager/logs.js.map +1 -1
  302. package/dist/src/manager/metadata.js +10 -2
  303. package/dist/src/manager/metadata.js.map +1 -1
  304. package/dist/src/manager/notes/assets.js +216 -0
  305. package/dist/src/manager/notes/assets.js.map +1 -0
  306. package/dist/src/manager/notes/capabilities.js +56 -0
  307. package/dist/src/manager/notes/capabilities.js.map +1 -0
  308. package/dist/src/manager/notes/constants.js +11 -0
  309. package/dist/src/manager/notes/constants.js.map +1 -0
  310. package/dist/src/manager/notes/frontmatter.js +120 -0
  311. package/dist/src/manager/notes/frontmatter.js.map +1 -0
  312. package/dist/src/manager/notes/link-resolver.js +96 -0
  313. package/dist/src/manager/notes/link-resolver.js.map +1 -0
  314. package/dist/src/manager/notes/path-guards.js +4 -0
  315. package/dist/src/manager/notes/path-guards.js.map +1 -1
  316. package/dist/src/manager/notes/remote-assets.js +128 -0
  317. package/dist/src/manager/notes/remote-assets.js.map +1 -0
  318. package/dist/src/manager/notes/routes.js +63 -13
  319. package/dist/src/manager/notes/routes.js.map +1 -1
  320. package/dist/src/manager/notes/store.js +3 -2
  321. package/dist/src/manager/notes/store.js.map +1 -1
  322. package/dist/src/manager/notes/system-trash.js +3 -3
  323. package/dist/src/manager/notes/system-trash.js.map +1 -1
  324. package/dist/src/manager/notes/vault-index.js +220 -0
  325. package/dist/src/manager/notes/vault-index.js.map +1 -0
  326. package/dist/src/manager/notes/watcher.js +27 -0
  327. package/dist/src/manager/notes/watcher.js.map +1 -0
  328. package/dist/src/manager/notes/wiki-links.js +109 -0
  329. package/dist/src/manager/notes/wiki-links.js.map +1 -0
  330. package/dist/src/manager/process-verify.js +29 -0
  331. package/dist/src/manager/process-verify.js.map +1 -1
  332. package/dist/src/manager/profiles.js +3 -2
  333. package/dist/src/manager/profiles.js.map +1 -1
  334. package/dist/src/manager/registry.js +60 -58
  335. package/dist/src/manager/registry.js.map +1 -1
  336. package/dist/src/manager/routes/electron-metrics.js +22 -22
  337. package/dist/src/manager/routes/electron-metrics.js.map +1 -1
  338. package/dist/src/manager/scan.js +2 -2
  339. package/dist/src/manager/scan.js.map +1 -1
  340. package/dist/src/manager/schedule/routes.js +24 -23
  341. package/dist/src/manager/schedule/routes.js.map +1 -1
  342. package/dist/src/manager/server.js +28 -28
  343. package/dist/src/manager/server.js.map +1 -1
  344. package/dist/src/manager/systemd-service.js +1 -1
  345. package/dist/src/manager/systemd-service.js.map +1 -1
  346. package/dist/src/memory/bootstrap.js +5 -4
  347. package/dist/src/memory/bootstrap.js.map +1 -1
  348. package/dist/src/memory/heartbeat-schedule.js +10 -10
  349. package/dist/src/memory/heartbeat-schedule.js.map +1 -1
  350. package/dist/src/memory/heartbeat.js +23 -22
  351. package/dist/src/memory/heartbeat.js.map +1 -1
  352. package/dist/src/memory/indexing.js.map +1 -1
  353. package/dist/src/memory/keyword-expand.js +1 -1
  354. package/dist/src/memory/keyword-expand.js.map +1 -1
  355. package/dist/src/memory/memory.js +2 -2
  356. package/dist/src/memory/memory.js.map +1 -1
  357. package/dist/src/memory/runtime.js +6 -5
  358. package/dist/src/memory/runtime.js.map +1 -1
  359. package/dist/src/memory/shared.js +2 -1
  360. package/dist/src/memory/shared.js.map +1 -1
  361. package/dist/src/memory/worklog.js +1 -1
  362. package/dist/src/memory/worklog.js.map +1 -1
  363. package/dist/src/messaging/runtime.js +13 -10
  364. package/dist/src/messaging/runtime.js.map +1 -1
  365. package/dist/src/messaging/send.js +16 -15
  366. package/dist/src/messaging/send.js.map +1 -1
  367. package/dist/src/orchestrator/collect.js +6 -6
  368. package/dist/src/orchestrator/collect.js.map +1 -1
  369. package/dist/src/orchestrator/distribute.js +112 -77
  370. package/dist/src/orchestrator/distribute.js.map +1 -1
  371. package/dist/src/orchestrator/gateway.js +6 -5
  372. package/dist/src/orchestrator/gateway.js.map +1 -1
  373. package/dist/src/orchestrator/pipeline.js +42 -42
  374. package/dist/src/orchestrator/pipeline.js.map +1 -1
  375. package/dist/src/orchestrator/state-machine.js +3 -3
  376. package/dist/src/orchestrator/state-machine.js.map +1 -1
  377. package/dist/src/orchestrator/worker-registry.js +4 -3
  378. package/dist/src/orchestrator/worker-registry.js.map +1 -1
  379. package/dist/src/prompt/builder.js +24 -19
  380. package/dist/src/prompt/builder.js.map +1 -1
  381. package/dist/src/prompt/templates/a1-system.md +15 -10
  382. package/dist/src/prompt/templates/control-system.md +9 -7
  383. package/dist/src/prompt/templates/employee.md +5 -4
  384. package/dist/src/prompt/templates/vision-click.md +1 -1
  385. package/dist/src/routes/_http-error.js +15 -0
  386. package/dist/src/routes/_http-error.js.map +1 -0
  387. package/dist/src/routes/avatar.js +6 -5
  388. package/dist/src/routes/avatar.js.map +1 -1
  389. package/dist/src/routes/browser.js +132 -45
  390. package/dist/src/routes/browser.js.map +1 -1
  391. package/dist/src/routes/employees.js +28 -21
  392. package/dist/src/routes/employees.js.map +1 -1
  393. package/dist/src/routes/heartbeat.js +12 -9
  394. package/dist/src/routes/heartbeat.js.map +1 -1
  395. package/dist/src/routes/i18n.js +13 -7
  396. package/dist/src/routes/i18n.js.map +1 -1
  397. package/dist/src/routes/jaw-memory.js +15 -11
  398. package/dist/src/routes/jaw-memory.js.map +1 -1
  399. package/dist/src/routes/memory.js +25 -20
  400. package/dist/src/routes/memory.js.map +1 -1
  401. package/dist/src/routes/messaging.js +50 -29
  402. package/dist/src/routes/messaging.js.map +1 -1
  403. package/dist/src/routes/orchestrate.js +38 -24
  404. package/dist/src/routes/orchestrate.js.map +1 -1
  405. package/dist/src/routes/quota.js +81 -17
  406. package/dist/src/routes/quota.js.map +1 -1
  407. package/dist/src/routes/settings.js +28 -18
  408. package/dist/src/routes/settings.js.map +1 -1
  409. package/dist/src/routes/skills.js +22 -13
  410. package/dist/src/routes/skills.js.map +1 -1
  411. package/dist/src/routes/traces.js +77 -0
  412. package/dist/src/routes/traces.js.map +1 -0
  413. package/dist/src/security/path-guards.js +2 -1
  414. package/dist/src/security/path-guards.js.map +1 -1
  415. package/dist/src/shared/tool-log-sanitize.js +191 -0
  416. package/dist/src/shared/tool-log-sanitize.js.map +1 -0
  417. package/dist/src/telegram/bot.js +65 -55
  418. package/dist/src/telegram/bot.js.map +1 -1
  419. package/dist/src/telegram/forwarder.js +5 -9
  420. package/dist/src/telegram/forwarder.js.map +1 -1
  421. package/dist/src/telegram/telegram-file.js +28 -24
  422. package/dist/src/telegram/telegram-file.js.map +1 -1
  423. package/dist/src/telegram/voice.js +4 -3
  424. package/dist/src/telegram/voice.js.map +1 -1
  425. package/dist/src/trace/redact.js +50 -0
  426. package/dist/src/trace/redact.js.map +1 -0
  427. package/dist/src/trace/store.js +162 -0
  428. package/dist/src/trace/store.js.map +1 -0
  429. package/dist/src/trace/types.js +2 -0
  430. package/dist/src/trace/types.js.map +1 -0
  431. package/dist/src/types/cli-engine.js +34 -0
  432. package/dist/src/types/cli-engine.js.map +1 -0
  433. package/dist/src/types/cli-events.js +31 -0
  434. package/dist/src/types/cli-events.js.map +1 -0
  435. package/package.json +22 -2
  436. package/public/css/tool-ui.css +3 -1
  437. package/public/css/trace-drawer.css +48 -0
  438. package/public/dist/assets/{AdvancedExport-3WAYIabE.js → AdvancedExport-DJZ2VmBR.js} +1 -1
  439. package/public/dist/assets/Agent-CgpLT8IY.js +1 -0
  440. package/public/dist/assets/Browser-CrkiQoB8.js +1 -0
  441. package/public/dist/assets/{ChannelsDiscord-UzFPlWT4.js → ChannelsDiscord-CVUC22D4.js} +1 -1
  442. package/public/dist/assets/{ChannelsTelegram-DNWtPX0w.js → ChannelsTelegram-DEatIQNM.js} +1 -1
  443. package/public/dist/assets/{DashboardMeta-Y_6nVeJO.js → DashboardMeta-BKoxRc7i.js} +1 -1
  444. package/public/dist/assets/{Display-D8vGOl4s.js → Display-DnNGV9Km.js} +1 -1
  445. package/public/dist/assets/{Employees-YR_sIRK4.js → Employees-DZ2iJYKy.js} +1 -1
  446. package/public/dist/assets/{HealthBadge-CPePajyU.js → HealthBadge-Cq2c7G9s.js} +1 -1
  447. package/public/dist/assets/{Heartbeat-DTpAULQR.js → Heartbeat-BML6eTXZ.js} +1 -1
  448. package/public/dist/assets/{Mcp-DM-PgG6z.js → Mcp-BlEviQ3h.js} +1 -1
  449. package/public/dist/assets/{Memory-C_LvJnkn.js → Memory-BRyH80He.js} +1 -1
  450. package/public/dist/assets/MilkdownWysiwygEditor-Cm3uXfWf.js +52 -0
  451. package/public/dist/assets/ModelProvider-DxyR7EL9.js +1 -0
  452. package/public/dist/assets/Network-DDOOESh1.js +1 -0
  453. package/public/dist/assets/{Permissions-B1naJjjw.js → Permissions-Br0eSbKb.js} +1 -1
  454. package/public/dist/assets/{Permissions-BKffrMJD.js → Permissions-QHkzStqQ.js} +1 -1
  455. package/public/dist/assets/{Profile-DIqjSe2C.js → Profile-C79NKumk.js} +1 -1
  456. package/public/dist/assets/{Prompts-BMfbV6Y4.js → Prompts-BmiIDiXW.js} +1 -1
  457. package/public/dist/assets/{SpeechKeys-DjiQTzSL.js → SpeechKeys-B8304XJK.js} +1 -1
  458. package/public/dist/assets/app-Be58Cs3Y.js +32 -0
  459. package/public/dist/assets/{app-BzvwJJiv.css → app-CphocJzo.css} +1 -1
  460. package/public/dist/assets/{dist-DTvxN3ux.js → dist-BD0UXfgF2.js} +1 -1
  461. package/public/dist/assets/{dist-CASeq-Tl.js → dist-BNfXO3Yr.js} +1 -1
  462. package/public/dist/assets/{dist-BMPiaUzF.js → dist-BUnPYbK3.js} +1 -1
  463. package/public/dist/assets/{dist-CT_X3hVT.js → dist-BZosRD2u.js} +1 -1
  464. package/public/dist/assets/{dist-4J6YbNXv.js → dist-Bd9PlnQm.js} +1 -1
  465. package/public/dist/assets/{dist-BcZjyn5g.js → dist-BsT5UdNP.js} +1 -1
  466. package/public/dist/assets/{dist-BfBhOPR-.js → dist-CIuXW-sc.js} +1 -1
  467. package/public/dist/assets/{dist-BMiCig3A2.js → dist-CL4PTYWf.js} +1 -1
  468. package/public/dist/assets/{dist-VyP6-HLb.js → dist-Ch5VAlV9.js} +1 -1
  469. package/public/dist/assets/dist-ClqO40BE.js +1 -0
  470. package/public/dist/assets/{dist-c98Tp7bP.js → dist-Cp42cMcI.js} +1 -1
  471. package/public/dist/assets/{dist-CIlYL1qe.js → dist-CpUK8ypo.js} +1 -1
  472. package/public/dist/assets/dist-CxeLAw2Y.js +1 -0
  473. package/public/dist/assets/dist-D2SH8nxa.js +1 -0
  474. package/public/dist/assets/{dist-84fwQ7bO.js → dist-D6cUXP7K.js} +1 -1
  475. package/public/dist/assets/{dist-BOCcQAyF.js → dist-D7bCuS3f.js} +1 -1
  476. package/public/dist/assets/{dist-DMmpfLVP.js → dist-DFYRUAjN.js} +1 -1
  477. package/public/dist/assets/{dist-DdY6pTJr.js → dist-DZsFVYF4.js} +1 -1
  478. package/public/dist/assets/{dist-B0p3Eyme.js → dist-Db16ogVk.js} +1 -1
  479. package/public/dist/assets/{dist-DlnNtr6L.js → dist-DfodGES_.js} +1 -1
  480. package/public/dist/assets/{dist-DO9so2a2.js → dist-SU-YTAIg.js} +1 -1
  481. package/public/dist/assets/{dist-DUjXiMLP.js → dist-UYn7T-GH.js} +1 -1
  482. package/public/dist/assets/{dist-BW1409rz.js → dist-W51oDoeA.js} +1 -1
  483. package/public/dist/assets/{dist-BimBQZx1.js → dist-eU7TyC86.js} +1 -1
  484. package/public/dist/assets/dist-l9HH00ip.js +1 -0
  485. package/public/dist/assets/{dist-BrOPNxdH.js → dist-urPTQzXL.js} +1 -1
  486. package/public/dist/assets/{dist-AloEV3J52.js → dist-yHP6L0Ty.js} +1 -1
  487. package/public/dist/assets/{employees-Bbabvbyx.js → employees-CxdghzoD.js} +7 -7
  488. package/public/dist/assets/{error-normalize-DdvKGLt_.js → error-normalize-5n-zlEQ3.js} +1 -1
  489. package/public/dist/assets/insert-image-markdown-DIEa-zjk.js +22 -0
  490. package/public/dist/assets/{manager-DyB2ZUr9.css → manager-DEiyrWDP.css} +1 -1
  491. package/public/dist/assets/manager-UEXd1_9T.js +25 -0
  492. package/public/dist/assets/{memory-B_plvcuQ.js → memory-CsMNkYtv.js} +9 -9
  493. package/public/dist/assets/memory-DXad_DPO.js +1 -0
  494. package/public/dist/assets/{page-shell-DML_HneX.js → page-shell-D5tbivHH.js} +1 -1
  495. package/public/dist/assets/{render-DEhbfUAW.js → render-DGQX46ei.js} +2 -2
  496. package/public/dist/assets/{settings-D7F-_kYG.js → settings-BH213Yv3.js} +14 -14
  497. package/public/dist/assets/settings-DXT87G2U.js +1 -0
  498. package/public/dist/assets/settings-client-ajlwI-oK.js +1 -0
  499. package/public/dist/assets/skills-5o_1v0nz.js +1 -0
  500. package/public/dist/assets/{skills-DoGJOc0D.js → skills-CQtCtHPA.js} +6 -6
  501. package/public/dist/assets/slash-commands-D4-hrrmh.js +1 -0
  502. package/public/dist/assets/{slash-commands-khNFPOyF.js → slash-commands-Dzk1xHWS.js} +1 -1
  503. package/public/dist/assets/trace-drawer-SRKcfm2S.js +15 -0
  504. package/public/dist/assets/ui-CdRKN2S6.js +141 -0
  505. package/public/dist/assets/ui-n43jmg_f.js +1 -0
  506. package/public/dist/assets/{ws-B2aJ-nD2.js → ws-CTHQFzM1.js} +8 -8
  507. package/public/dist/index.html +2 -2
  508. package/public/dist/manager/index.html +2 -2
  509. package/public/index.html +1 -0
  510. package/public/js/constants.ts +5 -5
  511. package/public/js/diagram/iframe-renderer.ts +11 -11
  512. package/public/js/features/chat.ts +1 -1
  513. package/public/js/features/memory.ts +10 -10
  514. package/public/js/features/pending-queue.ts +2 -2
  515. package/public/js/features/process-block.ts +257 -29
  516. package/public/js/features/process-step-match.ts +18 -0
  517. package/public/js/features/settings-cli-status.ts +1 -1
  518. package/public/js/features/settings-stt.ts +37 -11
  519. package/public/js/features/settings-templates.ts +2 -2
  520. package/public/js/features/slash-commands.ts +1 -1
  521. package/public/js/features/tool-ui.ts +13 -7
  522. package/public/js/features/trace-drawer.ts +122 -0
  523. package/public/js/icons.ts +1 -1
  524. package/public/js/main.ts +28 -28
  525. package/public/js/provider-icons.ts +1 -1
  526. package/public/js/render.ts +29 -27
  527. package/public/js/sanitizer.ts +4 -0
  528. package/public/js/ui.ts +234 -68
  529. package/public/js/virtual-scroll.ts +19 -33
  530. package/public/js/ws.ts +22 -10
  531. package/public/manager/src/App.tsx +34 -6
  532. package/public/manager/src/api.ts +55 -1
  533. package/public/manager/src/components/ActivityTimeline.tsx +1 -1
  534. package/public/manager/src/components/InstanceDetailPanel.tsx +2 -2
  535. package/public/manager/src/components/InstanceGroups.tsx +5 -5
  536. package/public/manager/src/components/InstanceListContent.tsx +1 -1
  537. package/public/manager/src/dashboard-features.ts +1 -1
  538. package/public/manager/src/manager-notes.css +49 -2
  539. package/public/manager/src/notes/MarkdownEditor.tsx +7 -3
  540. package/public/manager/src/notes/NotesFileTree.tsx +45 -6
  541. package/public/manager/src/notes/NotesSidebar.tsx +24 -55
  542. package/public/manager/src/notes/NotesWorkspace.tsx +50 -3
  543. package/public/manager/src/notes/image-assets/clipboard-images.ts +100 -0
  544. package/public/manager/src/notes/image-assets/insert-image-markdown.ts +85 -0
  545. package/public/manager/src/notes/notes-api.ts +2 -0
  546. package/public/manager/src/notes/notes-types.ts +8 -1
  547. package/public/manager/src/notes/rendering/MarkdownRenderer.tsx +6 -0
  548. package/public/manager/src/notes/rendering/markdown-render-security.ts +19 -1
  549. package/public/manager/src/notes/rich-markdown/paste-policy.ts +6 -2
  550. package/public/manager/src/notes/rich-markdown/rich-markdown-extension.ts +5 -5
  551. package/public/manager/src/notes/rich-markdown/rich-widget.ts +8 -8
  552. package/public/manager/src/notes/useNotesExternalSync.ts +37 -0
  553. package/public/manager/src/notes/useNotesModel.ts +91 -0
  554. package/public/manager/src/notes/wysiwyg/MilkdownWysiwygEditor.tsx +157 -17
  555. package/public/manager/src/notes/wysiwyg/milkdown-block-keymap.ts +14 -2
  556. package/public/manager/src/notes/wysiwyg/milkdown-code-block-view.ts +20 -20
  557. package/public/manager/src/notes/wysiwyg/milkdown-heading-source-view.ts +8 -8
  558. package/public/manager/src/notes/wysiwyg/milkdown-math.ts +25 -25
  559. package/public/manager/src/settings/pages/Browser.tsx +38 -20
  560. package/public/manager/src/settings/pages/ModelProvider.tsx +11 -9
  561. package/public/manager/src/settings/pages/Network.tsx +10 -10
  562. package/public/manager/src/settings/pages/components/HealthBadge.tsx +12 -12
  563. package/public/manager/src/settings/pages/components/agent/runtime-employees-helpers.ts +12 -10
  564. package/public/manager/src/settings/pages/components/employees-helpers.ts +9 -9
  565. package/public/manager/src/settings/pages/components/heartbeat-helpers.ts +13 -13
  566. package/public/manager/src/settings/pages/components/memory-helpers.ts +4 -4
  567. package/public/manager/src/settings/pages/mcp-helpers.ts +7 -7
  568. package/public/manager/src/settings/settings-client.ts +4 -3
  569. package/public/manager/src/sync/IframeBridge.tsx +29 -0
  570. package/public/manager/src/sync/VisibilityBridge.tsx +24 -0
  571. package/public/manager/src/sync/invalidation-bus.ts +30 -0
  572. package/public/manager/src/sync/useInvalidationSubscription.ts +19 -0
  573. package/public/manager/src/types.ts +101 -0
  574. package/scripts/check-strict-baseline.mjs +255 -0
  575. package/scripts/claim-audit.mjs +159 -0
  576. package/scripts/i18n-registry.ts +4 -4
  577. package/scripts/install-officecli.sh +2 -2
  578. package/scripts/install-wsl.sh +2 -1
  579. package/scripts/release-gates.mjs +347 -0
  580. package/scripts/release-preview.sh +3 -0
  581. package/scripts/release.sh +4 -0
  582. package/scripts/smoke/opencode-external-dir-smoke.ts +14 -5
  583. package/public/dist/assets/Agent-BYdzZwD0.js +0 -1
  584. package/public/dist/assets/Browser-CkGeczuN.js +0 -1
  585. package/public/dist/assets/MilkdownWysiwygEditor-B7k9bAey.js +0 -52
  586. package/public/dist/assets/ModelProvider-CX3Qhowu.js +0 -1
  587. package/public/dist/assets/Network-DfPLFAvw.js +0 -1
  588. package/public/dist/assets/app-DLYoRkU9.js +0 -32
  589. package/public/dist/assets/dist-8zNAQAIa.js +0 -1
  590. package/public/dist/assets/dist-BgeY6nvK.js +0 -1
  591. package/public/dist/assets/dist-BzDGGxHQ.js +0 -1
  592. package/public/dist/assets/dist-D3YKbVi-.js +0 -1
  593. package/public/dist/assets/manager-CUSgFbMO.js +0 -25
  594. package/public/dist/assets/markdown-render-security-DJfJPWO-.js +0 -22
  595. package/public/dist/assets/memory-DQ6dU0qs.js +0 -1
  596. package/public/dist/assets/settings-DxLPUbpj.js +0 -1
  597. package/public/dist/assets/settings-client-CGf3uPPf.js +0 -1
  598. package/public/dist/assets/skills-yMfNYJ8m.js +0 -1
  599. package/public/dist/assets/slash-commands-CZcwr1W6.js +0 -1
  600. package/public/dist/assets/ui-04YlOMgn.js +0 -136
  601. package/public/dist/assets/ui-D6hlMjRq.js +0 -1
  602. /package/public/dist/assets/{InlineWarn-CqgWEC41.js → InlineWarn-BooBRm7o.js} +0 -0
  603. /package/public/dist/assets/{agent-meta-puNn13DV.js → agent-meta-DHddpWHQ.js} +0 -0
  604. /package/public/dist/assets/{fields-DH9JS3mb.js → fields-BtncIZYA.js} +0 -0
  605. /package/public/dist/assets/{mermaid-loader-6XC0y10y.js → mermaid-loader-BEFIOoJn.js} +0 -0
  606. /package/public/dist/assets/{path-utils-CtsDDGZg.js → path-utils-BuEEtj9w.js} +0 -0
  607. /package/public/dist/assets/{w3c-keyname-VSld09PZ.js → w3c-keyname-IiiZScED.js} +0 -0
@@ -1,4 +1,4 @@
1
- import { useCallback, useEffect, useState, type CSSProperties, type DragEvent, type KeyboardEvent, type MouseEvent } from 'react';
1
+ import { useCallback, useEffect, useMemo, useState, type CSSProperties, type DragEvent, type KeyboardEvent, type MouseEvent } from 'react';
2
2
  import type { NotesTreeEntry } from './notes-types';
3
3
 
4
4
  type NotesTrashItem = { path: string; kind: NotesTreeEntry['kind'] };
@@ -122,6 +122,12 @@ function buildTrashItems(
122
122
  return items;
123
123
  }
124
124
 
125
+ function isEditableShortcutTarget(target: EventTarget | null): boolean {
126
+ if (!(target instanceof HTMLElement)) return false;
127
+ const tag = target.tagName.toLowerCase();
128
+ return target.isContentEditable || tag === 'input' || tag === 'textarea' || tag === 'select';
129
+ }
130
+
125
131
  function rangeSelect(flatPaths: string[], anchor: string, target: string): Set<string> {
126
132
  const anchorIndex = flatPaths.indexOf(anchor);
127
133
  const targetIndex = flatPaths.indexOf(target);
@@ -324,6 +330,7 @@ export function NotesFileTree(props: NotesFileTreeProps) {
324
330
  const [dropTargetPath, setDropTargetPath] = useState<string | null>(null);
325
331
  const [multiSelected, setMultiSelected] = useState<Set<string>>(() => new Set());
326
332
  const [anchorPath, setAnchorPath] = useState<string | null>(null);
333
+ const [activeTreePath, setActiveTreePath] = useState<string | null>(null);
327
334
 
328
335
  function toggleFolder(path: string): void {
329
336
  setExpandedFolders(current => {
@@ -335,11 +342,11 @@ export function NotesFileTree(props: NotesFileTreeProps) {
335
342
  }
336
343
 
337
344
  const flatPaths = flattenEntries(props.entries, expandedFolders);
338
- const pathKindLookup = (() => {
345
+ const pathKindLookup = useMemo(() => {
339
346
  const map = new Map<string, NotesTreeEntry['kind']>();
340
347
  collectPathKinds(props.entries, map);
341
348
  return map;
342
- })();
349
+ }, [props.entries]);
343
350
 
344
351
  function trashSelected(): void {
345
352
  if (multiSelected.size === 0) return;
@@ -349,6 +356,7 @@ export function NotesFileTree(props: NotesFileTreeProps) {
349
356
  }
350
357
 
351
358
  const onEntryClick = useCallback((path: string, event: MouseEvent) => {
359
+ setActiveTreePath(path);
352
360
  if (event.shiftKey && anchorPath) {
353
361
  const range = rangeSelect(flatPaths, anchorPath, path);
354
362
  setMultiSelected(range);
@@ -374,6 +382,9 @@ export function NotesFileTree(props: NotesFileTreeProps) {
374
382
  }, [props.selectedPath]);
375
383
 
376
384
  useEffect(() => {
385
+ if (activeTreePath && !pathKindLookup.has(activeTreePath)) {
386
+ setActiveTreePath(null);
387
+ }
377
388
  if (multiSelected.size === 0) return;
378
389
  setMultiSelected(prev => {
379
390
  let changed = false;
@@ -384,17 +395,20 @@ export function NotesFileTree(props: NotesFileTreeProps) {
384
395
  }
385
396
  return changed ? next : prev;
386
397
  });
387
- }, [props.entries]);
398
+ }, [activeTreePath, multiSelected.size, pathKindLookup]);
388
399
 
389
400
  useEffect(() => {
390
401
  function handleCopyPath(event: globalThis.KeyboardEvent): void {
391
402
  if (!(event.metaKey || event.ctrlKey) || !event.shiftKey || event.key.toLowerCase() !== 'c') return;
403
+ if (isEditableShortcutTarget(event.target)) return;
392
404
  const root = props.notesRoot;
393
405
  if (!root) return;
394
406
 
395
407
  const paths = multiSelected.size > 0
396
408
  ? Array.from(multiSelected)
397
- : props.selectedPath ? [props.selectedPath] : [];
409
+ : activeTreePath && pathKindLookup.has(activeTreePath)
410
+ ? [activeTreePath]
411
+ : props.selectedPath ? [props.selectedPath] : [];
398
412
  if (paths.length === 0) return;
399
413
 
400
414
  event.preventDefault();
@@ -404,7 +418,32 @@ export function NotesFileTree(props: NotesFileTreeProps) {
404
418
 
405
419
  window.addEventListener('keydown', handleCopyPath);
406
420
  return () => window.removeEventListener('keydown', handleCopyPath);
407
- }, [multiSelected, props.selectedPath, props.notesRoot]);
421
+ }, [activeTreePath, multiSelected, pathKindLookup, props.selectedPath, props.notesRoot]);
422
+
423
+ useEffect(() => {
424
+ function handleCommandTrash(event: globalThis.KeyboardEvent): void {
425
+ if (event.defaultPrevented) return;
426
+ if (!(event.metaKey || event.ctrlKey)) return;
427
+ if (event.key !== 'Delete' && event.key !== 'Backspace') return;
428
+ if (isEditableShortcutTarget(event.target)) return;
429
+
430
+ const items = multiSelected.size > 0
431
+ ? buildTrashItems(multiSelected, pathKindLookup)
432
+ : props.selectedFolderPath && pathKindLookup.get(props.selectedFolderPath) === 'folder'
433
+ ? [{ path: props.selectedFolderPath, kind: 'folder' as const }]
434
+ : props.selectedPath && pathKindLookup.get(props.selectedPath) === 'file'
435
+ ? [{ path: props.selectedPath, kind: 'file' as const }]
436
+ : [];
437
+ if (items.length === 0) return;
438
+
439
+ event.preventDefault();
440
+ if (items.length > 1) props.onTrashPaths(items);
441
+ else props.onTrashPath(items[0].path, items[0].kind);
442
+ }
443
+
444
+ window.addEventListener('keydown', handleCommandTrash);
445
+ return () => window.removeEventListener('keydown', handleCommandTrash);
446
+ }, [multiSelected, pathKindLookup, props]);
408
447
 
409
448
  return (
410
449
  <aside
@@ -1,32 +1,21 @@
1
1
  import { useEffect, useState } from 'react';
2
- import { createNoteFile, createNoteFolder, fetchNotesInfo, fetchNotesTree, renameNotePath, trashNotePath } from './notes-api';
2
+ import { createNoteFile, createNoteFolder, renameNotePath, trashNotePath } from './notes-api';
3
3
  import { NotesFileTree } from './NotesFileTree';
4
+ import { publishInvalidation } from '../sync/invalidation-bus';
4
5
  import type { NotesTreeEntry } from './notes-types';
5
6
 
6
7
  type NotesSidebarProps = {
8
+ tree: NotesTreeEntry[];
9
+ loading: boolean;
10
+ error: string | null;
11
+ notesRoot: string | null;
7
12
  selectedPath: string | null;
8
13
  dirtyPath: string | null;
9
14
  treeWidth: number;
10
15
  onSelectedPathChange: (path: string | null) => void;
16
+ onRefreshTree: (selectPath?: string | null) => Promise<void>;
11
17
  };
12
18
 
13
- function firstFile(entries: NotesTreeEntry[]): string | null {
14
- for (const entry of entries) {
15
- if (entry.kind === 'file') return entry.path;
16
- const child = firstFile(entry.children || []);
17
- if (child) return child;
18
- }
19
- return null;
20
- }
21
-
22
- function hasFile(entries: NotesTreeEntry[], path: string): boolean {
23
- for (const entry of entries) {
24
- if (entry.kind === 'file' && entry.path === path) return true;
25
- if (hasFile(entry.children || [], path)) return true;
26
- }
27
- return false;
28
- }
29
-
30
19
  function movePathToFolder(path: string, folderPath: string | null): string {
31
20
  const parts = path.split('/').filter(Boolean);
32
21
  const name = parts[parts.length - 1];
@@ -87,35 +76,9 @@ function batchTrashConfirmMessage(items: { path: string; kind: NotesTreeEntry['k
87
76
  }
88
77
 
89
78
  export function NotesSidebar(props: NotesSidebarProps) {
90
- const [tree, setTree] = useState<NotesTreeEntry[]>([]);
91
- const [loading, setLoading] = useState(false);
92
79
  const [error, setError] = useState<string | null>(null);
93
80
  const [status, setStatus] = useState<string | null>(null);
94
81
  const [selectedFolderPath, setSelectedFolderPath] = useState<string | null>(null);
95
- const [notesRoot, setNotesRoot] = useState<string | null>(null);
96
-
97
- useEffect(() => {
98
- void fetchNotesInfo().then(info => setNotesRoot(info.root)).catch(() => {});
99
- }, []);
100
-
101
- async function refreshTree(selectPath = props.selectedPath): Promise<void> {
102
- setLoading(true);
103
- setError(null);
104
- try {
105
- const next = await fetchNotesTree();
106
- setTree(next);
107
- const nextSelected = selectPath && hasFile(next, selectPath) ? selectPath : firstFile(next);
108
- if (nextSelected !== props.selectedPath) props.onSelectedPathChange(nextSelected);
109
- } catch (err) {
110
- setError((err as Error).message);
111
- } finally {
112
- setLoading(false);
113
- }
114
- }
115
-
116
- useEffect(() => {
117
- void refreshTree();
118
- }, []);
119
82
 
120
83
  async function createNote(): Promise<void> {
121
84
  const fallback = selectedFolderPath ? `${selectedFolderPath}/untitled.md` : 'untitled.md';
@@ -125,7 +88,8 @@ export function NotesSidebar(props: NotesSidebarProps) {
125
88
  setStatus(null);
126
89
  const created = await createNoteFile(name.endsWith('.md') ? name : `${name}.md`, '');
127
90
  props.onSelectedPathChange(created.path);
128
- await refreshTree(created.path);
91
+ await props.onRefreshTree(created.path);
92
+ publishInvalidation({ topics: ['notes'], reason: 'note:created', source: 'ui', sourceId: 'notes-sidebar' });
129
93
  } catch (err) {
130
94
  setError((err as Error).message);
131
95
  }
@@ -150,7 +114,8 @@ export function NotesSidebar(props: NotesSidebarProps) {
150
114
  setStatus(null);
151
115
  const created = await createNoteFolder(name);
152
116
  setSelectedFolderPath(created.path);
153
- await refreshTree();
117
+ await props.onRefreshTree();
118
+ publishInvalidation({ topics: ['notes'], reason: 'folder:created', source: 'ui', sourceId: 'notes-sidebar' });
154
119
  } catch (err) {
155
120
  setError((err as Error).message);
156
121
  }
@@ -163,7 +128,8 @@ export function NotesSidebar(props: NotesSidebarProps) {
163
128
  setStatus(null);
164
129
  const moved = await renameNotePath(from, to);
165
130
  if (props.selectedPath === from) props.onSelectedPathChange(moved.to);
166
- await refreshTree(moved.to);
131
+ await props.onRefreshTree(moved.to);
132
+ publishInvalidation({ topics: ['notes'], reason: 'note:moved', source: 'ui', sourceId: 'notes-sidebar' });
167
133
  } catch (err) {
168
134
  setError((err as Error).message);
169
135
  }
@@ -182,7 +148,8 @@ export function NotesSidebar(props: NotesSidebarProps) {
182
148
  const nextSelectedFolderPath = rebasePath(selectedFolderPath, renamed.from, renamed.to);
183
149
  if (nextSelectedFolderPath !== selectedFolderPath) setSelectedFolderPath(nextSelectedFolderPath);
184
150
  if (nextSelectedPath !== props.selectedPath) props.onSelectedPathChange(nextSelectedPath);
185
- await refreshTree(nextSelectedPath);
151
+ await props.onRefreshTree(nextSelectedPath);
152
+ publishInvalidation({ topics: ['notes'], reason: 'note:renamed', source: 'ui', sourceId: 'notes-sidebar' });
186
153
  } catch (err) {
187
154
  setError((err as Error).message);
188
155
  }
@@ -232,7 +199,8 @@ export function NotesSidebar(props: NotesSidebarProps) {
232
199
  setStatus(`Moved ${succeeded} item${succeeded === 1 ? '' : 's'} to trash.`);
233
200
  }
234
201
 
235
- await refreshTree(selectedCleared ? null : props.selectedPath);
202
+ await props.onRefreshTree(selectedCleared ? null : props.selectedPath);
203
+ publishInvalidation({ topics: ['notes'], reason: 'notes:batch-trashed', source: 'ui', sourceId: 'notes-sidebar' });
236
204
  }
237
205
 
238
206
  async function trashPath(path: string, kind: NotesTreeEntry['kind']): Promise<void> {
@@ -256,7 +224,8 @@ export function NotesSidebar(props: NotesSidebarProps) {
256
224
  ? ` Restore from: ${result.restoreHint}`
257
225
  : '';
258
226
  setStatus(`Moved ${result.path} to ${destination}.${restoreHint}`);
259
- await refreshTree(selectedWasInside ? null : props.selectedPath);
227
+ await props.onRefreshTree(selectedWasInside ? null : props.selectedPath);
228
+ publishInvalidation({ topics: ['notes'], reason: 'note:trashed', source: 'ui', sourceId: 'notes-sidebar' });
260
229
  } catch (err) {
261
230
  setError((err as Error).message);
262
231
  }
@@ -264,16 +233,16 @@ export function NotesSidebar(props: NotesSidebarProps) {
264
233
 
265
234
  return (
266
235
  <>
267
- {error && <section className="state error-state">{error}</section>}
236
+ {(props.error || error) && <section className="state error-state">{props.error || error}</section>}
268
237
  {status && <section className="state notes-status-state">{status}</section>}
269
238
  <NotesFileTree
270
- entries={tree}
239
+ entries={props.tree}
271
240
  selectedPath={props.selectedPath}
272
241
  selectedFolderPath={selectedFolderPath}
273
242
  dirtyPath={props.dirtyPath}
274
- loading={loading}
243
+ loading={props.loading}
275
244
  width={props.treeWidth}
276
- notesRoot={notesRoot}
245
+ notesRoot={props.notesRoot}
277
246
  onSelectPath={props.onSelectedPathChange}
278
247
  onSelectFolder={setSelectedFolderPath}
279
248
  onMovePath={(from, toFolder) => void moveNote(from, toFolder)}
@@ -282,7 +251,7 @@ export function NotesSidebar(props: NotesSidebarProps) {
282
251
  onTrashPaths={items => void trashPaths(items)}
283
252
  onCreateNote={() => void createNote()}
284
253
  onCreateFolder={() => void createFolder()}
285
- onRefresh={() => void refreshTree()}
254
+ onRefresh={() => void props.onRefreshTree()}
286
255
  />
287
256
  </>
288
257
  );
@@ -1,16 +1,19 @@
1
- import { useEffect } from 'react';
1
+ import { useEffect, useRef } from 'react';
2
2
  import { MarkdownEditor } from './MarkdownEditor';
3
3
  import { MarkdownPreview } from './MarkdownPreview';
4
4
  import { NotesEmptyState } from './NotesEmptyState';
5
5
  import { NotesToolbar } from './NotesToolbar';
6
+ import { renameNotePath } from './notes-api';
6
7
  import { useNoteDocument } from './useNoteDocument';
7
- import type { NotesAuthoringMode, NotesViewMode } from './notes-types';
8
+ import { publishInvalidation } from '../sync/invalidation-bus';
9
+ import type { NotesAuthoringMode, NotesVaultIndexSnapshot, NotesViewMode } from './notes-types';
8
10
 
9
11
  type NotesPrimaryMode = 'raw' | 'preview' | 'wysiwyg';
10
12
 
11
13
  type NotesWorkspaceProps = {
12
14
  active: boolean;
13
15
  selectedPath: string | null;
16
+ vaultIndex: NotesVaultIndexSnapshot | null;
14
17
  viewMode: NotesViewMode;
15
18
  authoringMode: NotesAuthoringMode;
16
19
  wordWrap: boolean;
@@ -24,6 +27,12 @@ type NotesWorkspaceProps = {
24
27
  };
25
28
 
26
29
  const PRIMARY_MODE_CYCLE: NotesPrimaryMode[] = ['raw', 'preview', 'wysiwyg'];
30
+ const INVALID_TITLE_CHARS = /[/\\]/g;
31
+
32
+ function titleFromPath(path: string): string {
33
+ const name = path.split('/').pop() ?? path;
34
+ return name.endsWith('.md') ? name.slice(0, -3) : name;
35
+ }
27
36
 
28
37
  function primaryModeFor(viewMode: NotesViewMode, authoringMode: NotesAuthoringMode): NotesPrimaryMode {
29
38
  if (viewMode === 'preview') return 'preview';
@@ -33,6 +42,7 @@ function primaryModeFor(viewMode: NotesViewMode, authoringMode: NotesAuthoringMo
33
42
 
34
43
  export function NotesWorkspace(props: NotesWorkspaceProps) {
35
44
  const document = useNoteDocument();
45
+ const renamingRef = useRef(false);
36
46
 
37
47
  useEffect(() => {
38
48
  if (!props.selectedPath) return;
@@ -74,6 +84,34 @@ export function NotesWorkspace(props: NotesWorkspaceProps) {
74
84
  return () => window.removeEventListener('keydown', handleModeShortcut);
75
85
  }, [props.active, props.viewMode, props.authoringMode, props.onViewModeChange, props.onAuthoringModeChange]);
76
86
 
87
+ async function handleTitleBlur(event: React.FocusEvent<HTMLInputElement>): Promise<void> {
88
+ if (renamingRef.current || !props.selectedPath) return;
89
+ const newTitle = event.currentTarget.value.trim().replace(INVALID_TITLE_CHARS, '');
90
+ const currentTitle = titleFromPath(props.selectedPath);
91
+ if (!newTitle || newTitle === currentTitle) {
92
+ event.currentTarget.value = currentTitle;
93
+ return;
94
+ }
95
+ const parts = props.selectedPath.split('/');
96
+ parts[parts.length - 1] = newTitle.endsWith('.md') ? newTitle : `${newTitle}.md`;
97
+ const newPath = parts.join('/');
98
+ if (newPath === props.selectedPath) {
99
+ event.currentTarget.value = currentTitle;
100
+ return;
101
+ }
102
+ renamingRef.current = true;
103
+ try {
104
+ await renameNotePath(props.selectedPath, newPath);
105
+ props.onSelectedPathChange(newPath);
106
+ publishInvalidation({ topics: ['notes'], reason: 'note:title-renamed', source: 'ui' });
107
+ } catch (error) {
108
+ console.warn('[notes-rename]', error);
109
+ event.currentTarget.value = currentTitle;
110
+ } finally {
111
+ renamingRef.current = false;
112
+ }
113
+ }
114
+
77
115
  const showEditor = props.viewMode === 'raw' || props.viewMode === 'split';
78
116
  const showPreview = props.viewMode === 'preview' || props.viewMode === 'split';
79
117
 
@@ -128,8 +166,17 @@ export function NotesWorkspace(props: NotesWorkspaceProps) {
128
166
  {!props.selectedPath && props.viewMode !== 'settings' && <NotesEmptyState />}
129
167
  {props.selectedPath && props.viewMode !== 'settings' && (
130
168
  <div className="notes-document-grid">
169
+ <input
170
+ className="notes-inline-title"
171
+ key={props.selectedPath}
172
+ defaultValue={titleFromPath(props.selectedPath)}
173
+ onBlur={handleTitleBlur}
174
+ onKeyDown={event => { if (event.key === 'Enter') event.currentTarget.blur(); }}
175
+ spellCheck={false}
176
+ aria-label="Note title"
177
+ />
131
178
  {showEditor && <div className="notes-editor-pane">
132
- <MarkdownEditor key={props.selectedPath} active={props.active && showEditor} authoringMode={props.authoringMode} content={document.content} wordWrap={props.wordWrap} onChange={document.setContent} />
179
+ <MarkdownEditor key={props.selectedPath} active={props.active && showEditor} authoringMode={props.authoringMode} content={document.content} notePath={props.selectedPath} wordWrap={props.wordWrap} onChange={document.setContent} />
133
180
  </div>}
134
181
  {showPreview && <MarkdownPreview markdown={document.content} />}
135
182
  </div>
@@ -0,0 +1,100 @@
1
+ const ALLOWED_IMAGE_TYPES = new Set(['image/png', 'image/jpeg', 'image/webp', 'image/gif']);
2
+
3
+ const EXTENSION_BY_MIME: Record<string, string> = {
4
+ 'image/png': 'png',
5
+ 'image/jpeg': 'jpg',
6
+ 'image/webp': 'webp',
7
+ 'image/gif': 'gif',
8
+ };
9
+
10
+ const DATA_IMAGE_RE = /<img\b[^>]*\bsrc=["'](data:(image\/(?:png|jpeg|webp|gif));base64,([^"']+))["'][^>]*>/i;
11
+ const HTML_IMAGE_SRC_RE = /<img\b[^>]*\bsrc=["']([^"']+)["'][^>]*>/i;
12
+
13
+ function isAllowedImage(file: File | null): file is File {
14
+ return Boolean(file && ALLOWED_IMAGE_TYPES.has(file.type));
15
+ }
16
+
17
+ function fileFromDataUrl(mime: string, base64: string): File | null {
18
+ if (!ALLOWED_IMAGE_TYPES.has(mime)) return null;
19
+ try {
20
+ const binary = atob(base64.replace(/\s+/g, ''));
21
+ const bytes = new Uint8Array(binary.length);
22
+ for (let index = 0; index < binary.length; index += 1) {
23
+ bytes[index] = binary.charCodeAt(index);
24
+ }
25
+ const extension = EXTENSION_BY_MIME[mime] ?? 'png';
26
+ return new File([bytes], `pasted-image.${extension}`, { type: mime });
27
+ } catch {
28
+ console.warn('[notes-image-paste] Failed to decode clipboard image data URL');
29
+ return null;
30
+ }
31
+ }
32
+
33
+ function namedClipboardFile(file: File): File {
34
+ if (file.name) return file;
35
+ const extension = EXTENSION_BY_MIME[file.type] ?? 'png';
36
+ return new File([file], `pasted-image.${extension}`, {
37
+ type: file.type,
38
+ lastModified: file.lastModified,
39
+ });
40
+ }
41
+
42
+ export function firstClipboardImage(data: DataTransfer | null): File | null {
43
+ if (!data) return null;
44
+ for (const item of Array.from(data.items ?? [])) {
45
+ if (item.kind !== 'file' || !ALLOWED_IMAGE_TYPES.has(item.type)) continue;
46
+ const file = item.getAsFile();
47
+ if (isAllowedImage(file)) return namedClipboardFile(file);
48
+ }
49
+ for (const file of Array.from(data.files ?? [])) {
50
+ if (isAllowedImage(file)) return namedClipboardFile(file);
51
+ }
52
+ const html = data.getData?.('text/html') ?? '';
53
+ const match = DATA_IMAGE_RE.exec(html);
54
+ if (match) {
55
+ const [, , mime, base64] = match;
56
+ return fileFromDataUrl(mime, base64);
57
+ }
58
+ return null;
59
+ }
60
+
61
+ const REMOTE_IMAGE_EXT_RE = /\.(?:png|jpe?g|gif|webp)$/i;
62
+
63
+ function cleanRemoteImageUrl(input: string): string | null {
64
+ const trimmed = input.trim();
65
+ if (!trimmed) return null;
66
+ try {
67
+ const url = new URL(trimmed);
68
+ if (url.protocol !== 'http:' && url.protocol !== 'https:') return null;
69
+ url.hash = '';
70
+ return url.toString();
71
+ } catch {
72
+ return null;
73
+ }
74
+ }
75
+
76
+ function looksLikeImageUrl(url: string): boolean {
77
+ try { return REMOTE_IMAGE_EXT_RE.test(new URL(url).pathname); } catch { return false; }
78
+ }
79
+
80
+ export function firstRemoteClipboardImageUrl(data: DataTransfer | null): string | null {
81
+ if (!data) return null;
82
+ const html = data.getData?.('text/html') ?? '';
83
+ const htmlMatch = HTML_IMAGE_SRC_RE.exec(html);
84
+ if (htmlMatch) {
85
+ const url = cleanRemoteImageUrl(htmlMatch[1]);
86
+ if (url && looksLikeImageUrl(url)) return url;
87
+ }
88
+ const uriList = data.getData?.('text/uri-list') ?? '';
89
+ for (const line of uriList.split(/\r?\n/)) {
90
+ if (!line || line.startsWith('#')) continue;
91
+ const url = cleanRemoteImageUrl(line);
92
+ if (url && looksLikeImageUrl(url)) return url;
93
+ }
94
+ const plain = cleanRemoteImageUrl(data.getData?.('text/plain') ?? '');
95
+ return plain && looksLikeImageUrl(plain) ? plain : null;
96
+ }
97
+
98
+ export function hasImportableClipboardImage(data: DataTransfer | null): boolean {
99
+ return Boolean(firstClipboardImage(data) || firstRemoteClipboardImageUrl(data));
100
+ }
@@ -0,0 +1,85 @@
1
+ import type { EditorView } from '@codemirror/view';
2
+ import { uploadNoteAsset, uploadRemoteNoteAsset } from '../../api';
3
+ import { firstClipboardImage, firstRemoteClipboardImageUrl, hasImportableClipboardImage } from './clipboard-images';
4
+
5
+ export type NotesImagePasteOptions = {
6
+ notePath: string;
7
+ onError?: (error: Error) => void;
8
+ };
9
+
10
+ function errorFromUnknown(error: unknown): Error {
11
+ return error instanceof Error ? error : new Error(String(error));
12
+ }
13
+
14
+ const NOTE_IMAGE_MAX_BYTES = 5 * 1024 * 1024;
15
+
16
+ async function compressImageFile(file: File, maxBytes: number): Promise<File> {
17
+ const bitmap = await createImageBitmap(file);
18
+ const maxDim = 4096;
19
+ let { width, height } = bitmap;
20
+ if (width > maxDim || height > maxDim) {
21
+ const scale = maxDim / Math.max(width, height);
22
+ width = Math.round(width * scale);
23
+ height = Math.round(height * scale);
24
+ }
25
+ const canvas = new OffscreenCanvas(width, height);
26
+ const ctx = canvas.getContext('2d');
27
+ if (!ctx) throw new Error('Canvas 2D context unavailable');
28
+ ctx.drawImage(bitmap, 0, 0, width, height);
29
+ bitmap.close();
30
+ const name = file.name.replace(/\.\w+$/, '') + '.jpg';
31
+ for (const quality of [0.85, 0.7, 0.5, 0.3]) {
32
+ const blob = await canvas.convertToBlob({ type: 'image/jpeg', quality });
33
+ if (blob.size <= maxBytes) {
34
+ return new File([blob], name, { type: 'image/jpeg', lastModified: file.lastModified });
35
+ }
36
+ }
37
+ const half = new OffscreenCanvas(Math.round(width / 2), Math.round(height / 2));
38
+ const halfCtx = half.getContext('2d');
39
+ if (!halfCtx) throw new Error('Canvas 2D context unavailable');
40
+ halfCtx.drawImage(canvas, 0, 0, half.width, half.height);
41
+ const blob = await half.convertToBlob({ type: 'image/jpeg', quality: 0.7 });
42
+ if (blob.size > maxBytes) throw new Error('Image too large even after compression');
43
+ return new File([blob], name, { type: 'image/jpeg', lastModified: file.lastModified });
44
+ }
45
+
46
+ export async function uploadClipboardImageMarkdown(notePath: string, data: DataTransfer | null): Promise<string | null> {
47
+ let image = firstClipboardImage(data);
48
+ const remoteUrl = image ? null : firstRemoteClipboardImageUrl(data);
49
+ if (!image && !remoteUrl) return null;
50
+ if (image && image.size > NOTE_IMAGE_MAX_BYTES) {
51
+ image = await compressImageFile(image, NOTE_IMAGE_MAX_BYTES);
52
+ }
53
+ const result = image
54
+ ? await uploadNoteAsset(notePath, image)
55
+ : await uploadRemoteNoteAsset(notePath, remoteUrl!);
56
+ return result.markdown;
57
+ }
58
+
59
+ export function handleImageDataTransfer(
60
+ event: ClipboardEvent | DragEvent,
61
+ view: EditorView,
62
+ options: NotesImagePasteOptions,
63
+ ): boolean {
64
+ const data = 'clipboardData' in event ? event.clipboardData : event.dataTransfer;
65
+ if (!hasImportableClipboardImage(data)) return false;
66
+ const textFallback = data?.getData('text/plain') ?? '';
67
+ event.preventDefault();
68
+ void uploadClipboardImageMarkdown(options.notePath, data)
69
+ .then(result => {
70
+ if (result) view.dispatch(view.state.replaceSelection(result));
71
+ })
72
+ .catch(error => {
73
+ if (textFallback) view.dispatch(view.state.replaceSelection(textFallback));
74
+ options.onError?.(errorFromUnknown(error));
75
+ });
76
+ return true;
77
+ }
78
+
79
+ export function handleClipboardImagePaste(
80
+ event: ClipboardEvent,
81
+ view: EditorView,
82
+ options: NotesImagePasteOptions,
83
+ ): boolean {
84
+ return handleImageDataTransfer(event, view, options);
85
+ }
@@ -1,5 +1,7 @@
1
1
  export {
2
2
  fetchNotesInfo,
3
+ fetchNotesIndex,
4
+ fetchNotesCapabilities,
3
5
  fetchNotesTree,
4
6
  fetchNoteFile,
5
7
  createNoteFile,
@@ -1,4 +1,9 @@
1
- import type { DashboardNoteFileResponse, DashboardNoteTreeEntry } from '../types';
1
+ import type {
2
+ DashboardNoteFileResponse,
3
+ DashboardNotesCapabilities,
4
+ DashboardNoteTreeEntry,
5
+ VaultIndexSnapshot,
6
+ } from '../types';
2
7
 
3
8
  export type NotesViewMode = 'raw' | 'split' | 'preview' | 'settings';
4
9
  export type NotesAuthoringMode = 'plain' | 'rich' | 'wysiwyg';
@@ -15,3 +20,5 @@ export type NoteConflictState = {
15
20
 
16
21
  export type NotesTreeEntry = DashboardNoteTreeEntry;
17
22
  export type NoteFile = DashboardNoteFileResponse;
23
+ export type NotesVaultIndexSnapshot = VaultIndexSnapshot;
24
+ export type NotesCapabilities = DashboardNotesCapabilities;
@@ -11,6 +11,7 @@ import { MermaidBlock } from './MermaidBlock';
11
11
  import {
12
12
  isSafeExternalHref,
13
13
  markdownSanitizeSchema,
14
+ notesImageSrc,
14
15
  safeMarkdownUrl,
15
16
  } from './markdown-render-security';
16
17
 
@@ -70,6 +71,11 @@ export function MarkdownRenderer(props: MarkdownRendererProps) {
70
71
  if (language === 'mermaid') return <MermaidBlock code={code} />;
71
72
  return <CodeBlock code={code} language={language} />;
72
73
  },
74
+ img: ({ src, alt, ...imageProps }: ComponentProps<'img'>) => {
75
+ const safeSrc = typeof src === 'string' ? notesImageSrc(src) : '';
76
+ if (!safeSrc) return null;
77
+ return <img {...imageProps} src={safeSrc} alt={alt ?? ''} loading="lazy" />;
78
+ },
73
79
  }}
74
80
  >
75
81
  {props.markdown}
@@ -17,6 +17,24 @@ export function safeMarkdownUrl(url: string): string {
17
17
  return isSafeExternalHref(url) ? url : '';
18
18
  }
19
19
 
20
+ export function notesImageSrc(src: string): string {
21
+ const trimmed = src.trim();
22
+ if (!trimmed || /^(?:javascript|data|file):/i.test(trimmed)) return '';
23
+ if (/^\/(?:Users|home|var|tmp|private|etc)\//i.test(trimmed)) return '';
24
+ const assetPath = trimmed.startsWith('./.assets/')
25
+ ? trimmed.slice(2)
26
+ : trimmed.startsWith('.assets/')
27
+ ? trimmed
28
+ : null;
29
+ if (assetPath) {
30
+ if (assetPath.includes('\\') || assetPath.split('/').some(segment => !segment || segment === '.' || segment === '..')) {
31
+ return '';
32
+ }
33
+ return `/api/dashboard/notes/asset?path=${encodeURIComponent(assetPath)}`;
34
+ }
35
+ return isSafeExternalHref(trimmed) ? trimmed : '';
36
+ }
37
+
20
38
  // rehype-raw is intentionally absent: Notes render user-authored markdown only,
21
39
  // and raw HTML must stay disabled before future WYSIWYG reuse.
22
40
  export const markdownSanitizeSchema: RehypeSanitizeSchema = {
@@ -24,7 +42,7 @@ export const markdownSanitizeSchema: RehypeSanitizeSchema = {
24
42
  attributes: {
25
43
  ...defaultSchema.attributes,
26
44
  code: [
27
- ...(defaultSchema.attributes?.code ?? []),
45
+ ...(defaultSchema.attributes?.['code'] ?? []),
28
46
  ['className', /^language-./, 'math-inline', 'math-display'],
29
47
  ],
30
48
  },
@@ -1,4 +1,5 @@
1
1
  import { EditorView } from '@codemirror/view';
2
+ import { handleClipboardImagePaste, handleImageDataTransfer, type NotesImagePasteOptions } from '../image-assets/insert-image-markdown';
2
3
 
3
4
  function htmlToPlainText(html: string): string {
4
5
  const template = document.createElement('template');
@@ -6,9 +7,10 @@ function htmlToPlainText(html: string): string {
6
7
  return template.content.textContent || '';
7
8
  }
8
9
 
9
- export function richMarkdownPastePolicy() {
10
+ export function richMarkdownPastePolicy(options?: NotesImagePasteOptions) {
10
11
  return EditorView.domEventHandlers({
11
12
  paste(event, view) {
13
+ if (options && handleClipboardImagePaste(event, view, options)) return true;
12
14
  const text = event.clipboardData?.getData('text/plain');
13
15
  if (text) return false;
14
16
  const html = event.clipboardData?.getData('text/html');
@@ -17,6 +19,8 @@ export function richMarkdownPastePolicy() {
17
19
  view.dispatch(view.state.replaceSelection(htmlToPlainText(html)));
18
20
  return true;
19
21
  },
22
+ drop(event, view) {
23
+ return Boolean(options && handleImageDataTransfer(event, view, options));
24
+ },
20
25
  });
21
26
  }
22
-