mulmoclaude 0.5.2 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (486) hide show
  1. package/Dockerfile.sandbox +100 -0
  2. package/README.md +17 -4
  3. package/bin/mulmoclaude.js +46 -15
  4. package/bin/prepare-dist.js +18 -2
  5. package/client/assets/chunk-CernVdwh.js +1 -0
  6. package/client/assets/chunk-D8eiyYIV-C1eAZMzz.js +1 -0
  7. package/client/assets/html2canvas-CDGcmOD3-BbPeutDg.js +5 -0
  8. package/client/assets/index-BbgSjFQ8.js +4968 -0
  9. package/client/assets/index-ECD0lgIv.css +2 -0
  10. package/client/assets/{index.es-D4YyL_Dg-BgT6a3Nd.js → index.es-DqtpmBm8-DJdTPdnc.js} +5 -5
  11. package/client/assets/material-symbols-outlined-BLDfUw-_.woff2 +0 -0
  12. package/client/assets/runtime-protocol-vue-6WYa8hAs.js +1 -0
  13. package/client/assets/runtime-vue-BVUzgYGA.js +1 -0
  14. package/client/assets/typeof-DBp4T-Ny-C2xoZtcz.js +1 -0
  15. package/client/assets/vue-1e_vz2LW.js +1 -0
  16. package/client/assets/vue.runtime.esm-bundler-DQ8Kjjui.js +4 -0
  17. package/client/index.html +33 -2
  18. package/package.json +20 -18
  19. package/sandbox-entrypoint.sh +106 -0
  20. package/server/accounting/accountNormalize.ts +32 -0
  21. package/server/accounting/defaultAccounts.ts +87 -0
  22. package/server/accounting/eventPublisher.ts +51 -0
  23. package/server/accounting/journal.ts +252 -0
  24. package/server/accounting/openingBalances.ts +114 -0
  25. package/server/accounting/report.ts +237 -0
  26. package/server/accounting/service.ts +718 -0
  27. package/server/accounting/snapshotCache.ts +333 -0
  28. package/server/accounting/timeSeries.ts +265 -0
  29. package/server/accounting/types.ts +148 -0
  30. package/server/agent/activeTools.ts +128 -0
  31. package/server/agent/attachmentConverter.ts +10 -5
  32. package/server/agent/backend/claude-code.ts +8 -2
  33. package/server/agent/backend/types.ts +1 -1
  34. package/server/agent/config.ts +101 -31
  35. package/server/agent/index.ts +45 -33
  36. package/server/agent/mcp-server.ts +146 -69
  37. package/server/agent/mcp-tools/index.ts +1 -5
  38. package/server/agent/mcp-tools/notify.ts +2 -22
  39. package/server/agent/mcp-tools/x.ts +0 -4
  40. package/server/agent/mcpHealth.ts +168 -0
  41. package/server/agent/plugin-names.ts +20 -77
  42. package/server/agent/prompt.ts +259 -51
  43. package/server/agent/resumeFailover.ts +1 -1
  44. package/server/agent/stream.ts +0 -1
  45. package/server/api/auth/bearerAuth.ts +5 -5
  46. package/server/api/csrfGuard.ts +1 -1
  47. package/server/api/routes/accounting.ts +366 -0
  48. package/server/api/routes/agent.ts +509 -46
  49. package/server/api/routes/attachment.ts +104 -0
  50. package/server/api/routes/chart.ts +2 -1
  51. package/server/api/routes/config.ts +12 -12
  52. package/server/api/routes/files.ts +105 -48
  53. package/server/api/routes/image.ts +70 -25
  54. package/server/api/routes/journal.ts +35 -0
  55. package/server/api/routes/mulmo-script.ts +358 -118
  56. package/server/api/routes/mulmoScriptValidate.ts +1 -1
  57. package/server/api/routes/news.ts +1 -1
  58. package/server/api/routes/notifications.ts +92 -22
  59. package/server/api/routes/notifier.ts +98 -0
  60. package/server/api/routes/pdf.ts +188 -48
  61. package/server/api/routes/plugins.ts +34 -14
  62. package/server/api/routes/presentHtml.ts +58 -3
  63. package/server/api/routes/roles.ts +1 -8
  64. package/server/api/routes/runtime-plugin.ts +224 -0
  65. package/server/api/routes/scheduler.ts +7 -5
  66. package/server/api/routes/schedulerHandlers.ts +1 -1
  67. package/server/api/routes/schedulerTasks.ts +8 -7
  68. package/server/api/routes/sessions.ts +234 -121
  69. package/server/api/routes/skills.ts +56 -51
  70. package/server/api/routes/sources.ts +52 -45
  71. package/server/api/routes/translation.ts +44 -0
  72. package/server/api/routes/wiki/frontmatter.ts +13 -65
  73. package/server/api/routes/wiki/history.ts +261 -0
  74. package/server/api/routes/wiki/pageIndex.ts +1 -1
  75. package/server/api/routes/wiki.ts +50 -26
  76. package/server/events/file-change.ts +83 -0
  77. package/server/events/notifications.ts +247 -91
  78. package/server/events/pub-sub/index.ts +1 -1
  79. package/server/events/relay-client.ts +5 -5
  80. package/server/events/scheduler-adapter.ts +2 -2
  81. package/server/events/session-store/index.ts +110 -22
  82. package/server/events/task-manager/index.ts +10 -9
  83. package/server/index.ts +509 -33
  84. package/server/notifier/engine.ts +419 -0
  85. package/server/notifier/legacy-adapters.ts +76 -0
  86. package/server/notifier/runtime-api.ts +74 -0
  87. package/server/notifier/store.ts +70 -0
  88. package/server/notifier/types.ts +121 -0
  89. package/server/plugins/dev-loader.ts +171 -0
  90. package/server/plugins/dev-watcher.ts +150 -0
  91. package/server/plugins/diagnostics.ts +188 -0
  92. package/server/plugins/preset-list.ts +52 -0
  93. package/server/plugins/preset-loader.ts +112 -0
  94. package/server/plugins/runtime-chat-api.ts +38 -0
  95. package/server/plugins/runtime-loader.ts +430 -0
  96. package/server/plugins/runtime-registry.ts +112 -0
  97. package/server/plugins/runtime-tasks-api.ts +50 -0
  98. package/server/plugins/runtime.ts +378 -0
  99. package/server/services/translation/cache.ts +72 -0
  100. package/server/services/translation/index.ts +106 -0
  101. package/server/services/translation/llm.ts +140 -0
  102. package/server/services/translation/types.ts +35 -0
  103. package/server/system/credentials.ts +13 -2
  104. package/server/system/env.ts +6 -1
  105. package/server/system/logger/formatters.ts +46 -4
  106. package/server/system/logger/index.ts +4 -4
  107. package/server/system/logger/sinks.ts +26 -5
  108. package/server/system/logger/types.ts +2 -2
  109. package/server/utils/dev-plugin-args.d.mts +11 -0
  110. package/server/utils/dev-plugin-args.mjs +43 -0
  111. package/server/utils/errors.ts +13 -4
  112. package/server/utils/files/accounting-io.ts +295 -0
  113. package/server/utils/files/atomic.ts +17 -49
  114. package/server/utils/files/attachment-store.ts +182 -0
  115. package/server/utils/files/html-io.ts +1 -7
  116. package/server/utils/files/html-store.ts +19 -0
  117. package/server/utils/files/image-store.ts +20 -22
  118. package/server/utils/files/index.ts +5 -15
  119. package/server/utils/files/journal-io.ts +7 -35
  120. package/server/utils/files/json.ts +2 -29
  121. package/server/utils/files/markdown-image-fill.ts +6 -37
  122. package/server/utils/files/markdown-store.ts +6 -21
  123. package/server/utils/files/naming.ts +3 -39
  124. package/server/utils/files/plugins-io.ts +100 -0
  125. package/server/utils/files/reference-dirs-io.ts +1 -9
  126. package/server/utils/files/roles-io.ts +2 -10
  127. package/server/utils/files/safe.ts +17 -19
  128. package/server/utils/files/scheduler-io.ts +1 -7
  129. package/server/utils/files/scheduler-overrides-io.ts +3 -12
  130. package/server/utils/files/session-io.ts +21 -30
  131. package/server/utils/files/spreadsheet-store.ts +9 -22
  132. package/server/utils/files/translation-io.ts +46 -0
  133. package/server/utils/files/user-tasks-io.ts +1 -7
  134. package/server/utils/files/workspace-io.ts +3 -79
  135. package/server/utils/gemini.ts +33 -11
  136. package/server/utils/html/htmlArtifactSplicer.ts +41 -0
  137. package/server/utils/markdown/frontmatter.ts +112 -0
  138. package/server/utils/regex.ts +56 -0
  139. package/server/utils/router.ts +41 -0
  140. package/server/utils/slug.ts +5 -3
  141. package/server/utils/time.ts +12 -0
  142. package/server/workspace/chat-index/indexer.ts +15 -2
  143. package/server/workspace/chat-index/summarizer.ts +1 -1
  144. package/server/workspace/custom-dirs.ts +1 -1
  145. package/server/workspace/helps/gemini.md +1 -1
  146. package/server/workspace/helps/guide.md +61 -0
  147. package/server/workspace/helps/index.md +4 -0
  148. package/server/workspace/helps/presenthtml.md +80 -0
  149. package/server/workspace/helps/sandbox.md +7 -0
  150. package/server/workspace/helps/storyteller.md +101 -0
  151. package/server/workspace/helps/telegram.md +1 -0
  152. package/server/workspace/helps/wiki.md +9 -7
  153. package/server/workspace/journal/archivist-cli.ts +7 -33
  154. package/server/workspace/journal/archivist-schemas.ts +5 -43
  155. package/server/workspace/journal/dailyPass.ts +34 -187
  156. package/server/workspace/journal/diff.ts +3 -28
  157. package/server/workspace/journal/index.ts +10 -81
  158. package/server/workspace/journal/indexFile.ts +3 -24
  159. package/server/workspace/journal/latestDaily.ts +51 -0
  160. package/server/workspace/journal/memoryExtractor.ts +4 -20
  161. package/server/workspace/journal/optimizationPass.ts +4 -21
  162. package/server/workspace/journal/paths.ts +4 -23
  163. package/server/workspace/journal/state.ts +6 -29
  164. package/server/workspace/memory/io.ts +213 -0
  165. package/server/workspace/memory/llm-classifier.ts +158 -0
  166. package/server/workspace/memory/migrate.ts +263 -0
  167. package/server/workspace/memory/run.ts +84 -0
  168. package/server/workspace/memory/topic-cluster.ts +218 -0
  169. package/server/workspace/memory/topic-detect.ts +67 -0
  170. package/server/workspace/memory/topic-index-hook.ts +128 -0
  171. package/server/workspace/memory/topic-io.ts +180 -0
  172. package/server/workspace/memory/topic-migrate.ts +248 -0
  173. package/server/workspace/memory/topic-run.ts +172 -0
  174. package/server/workspace/memory/topic-swap.ts +135 -0
  175. package/server/workspace/memory/topic-types.ts +142 -0
  176. package/server/workspace/memory/types.ts +83 -0
  177. package/server/workspace/news/reader.ts +4 -5
  178. package/server/workspace/paths.ts +124 -47
  179. package/server/workspace/roles.ts +2 -11
  180. package/server/workspace/skills/parser.ts +38 -55
  181. package/server/workspace/skills/user-tasks.ts +1 -2
  182. package/server/workspace/skills-preset/mc-library/SKILL.md +188 -0
  183. package/server/workspace/skills-preset.ts +196 -0
  184. package/server/workspace/sources/fetchers/githubIssues.ts +13 -11
  185. package/server/workspace/sources/fetchers/index.ts +1 -1
  186. package/server/workspace/sources/fetchers/rssParser.ts +1 -1
  187. package/server/workspace/sources/pipeline/index.ts +2 -2
  188. package/server/workspace/sources/pipeline/notify.ts +3 -3
  189. package/server/workspace/sources/pipeline/write.ts +2 -2
  190. package/server/workspace/sources/registry.ts +39 -61
  191. package/server/workspace/sources/robots.ts +1 -1
  192. package/server/workspace/tool-trace/classify.ts +2 -1
  193. package/server/workspace/tool-trace/index.ts +1 -1
  194. package/server/workspace/tool-trace/writeSearch.ts +6 -1
  195. package/server/workspace/wiki-backlinks/index.ts +19 -7
  196. package/server/workspace/wiki-backlinks/sessionBacklinks.ts +1 -0
  197. package/server/workspace/wiki-history/hook/snapshot.mjs +98 -0
  198. package/server/workspace/wiki-history/hook/snapshot.ts +135 -0
  199. package/server/workspace/wiki-history/provision.ts +181 -0
  200. package/server/workspace/wiki-pages/io.ts +217 -0
  201. package/server/workspace/wiki-pages/snapshot.ts +380 -0
  202. package/server/workspace/workspace.ts +75 -13
  203. package/src/App.vue +115 -40
  204. package/src/_runtime/protocol-vue.ts +21 -0
  205. package/src/_runtime/vue.ts +22 -0
  206. package/src/components/ChatInput.vue +14 -10
  207. package/src/components/CopyChatButton.vue +76 -0
  208. package/src/components/FileContentRenderer.vue +67 -14
  209. package/src/components/FileTree.vue +2 -2
  210. package/src/components/FilesView.vue +17 -1
  211. package/src/components/NewsView.vue +16 -2
  212. package/src/components/NotificationBell.vue +320 -93
  213. package/src/components/PageChatComposer.vue +5 -4
  214. package/src/components/PluginLauncher.vue +42 -6
  215. package/src/components/PluginScopedRoot.vue +87 -0
  216. package/src/components/RoleSelector.vue +12 -1
  217. package/src/components/RolesView.vue +562 -0
  218. package/src/components/SentAttachmentChip.vue +102 -0
  219. package/src/components/SessionHistoryPanel.vue +109 -20
  220. package/src/components/SessionRoleIcon.vue +7 -4
  221. package/src/components/SessionSidebar.vue +20 -7
  222. package/src/components/SessionTabBar.vue +1 -1
  223. package/src/components/SettingsMcpTab.vue +4 -4
  224. package/src/components/SettingsModal.vue +2 -0
  225. package/src/components/SidebarHeader.vue +16 -5
  226. package/src/components/SourcesManager.vue +23 -9
  227. package/src/components/SourcesView.vue +1 -1
  228. package/src/components/StackView.vue +102 -6
  229. package/src/components/SuggestionsPanel.vue +105 -16
  230. package/src/components/SystemFileBanner.vue +1 -1
  231. package/src/components/TodoExplorer.vue +4 -5
  232. package/src/components/todo/TodoAddDialog.vue +2 -3
  233. package/src/components/todo/TodoEditDialog.vue +1 -2
  234. package/src/components/todo/TodoEditPanel.vue +2 -3
  235. package/src/components/todo/TodoKanbanView.vue +8 -5
  236. package/src/components/todo/TodoListView.vue +3 -5
  237. package/src/components/todo/TodoTableView.vue +7 -5
  238. package/src/composables/useAccountingChannel.ts +58 -0
  239. package/src/composables/useActiveSession.ts +4 -25
  240. package/src/composables/useAppApi.ts +6 -44
  241. package/src/composables/useClipboardCopy.ts +3 -20
  242. package/src/composables/useContentDisplay.ts +33 -2
  243. package/src/composables/useDevPluginReload.ts +23 -0
  244. package/src/composables/useDynamicFavicon.ts +5 -31
  245. package/src/composables/useEventListeners.ts +0 -20
  246. package/src/composables/useExpandedDirs.ts +4 -15
  247. package/src/composables/useFaviconState.ts +12 -46
  248. package/src/composables/useFileChange.ts +53 -0
  249. package/src/composables/useFreshPluginData.ts +6 -43
  250. package/src/composables/useHealth.ts +14 -43
  251. package/src/composables/useImageErrorRepair.ts +104 -0
  252. package/src/composables/useLatestDaily.ts +40 -0
  253. package/src/composables/useMarkdownDoc.ts +39 -0
  254. package/src/composables/useMarkdownLinkHandler.ts +1 -1
  255. package/src/composables/useMcpTools.ts +3 -16
  256. package/src/composables/useNotifications.ts +138 -112
  257. package/src/composables/usePdfDownload.ts +17 -3
  258. package/src/composables/usePendingCalls.ts +8 -26
  259. package/src/composables/usePluginErrorBoundary.ts +68 -0
  260. package/src/composables/usePubSub.ts +9 -17
  261. package/src/composables/useRunElapsed.ts +5 -22
  262. package/src/composables/useSandboxStatus.ts +4 -20
  263. package/src/composables/useSessionDerived.ts +7 -15
  264. package/src/composables/useSessionHistory.ts +70 -29
  265. package/src/composables/useSessionSync.ts +25 -3
  266. package/src/composables/useSkillsList.ts +59 -0
  267. package/src/composables/useTranslatedQueries.ts +109 -0
  268. package/src/config/apiRoutes.ts +181 -80
  269. package/src/config/historyFilters.ts +5 -3
  270. package/src/config/hostEvents.ts +17 -0
  271. package/src/config/mcpCatalog.ts +277 -5
  272. package/src/config/pubsubChannels.ts +134 -12
  273. package/src/config/roles.ts +212 -147
  274. package/src/config/systemFileDescriptors.ts +5 -5
  275. package/src/config/toolNames.ts +52 -30
  276. package/src/config/workspacePaths.ts +26 -2
  277. package/src/lang/de.ts +483 -27
  278. package/src/lang/en.ts +448 -27
  279. package/src/lang/es.ts +474 -27
  280. package/src/lang/fr.ts +476 -27
  281. package/src/lang/ja.ts +465 -27
  282. package/src/lang/ko.ts +466 -27
  283. package/src/lang/pt-BR.ts +473 -27
  284. package/src/lang/zh.ts +463 -27
  285. package/src/lib/vue-i18n.ts +1 -1
  286. package/src/lib/wiki-page/slug.ts +66 -0
  287. package/src/main.ts +85 -0
  288. package/src/plugins/_extras.ts +58 -0
  289. package/src/plugins/_generated/metas.ts +42 -0
  290. package/src/plugins/_generated/registrations.ts +44 -0
  291. package/src/plugins/_generated/server-bindings.ts +47 -0
  292. package/src/plugins/accounting/Preview.vue +106 -0
  293. package/src/plugins/accounting/View.vue +632 -0
  294. package/src/plugins/accounting/actions.ts +34 -0
  295. package/src/plugins/accounting/api.ts +301 -0
  296. package/src/plugins/accounting/components/AccountEditor.vue +250 -0
  297. package/src/plugins/accounting/components/AccountRow.vue +50 -0
  298. package/src/plugins/accounting/components/AccountsList.vue +102 -0
  299. package/src/plugins/accounting/components/AccountsModal.vue +300 -0
  300. package/src/plugins/accounting/components/BalanceSheet.vue +186 -0
  301. package/src/plugins/accounting/components/BookSettings.vue +284 -0
  302. package/src/plugins/accounting/components/BookSwitcher.vue +78 -0
  303. package/src/plugins/accounting/components/DateRangePicker.vue +140 -0
  304. package/src/plugins/accounting/components/JournalEntryForm.vue +504 -0
  305. package/src/plugins/accounting/components/JournalList.vue +553 -0
  306. package/src/plugins/accounting/components/Ledger.vue +206 -0
  307. package/src/plugins/accounting/components/NewBookForm.vue +211 -0
  308. package/src/plugins/accounting/components/OpeningBalancesForm.vue +271 -0
  309. package/src/plugins/accounting/components/ProfitLoss.vue +160 -0
  310. package/src/plugins/accounting/components/accountDraft.ts +13 -0
  311. package/src/plugins/accounting/components/accountNumbering.ts +103 -0
  312. package/src/plugins/accounting/components/accountValidation.ts +75 -0
  313. package/src/plugins/accounting/components/useLatestRequest.ts +44 -0
  314. package/src/plugins/accounting/countries.ts +158 -0
  315. package/src/plugins/accounting/currencies.ts +64 -0
  316. package/src/plugins/accounting/dates.ts +51 -0
  317. package/src/plugins/accounting/definition.ts +199 -0
  318. package/src/plugins/accounting/fiscalYear.ts +136 -0
  319. package/src/plugins/accounting/index.ts +49 -0
  320. package/src/plugins/accounting/meta.ts +91 -0
  321. package/src/plugins/accounting/timeSeriesEnums.ts +16 -0
  322. package/src/plugins/api.ts +125 -0
  323. package/src/plugins/canvas/View.vue +38 -28
  324. package/src/plugins/canvas/definition.ts +10 -8
  325. package/src/plugins/canvas/index.ts +15 -8
  326. package/src/plugins/canvas/meta.ts +12 -0
  327. package/src/plugins/chart/Preview.vue +1 -1
  328. package/src/plugins/chart/View.vue +2 -2
  329. package/src/plugins/chart/definition.ts +12 -2
  330. package/src/plugins/chart/index.ts +15 -7
  331. package/src/plugins/chart/meta.ts +18 -0
  332. package/src/plugins/editImages/definition.ts +44 -0
  333. package/src/plugins/editImages/index.ts +43 -0
  334. package/src/plugins/editImages/meta.ts +5 -0
  335. package/src/plugins/generateImage/View.vue +3 -1
  336. package/src/plugins/generateImage/definition.ts +2 -0
  337. package/src/plugins/generateImage/index.ts +13 -5
  338. package/src/plugins/generateImage/meta.ts +5 -0
  339. package/src/plugins/index.ts +35 -0
  340. package/src/plugins/manageRoles/Preview.vue +7 -4
  341. package/src/plugins/manageRoles/View.vue +12 -8
  342. package/src/plugins/manageRoles/definition.ts +6 -0
  343. package/src/plugins/manageRoles/index.ts +7 -6
  344. package/src/plugins/manageSkills/View.vue +11 -7
  345. package/src/plugins/manageSkills/definition.ts +4 -1
  346. package/src/plugins/manageSkills/index.ts +14 -7
  347. package/src/plugins/manageSkills/meta.ts +21 -0
  348. package/src/plugins/manageSource/definition.ts +4 -1
  349. package/src/plugins/manageSource/index.ts +15 -7
  350. package/src/plugins/manageSource/meta.ts +21 -0
  351. package/src/plugins/markdown/Preview.vue +10 -8
  352. package/src/plugins/markdown/View.vue +84 -17
  353. package/src/plugins/markdown/definition.ts +7 -1
  354. package/src/plugins/markdown/index.ts +15 -8
  355. package/src/plugins/markdown/meta.ts +16 -0
  356. package/src/plugins/meta-types.ts +97 -0
  357. package/src/plugins/metas.ts +224 -0
  358. package/src/plugins/presentForm/Preview.vue +4 -15
  359. package/src/plugins/presentForm/View.vue +35 -78
  360. package/src/plugins/presentForm/definition.ts +7 -6
  361. package/src/plugins/presentForm/index.ts +12 -5
  362. package/src/plugins/presentForm/meta.ts +11 -0
  363. package/src/plugins/presentForm/plugin.ts +8 -9
  364. package/src/plugins/presentForm/types.ts +0 -24
  365. package/src/plugins/presentHtml/Preview.vue +1 -8
  366. package/src/plugins/presentHtml/View.vue +401 -30
  367. package/src/plugins/presentHtml/definition.ts +8 -5
  368. package/src/plugins/presentHtml/index.ts +15 -8
  369. package/src/plugins/presentHtml/meta.ts +14 -0
  370. package/src/plugins/presentMulmoScript/View.vue +327 -107
  371. package/src/plugins/presentMulmoScript/definition.ts +34 -7
  372. package/src/plugins/presentMulmoScript/helpers.ts +4 -5
  373. package/src/plugins/presentMulmoScript/index.ts +20 -7
  374. package/src/plugins/presentMulmoScript/meta.ts +52 -0
  375. package/src/plugins/scheduler/AutomationsPreview.vue +2 -8
  376. package/src/plugins/scheduler/Preview.vue +5 -2
  377. package/src/plugins/scheduler/TasksTab.vue +16 -36
  378. package/src/plugins/scheduler/View.vue +22 -54
  379. package/src/plugins/scheduler/automationsDefinition.ts +14 -9
  380. package/src/plugins/scheduler/automationsMeta.ts +5 -0
  381. package/src/plugins/scheduler/calendarDefinition.ts +4 -7
  382. package/src/plugins/scheduler/calendarMeta.ts +28 -0
  383. package/src/plugins/scheduler/formatSchedule.ts +6 -24
  384. package/src/plugins/scheduler/index.ts +26 -52
  385. package/src/plugins/scope.ts +57 -0
  386. package/src/plugins/server-bindings-types.ts +38 -0
  387. package/src/plugins/server.ts +32 -0
  388. package/src/plugins/skill/Preview.vue +25 -0
  389. package/src/plugins/skill/View.vue +125 -0
  390. package/src/plugins/skill/definition.ts +23 -0
  391. package/src/plugins/skill/index.ts +36 -0
  392. package/src/plugins/skill/plugin.ts +31 -0
  393. package/src/plugins/skill/types.ts +21 -0
  394. package/src/plugins/spreadsheet/Preview.vue +1 -3
  395. package/src/plugins/spreadsheet/View.vue +29 -49
  396. package/src/plugins/spreadsheet/cellHighlights.ts +2 -3
  397. package/src/plugins/spreadsheet/definition.ts +5 -2
  398. package/src/plugins/spreadsheet/index.ts +15 -8
  399. package/src/plugins/spreadsheet/keyboardNav.ts +38 -0
  400. package/src/plugins/spreadsheet/meta.ts +14 -0
  401. package/src/plugins/textResponse/Preview.vue +9 -1
  402. package/src/plugins/textResponse/View.vue +59 -8
  403. package/src/plugins/textResponse/index.ts +11 -3
  404. package/src/plugins/textResponse/plugin.ts +8 -10
  405. package/src/plugins/textResponse/types.ts +28 -0
  406. package/src/plugins/wiki/Preview.vue +6 -4
  407. package/src/plugins/wiki/View.vue +463 -254
  408. package/src/plugins/wiki/components/WikiPageBody.vue +159 -0
  409. package/src/plugins/wiki/helpers.ts +17 -0
  410. package/src/plugins/wiki/history/HistoryDetail.vue +325 -0
  411. package/src/plugins/wiki/history/HistoryTab.vue +167 -0
  412. package/src/plugins/wiki/history/RestoreConfirm.vue +63 -0
  413. package/src/plugins/wiki/history/api.ts +52 -0
  414. package/src/plugins/wiki/history/diff.ts +145 -0
  415. package/src/plugins/wiki/index.ts +42 -32
  416. package/src/plugins/wiki/meta.ts +10 -0
  417. package/src/plugins/wiki/pageEditLoader.ts +53 -0
  418. package/src/plugins/wiki/route.ts +8 -0
  419. package/src/router/guards.ts +2 -1
  420. package/src/router/index.ts +19 -0
  421. package/src/router/pageRoutes.ts +1 -0
  422. package/src/tools/index.ts +50 -51
  423. package/src/tools/runtimeLoader.ts +141 -0
  424. package/src/tools/types.ts +44 -1
  425. package/src/types/notification.ts +23 -0
  426. package/src/types/pastedFile.ts +10 -0
  427. package/src/types/session.ts +61 -3
  428. package/src/types/sse.ts +21 -6
  429. package/src/utils/agent/eventDispatch.ts +12 -9
  430. package/src/utils/agent/pastedAttachment.ts +35 -0
  431. package/src/utils/agent/request.ts +32 -3
  432. package/src/utils/agent/toolCalls.ts +7 -1
  433. package/src/utils/api.ts +1 -1
  434. package/src/utils/chat/exportMarkdown.ts +243 -0
  435. package/src/utils/errors.ts +10 -2
  436. package/src/utils/files/expandedDirs.ts +1 -1
  437. package/src/utils/filesPreview/todoPreview.ts +13 -2
  438. package/src/utils/format/date.ts +1 -3
  439. package/src/utils/format/jsonSyntax.ts +5 -0
  440. package/src/utils/html/iframeHeightReporterScript.ts +62 -0
  441. package/src/utils/html/previewCsp.ts +29 -2
  442. package/src/utils/image/htmlSrcAttrs.ts +122 -0
  443. package/src/utils/image/imageRepairInlineScript.ts +115 -0
  444. package/src/utils/image/resolve.ts +17 -3
  445. package/src/utils/image/rewriteMarkdownImageRefs.ts +62 -9
  446. package/src/utils/markdown/frontmatter.ts +125 -0
  447. package/src/utils/markdown/taskList.ts +7 -2
  448. package/src/utils/plugin/runtime.ts +132 -0
  449. package/src/utils/session/mergeSessions.ts +40 -37
  450. package/src/utils/session/sessionEntries.ts +74 -18
  451. package/src/utils/session/sessionHelpers.ts +54 -10
  452. package/src/utils/tools/result.ts +76 -14
  453. package/src/vite-env.d.ts +6 -0
  454. package/client/assets/html2canvas-Cx501zZr-Bug0qRNv.js +0 -5
  455. package/client/assets/index-CY-WpQUm.css +0 -2
  456. package/client/assets/index-DbTz2Mfs.js +0 -4911
  457. package/client/assets/material-symbols-outlined-NzYEeyps.woff2 +0 -0
  458. package/server/api/routes/html.ts +0 -114
  459. package/server/api/routes/todos.ts +0 -293
  460. package/server/api/routes/todosColumnsHandlers.ts +0 -333
  461. package/server/api/routes/todosHandlers.ts +0 -274
  462. package/server/api/routes/todosItemsHandlers.ts +0 -386
  463. package/server/utils/files/todos-io.ts +0 -29
  464. package/src/components/NotificationToast.vue +0 -75
  465. package/src/plugins/editImage/definition.ts +0 -27
  466. package/src/plugins/editImage/index.ts +0 -37
  467. package/src/plugins/presentHtml/helpers.ts +0 -72
  468. package/src/plugins/scheduler/LegacySchedulerView.vue +0 -32
  469. package/src/plugins/scheduler/legacyShape.ts +0 -34
  470. package/src/plugins/todo/Preview.vue +0 -68
  471. package/src/plugins/todo/View.vue +0 -378
  472. package/src/plugins/todo/composables/useTodos.ts +0 -179
  473. package/src/plugins/todo/definition.ts +0 -45
  474. package/src/plugins/todo/index.ts +0 -62
  475. package/src/plugins/todo/labels.ts +0 -163
  476. package/src/plugins/todo/priority.ts +0 -98
  477. package/src/plugins/todo/viewModes.ts +0 -19
  478. package/src/plugins/wiki/definition.ts +0 -25
  479. package/src/tools/legacyPluginNames.ts +0 -13
  480. package/src/utils/format/frontmatter.ts +0 -80
  481. package/src/utils/image/rewriteHtmlImageRefs.ts +0 -50
  482. package/src/utils/notification/dispatch.ts +0 -58
  483. /package/client/assets/{purify.es-Fx1Nqyry-BwJECkqS.js → purify.es-Fx1Nqyry-BSVNht6S.js} +0 -0
  484. /package/src/plugins/{editImage → editImages}/Preview.vue +0 -0
  485. /package/src/plugins/{editImage → editImages}/View.vue +0 -0
  486. /package/src/{config/schedulerActions.ts → plugins/scheduler/actions.ts} +0 -0
@@ -0,0 +1,102 @@
1
+ <template>
2
+ <img v-if="isImage" :src="rawUrl" :alt="basename" :title="basename" :class="imgClass" data-testid="sent-attachment-chip" :data-variant="variant" />
3
+ <div v-else :title="basename" :class="fileChipClass" data-testid="sent-attachment-chip" :data-variant="variant">
4
+ <span class="material-icons" :class="[iconColor, fileIconSize]">{{ icon }}</span>
5
+ <span class="truncate">{{ basename }}</span>
6
+ </div>
7
+ </template>
8
+
9
+ <script setup lang="ts">
10
+ import { computed } from "vue";
11
+ import { API_ROUTES } from "../config/apiRoutes";
12
+
13
+ // "thumb" — small, used by the sidebar Preview (single-mode chat list).
14
+ // "block" — fills the canvas content width, used by the in-bubble
15
+ // View on the canvas (stack and single mode).
16
+ type Variant = "thumb" | "block";
17
+
18
+ const props = withDefaults(
19
+ defineProps<{
20
+ /** Workspace-relative path (e.g. `data/attachments/2026/04/<id>.png`). */
21
+ path: string;
22
+ variant?: Variant;
23
+ }>(),
24
+ { variant: "thumb" },
25
+ );
26
+
27
+ const IMAGE_EXTS = new Set([".png", ".jpg", ".jpeg", ".gif", ".webp", ".svg"]);
28
+
29
+ const ext = computed(() => {
30
+ const dot = props.path.lastIndexOf(".");
31
+ return dot >= 0 ? props.path.slice(dot).toLowerCase() : "";
32
+ });
33
+
34
+ const basename = computed(() => {
35
+ const slash = props.path.lastIndexOf("/");
36
+ return slash >= 0 ? props.path.slice(slash + 1) : props.path;
37
+ });
38
+
39
+ const isImage = computed(() => IMAGE_EXTS.has(ext.value));
40
+
41
+ // `/api/files/raw` is auth-exempt for `<img src>` use (see
42
+ // server/index.ts:114) so the path round-trips without a bearer token.
43
+ const rawUrl = computed(() => `${API_ROUTES.files.raw}?path=${encodeURIComponent(props.path)}`);
44
+
45
+ const imgClass = computed(() =>
46
+ props.variant === "block"
47
+ ? // Canvas: stretch to the bubble's content width, cap height so
48
+ // tall images don't dominate. `object-contain` preserves aspect
49
+ // ratio when the natural ratio would push past max-h.
50
+ "block w-full max-h-[60vh] object-contain rounded-lg border border-gray-200 bg-gray-50"
51
+ : // Sidebar preview: small thumbnail.
52
+ "block max-h-16 max-w-24 object-contain rounded border border-gray-300 bg-white",
53
+ );
54
+
55
+ const fileChipClass = computed(() => {
56
+ const base = "inline-flex items-center gap-1.5 border border-gray-300 bg-white rounded text-gray-700";
57
+ return props.variant === "block" ? `${base} px-3 py-2 text-sm max-w-full` : `${base} px-2 py-1 text-xs`;
58
+ });
59
+
60
+ const fileIconSize = computed(() => (props.variant === "block" ? "text-xl" : "text-base"));
61
+
62
+ const icon = computed(() => {
63
+ switch (ext.value) {
64
+ case ".pdf":
65
+ return "picture_as_pdf";
66
+ case ".docx":
67
+ return "description";
68
+ case ".xlsx":
69
+ case ".csv":
70
+ return "table_chart";
71
+ case ".pptx":
72
+ return "slideshow";
73
+ case ".txt":
74
+ case ".md":
75
+ return "article";
76
+ case ".json":
77
+ case ".xml":
78
+ case ".yaml":
79
+ case ".yml":
80
+ case ".toml":
81
+ return "data_object";
82
+ default:
83
+ return "insert_drive_file";
84
+ }
85
+ });
86
+
87
+ const iconColor = computed(() => {
88
+ switch (ext.value) {
89
+ case ".pdf":
90
+ return "text-red-500";
91
+ case ".docx":
92
+ return "text-blue-500";
93
+ case ".xlsx":
94
+ case ".csv":
95
+ return "text-green-600";
96
+ case ".pptx":
97
+ return "text-orange-500";
98
+ default:
99
+ return "text-gray-500";
100
+ }
101
+ });
102
+ </script>
@@ -14,7 +14,7 @@
14
14
  :label="t(`sessionHistoryPanel.filters.${f}`)"
15
15
  :count="f === HISTORY_FILTERS.all ? undefined : countByOrigin(f)"
16
16
  :data-testid="`session-filter-${f}`"
17
- @click="activeFilter = f"
17
+ @click="toggleFilter(f)"
18
18
  />
19
19
  </div>
20
20
 
@@ -44,13 +44,53 @@
44
44
  @keydown.space.prevent.self="(e) => !e.repeat && emit('loadSession', session.id)"
45
45
  >
46
46
  <!-- Timestamp pill straddling the top border, mirroring the
47
- SessionSidebar card design. The running indicator
48
- still renders inline in the meta line below (it's a
49
- status, not a time); unread is signalled solely through
50
- previewClasses (bold text). -->
51
- <span class="absolute top-0 right-2 -translate-y-1/2 bg-white px-1 text-[10px] text-gray-400 leading-none pointer-events-none">
52
- {{ formatDate(session.updatedAt) }}
53
- </span>
47
+ SessionSidebar card design. The kebab "..." button sits
48
+ next to it on the same border line clicking opens a
49
+ popover with delete + bookmark actions. The running
50
+ indicator still renders inline in the meta line below
51
+ (it's a status, not a time); unread is signalled through
52
+ previewClasses (bold text); bookmark state is signalled
53
+ via the green role icon. -->
54
+ <div class="absolute top-0 right-6 -translate-y-1/2 flex items-center gap-1 bg-white px-1 leading-none">
55
+ <span class="text-[10px] text-gray-400 pointer-events-none">{{ formatDate(session.updatedAt) }}</span>
56
+ <button
57
+ type="button"
58
+ class="flex items-center justify-center px-0.5 border border-gray-300 rounded-md text-gray-400 hover:text-gray-700 hover:border-gray-400 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-blue-400"
59
+ :aria-label="t('sessionHistoryPanel.rowMenuAria')"
60
+ :data-testid="`session-row-menu-${session.id}`"
61
+ @click.stop="toggleMenu(session.id)"
62
+ @keydown.enter.stop
63
+ @keydown.space.stop
64
+ >
65
+ <span class="material-icons !text-[14px] leading-none" aria-hidden="true">more_horiz</span>
66
+ </button>
67
+ </div>
68
+ <div
69
+ v-if="openMenuId === session.id"
70
+ class="absolute top-2 right-2 z-10 min-w-[140px] rounded border border-gray-200 bg-white shadow-md py-1 text-xs"
71
+ role="menu"
72
+ :data-testid="`session-row-menu-popover-${session.id}`"
73
+ @click.stop
74
+ >
75
+ <button
76
+ type="button"
77
+ role="menuitem"
78
+ class="block w-full text-left px-3 py-1.5 hover:bg-gray-100"
79
+ :data-testid="`session-row-bookmark-${session.id}`"
80
+ @click.stop="onToggleBookmark(session)"
81
+ >
82
+ {{ session.isBookmarked ? t("sessionHistoryPanel.unbookmark") : t("sessionHistoryPanel.bookmark") }}
83
+ </button>
84
+ <button
85
+ type="button"
86
+ role="menuitem"
87
+ class="block w-full text-left px-3 py-1.5 text-red-600 hover:bg-red-50"
88
+ :data-testid="`session-row-delete-${session.id}`"
89
+ @click.stop="onDelete(session)"
90
+ >
91
+ {{ t("sessionHistoryPanel.delete") }}
92
+ </button>
93
+ </div>
54
94
  <div class="flex items-center gap-1.5">
55
95
  <SessionRoleIcon :session="session" :roles="roles" size="sm" />
56
96
  <p class="truncate flex-1 min-w-0" :class="previewClasses(session)">
@@ -71,7 +111,7 @@
71
111
  </template>
72
112
 
73
113
  <script setup lang="ts">
74
- import { ref, computed } from "vue";
114
+ import { ref, computed, onMounted, onBeforeUnmount } from "vue";
75
115
  import { useI18n } from "vue-i18n";
76
116
  import type { Role } from "../config/roles";
77
117
  import type { SessionSummary, SessionOrigin } from "../types/session";
@@ -83,10 +123,11 @@ import FilterChip from "./FilterChip.vue";
83
123
 
84
124
  const { t } = useI18n();
85
125
 
86
- // `unread` is mutually exclusive with origin pills — selecting it
87
- // shows every unread-flagged session regardless of origin, matching
88
- // the user expectation that "unread" is the primary question ("what
89
- // needs my attention?") rather than an origin sub-filter.
126
+ // `unread` and `bookmarked` are mutually exclusive with origin pills —
127
+ // selecting either shows every matching session regardless of origin,
128
+ // matching the user expectation that those are the primary questions
129
+ // ("what needs my attention?", "what did I save?") rather than origin
130
+ // sub-filters.
90
131
 
91
132
  const props = defineProps<{
92
133
  sessions: SessionSummary[];
@@ -98,6 +139,8 @@ const props = defineProps<{
98
139
 
99
140
  const emit = defineEmits<{
100
141
  loadSession: [id: string];
142
+ toggleBookmark: [id: string, bookmarked: boolean];
143
+ deleteSession: [id: string];
101
144
  }>();
102
145
 
103
146
  const root = ref<HTMLDivElement | null>(null);
@@ -115,16 +158,25 @@ function originOf(session: SessionSummary): SessionOrigin {
115
158
  return session.origin ?? SESSION_ORIGINS.human;
116
159
  }
117
160
 
118
- const filteredSessions = computed(() => {
119
- if (activeFilter.value === HISTORY_FILTERS.all) return props.sessions;
120
- if (activeFilter.value === HISTORY_FILTERS.unread) return props.sessions.filter((session) => session.hasUnread === true);
121
- return props.sessions.filter((session) => originOf(session) === activeFilter.value);
122
- });
161
+ function matchesFilter(session: SessionSummary, filter: HistoryFilter): boolean {
162
+ if (filter === HISTORY_FILTERS.all) return true;
163
+ if (filter === HISTORY_FILTERS.unread) return session.hasUnread === true;
164
+ if (filter === HISTORY_FILTERS.bookmarked) return session.isBookmarked === true;
165
+ return originOf(session) === filter;
166
+ }
167
+
168
+ const filteredSessions = computed(() => props.sessions.filter((session) => matchesFilter(session, activeFilter.value)));
169
+
170
+ // Mirror Wiki's toggleTagFilter (plugins/wiki/View.vue): clicking the
171
+ // already-active chip resets to `all`. The `all` chip itself is a
172
+ // no-op when active — there's nothing to "deselect" back to.
173
+ function toggleFilter(filter: HistoryFilter): void {
174
+ activeFilter.value = activeFilter.value === filter ? HISTORY_FILTERS.all : filter;
175
+ }
123
176
 
124
177
  function countByOrigin(filterKey: HistoryFilter): number {
125
178
  if (filterKey === HISTORY_FILTERS.all) return props.sessions.length;
126
- if (filterKey === HISTORY_FILTERS.unread) return props.sessions.filter((session) => session.hasUnread === true).length;
127
- return props.sessions.filter((session) => originOf(session) === filterKey).length;
179
+ return props.sessions.filter((session) => matchesFilter(session, filterKey)).length;
128
180
  }
129
181
 
130
182
  function isSessionRunning(session: SessionSummary): boolean {
@@ -144,4 +196,41 @@ function previewClasses(session: SessionSummary): string {
144
196
  if (isSessionUnread(session)) return "text-gray-900 font-bold";
145
197
  return "text-gray-700";
146
198
  }
199
+
200
+ // ── Row action menu ─────────────────────────────────────────
201
+ //
202
+ // Only one popover is open at a time, tracked by session id. A
203
+ // document-level click listener closes it on any outside click; the
204
+ // kebab button and popover stop propagation so clicks inside don't
205
+ // trigger the closer (or the row's load-session handler).
206
+
207
+ const openMenuId = ref<string | null>(null);
208
+
209
+ function toggleMenu(sessionId: string): void {
210
+ openMenuId.value = openMenuId.value === sessionId ? null : sessionId;
211
+ }
212
+
213
+ function closeMenu(): void {
214
+ openMenuId.value = null;
215
+ }
216
+
217
+ function onToggleBookmark(session: SessionSummary): void {
218
+ emit("toggleBookmark", session.id, !session.isBookmarked);
219
+ closeMenu();
220
+ }
221
+
222
+ function onDelete(session: SessionSummary): void {
223
+ const ok = window.confirm(t("sessionHistoryPanel.deleteConfirm", { preview: session.preview || t("sessionHistoryPanel.noMessages") }));
224
+ if (!ok) return;
225
+ emit("deleteSession", session.id);
226
+ closeMenu();
227
+ }
228
+
229
+ onMounted(() => {
230
+ document.addEventListener("click", closeMenu);
231
+ });
232
+
233
+ onBeforeUnmount(() => {
234
+ document.removeEventListener("click", closeMenu);
235
+ });
147
236
  </script>
@@ -3,8 +3,10 @@
3
3
  SessionHistoryPanel. Visual rules:
4
4
  - Running → yellow with a slow spin (the one place we keep
5
5
  colour because the state is load-bearing)
6
- - Unread (not running) darker gray so the row reads as
7
- "something to look at"
6
+ - Bookmarkedgreen (user-set, sits below "running" because
7
+ status beats user-state)
8
+ - Unread (not running, not bookmarked) → darker gray so the row
9
+ reads as "something to look at"
8
10
  - Default → light gray
9
11
  - Origin (scheduler / skill / bridge) → tiny B&W badge on the
10
12
  top-right. Human / unknown origin → no badge. -->
@@ -47,6 +49,7 @@ const iconSizeClass = computed(() => (props.size === "sm" ? "text-sm" : "text-ba
47
49
 
48
50
  const stateClass = computed(() => {
49
51
  if (props.session.isRunning) return "text-yellow-400";
52
+ if (props.session.isBookmarked) return "text-green-600";
50
53
  if (props.session.hasUnread) return "text-gray-900";
51
54
  return "text-gray-400";
52
55
  });
@@ -56,7 +59,7 @@ const spinClass = computed(() => (props.session.isRunning ? "animate-spin [anima
56
59
  const glyph = computed(() => roleIcon(props.roles, props.session.roleId));
57
60
 
58
61
  const originGlyph = computed(() => {
59
- const origin = props.session.origin;
62
+ const { origin } = props.session;
60
63
  if (!origin || origin === SESSION_ORIGINS.human) return "";
61
64
  if (origin === SESSION_ORIGINS.scheduler) return "schedule";
62
65
  if (origin === SESSION_ORIGINS.skill) return "build";
@@ -65,7 +68,7 @@ const originGlyph = computed(() => {
65
68
  });
66
69
 
67
70
  const originTooltip = computed(() => {
68
- const origin = props.session.origin;
71
+ const { origin } = props.session;
69
72
  if (!origin || origin === SESSION_ORIGINS.human) return "";
70
73
  return t(`sessionTabBar.origin.${origin}`);
71
74
  });
@@ -4,6 +4,7 @@
4
4
  <span v-if="sessionRoleIcon" class="material-icons text-xs leading-none">{{ sessionRoleIcon }}</span>
5
5
  <span v-if="sessionRoleName" class="truncate">{{ sessionRoleName }}</span>
6
6
  <div class="ml-auto flex items-center gap-0.5 shrink-0">
7
+ <CopyChatButton :results="results" :result-timestamps="resultTimestamps" :session-role-name="sessionRoleName" />
7
8
  <button
8
9
  type="button"
9
10
  class="h-8 w-8 flex items-center justify-center rounded text-gray-400 hover:text-gray-700 hover:bg-gray-100 transition-colors"
@@ -55,16 +56,17 @@ import type { ToolResultComplete } from "gui-chat-protocol/vue";
55
56
  import { getPlugin } from "../tools";
56
57
  import { formatSmartTime } from "../utils/format/date";
57
58
  import CanvasViewToggle from "./CanvasViewToggle.vue";
59
+ import CopyChatButton from "./CopyChatButton.vue";
58
60
  import type { LayoutMode } from "../utils/canvas/layoutMode";
59
61
 
60
62
  const { t } = useI18n();
61
63
 
62
- function sourceLabel(result: ToolResultComplete): string {
63
- if (result.toolName === "text-response") return result.title ?? "Assistant";
64
- return result.toolName;
65
- }
66
-
67
64
  defineProps<{
65
+ // Already filtered to "what the user should see" by the parent's
66
+ // `sidebarResults` computed (see useSessionDerived.ts) — keyboard
67
+ // navigation, selection, and this render all consume the same
68
+ // visible-only list so a hidden result can't slip into selected
69
+ // state and leave the sidebar showing no highlight.
68
70
  results: ToolResultComplete[];
69
71
  selectedUuid: string | null;
70
72
  resultTimestamps: Map<string, number>;
@@ -74,6 +76,18 @@ defineProps<{
74
76
  showRightSidebar: boolean;
75
77
  }>();
76
78
 
79
+ function sourceLabel(result: ToolResultComplete): string {
80
+ if (result.toolName === "text-response") return result.title ?? "Assistant";
81
+ // `action` lives on the persisted tool-result (see #670b40a5
82
+ // `feat(sidebar): use ToolResult.action for multi-feature labels`)
83
+ // but is not yet declared on `ToolResultComplete` in
84
+ // `gui-chat-protocol`. Cast to a local view so vue-tsc accepts
85
+ // the access; runtime is unchanged (it was already returning
86
+ // `undefined` for results that don't carry one).
87
+ const { action } = result as { action?: unknown };
88
+ return typeof action === "string" && action.length > 0 ? `${result.toolName}(${action})` : result.toolName;
89
+ }
90
+
77
91
  const emit = defineEmits<{
78
92
  select: [uuid: string];
79
93
  activate: [];
@@ -86,8 +100,7 @@ defineExpose({ root });
86
100
  </script>
87
101
 
88
102
  <style scoped>
89
- /* Prevent rendered markdown links inside preview cards from navigating.
90
- Clicking a sidebar card should select the result, not follow links. */
103
+ /* Card click selects the result; rendered markdown links inside the preview must not navigate. */
91
104
  :deep(a) {
92
105
  pointer-events: none;
93
106
  color: inherit;
@@ -31,7 +31,7 @@
31
31
  <script setup lang="ts">
32
32
  import { useI18n } from "vue-i18n";
33
33
  import type { Role } from "../config/roles";
34
- import { type SessionSummary } from "../types/session";
34
+ import type { SessionSummary } from "../types/session";
35
35
  import { roleName } from "../utils/role/icon";
36
36
  import SessionRoleIcon from "./SessionRoleIcon.vue";
37
37
 
@@ -294,15 +294,15 @@
294
294
  import { computed, ref } from "vue";
295
295
  import { useI18n } from "vue-i18n";
296
296
 
297
- const { t } = useI18n();
298
-
299
297
  // UI-local representation of a configured server. Matches
300
298
  // server/config.ts#McpServerEntry. Re-declared in src/config/mcpTypes.ts
301
299
  // to avoid a cross-module type import from the server package.
302
- import { type HttpSpec, type StdioSpec, type McpServerSpec, type McpServerEntry } from "../config/mcpTypes";
300
+ import type { HttpSpec, StdioSpec, McpServerSpec, McpServerEntry } from "../config/mcpTypes";
303
301
  import { MCP_CATALOG, requiredKeysOf, type McpCatalogEntry, type McpConfigField } from "../config/mcpCatalog";
304
302
  import { interpolateMcpSpec } from "../utils/mcp/interpolateSpec";
305
303
 
304
+ const { t } = useI18n();
305
+
306
306
  // Re-exported for callers that imported these from the SFC before the
307
307
  // types moved out (#823 Phase 1).
308
308
  export type { HttpSpec, StdioSpec, McpServerSpec, McpServerEntry };
@@ -350,7 +350,7 @@ function isInstalled(serverId: string): boolean {
350
350
  }
351
351
 
352
352
  function onCatalogToggle(entry: McpCatalogEntry, event: Event): void {
353
- const checked = (event.target as HTMLInputElement).checked;
353
+ const { checked } = event.target as HTMLInputElement;
354
354
  if (checked) {
355
355
  if (entry.configSchema.length > 0) {
356
356
  openConfigForm(entry);
@@ -255,6 +255,7 @@ async function loadConfig(): Promise<void> {
255
255
  mcp?: { servers: McpServerEntry[] };
256
256
  }>(API_ROUTES.config.base);
257
257
  // A newer open() has already started another load — drop this one.
258
+ // eslint-disable-next-line security/detect-possible-timing-attacks -- in-memory race-token guard, not an auth compare
258
259
  if (token !== loadToken) return;
259
260
  if (!response.ok) {
260
261
  loadError.value = response.status === 0 ? response.error || "Network error" : `Failed to load settings (HTTP ${response.status})`;
@@ -264,6 +265,7 @@ async function loadConfig(): Promise<void> {
264
265
  toolsSavedText.value = text;
265
266
  mcpServers.value = response.data.mcp?.servers ?? [];
266
267
  }
268
+ // eslint-disable-next-line security/detect-possible-timing-attacks -- same race-token guard as above
267
269
  if (token === loadToken) loading.value = false;
268
270
  }
269
271
 
@@ -2,7 +2,7 @@
2
2
  <div class="flex items-center gap-2">
3
3
  <button
4
4
  type="button"
5
- class="flex items-center gap-2 -my-1 -ml-1 py-1 pl-1 pr-2 rounded hover:bg-gray-100 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-400"
5
+ class="flex items-center gap-2 -my-1 -ml-1 py-1 pl-1 pr-1 rounded hover:bg-gray-100 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-400"
6
6
  data-testid="app-home-btn"
7
7
  :title="t('sidebarHeader.home')"
8
8
  :aria-label="t('sidebarHeader.home')"
@@ -11,7 +11,7 @@
11
11
  <img :src="logoUrl" alt="" class="h-[50px] w-auto -my-3.5 -ml-3 rounded object-contain shrink-0" />
12
12
  <!-- span, not h1: `<h1>` inside `<button>` is invalid HTML, and
13
13
  the brand label here is a clickable logo, not a page heading. -->
14
- <span data-testid="app-title" class="text-sm font-semibold text-gray-800 mr-1" :style="titleStyle">MulmoClaude</span>
14
+ <span data-testid="app-title" class="text-sm font-semibold text-gray-800" :style="titleStyle">MulmoClaude</span>
15
15
  </button>
16
16
  <div class="flex gap-0.5">
17
17
  <LockStatusPopup
@@ -21,7 +21,17 @@
21
21
  @update:open="lockPopupOpen = $event"
22
22
  @test-query="(q) => emit('testQuery', q)"
23
23
  />
24
- <NotificationBell :force-close="lockPopupOpen" @navigate="(action) => emit('notificationNavigate', action)" @update:open="onNotificationOpen" />
24
+ <NotificationBell :force-close="lockPopupOpen" @update:open="onNotificationOpen" />
25
+ <button
26
+ class="h-8 w-8 flex items-center justify-center rounded text-gray-400 hover:text-gray-700 disabled:opacity-50 disabled:cursor-not-allowed"
27
+ data-testid="today-journal-btn"
28
+ :title="t('sidebarHeader.todayJournal')"
29
+ :aria-label="t('sidebarHeader.todayJournal')"
30
+ :disabled="todayJournalLoading"
31
+ @click="openLatestDaily"
32
+ >
33
+ <span class="material-icons">today</span>
34
+ </button>
25
35
  <button
26
36
  class="relative h-8 w-8 flex items-center justify-center rounded text-gray-400 hover:text-gray-700"
27
37
  data-testid="settings-btn"
@@ -47,7 +57,7 @@ import { useI18n } from "vue-i18n";
47
57
  import LockStatusPopup from "./LockStatusPopup.vue";
48
58
  import NotificationBell from "./NotificationBell.vue";
49
59
  import { useClickOutside } from "../composables/useClickOutside";
50
- import type { NotificationPayload } from "../types/notification";
60
+ import { useLatestDaily } from "../composables/useLatestDaily";
51
61
  import logoUrl from "../assets/mulmo_bw.png";
52
62
 
53
63
  const { t } = useI18n();
@@ -63,7 +73,6 @@ const props = withDefaults(
63
73
 
64
74
  const emit = defineEmits<{
65
75
  testQuery: [query: string];
66
- notificationNavigate: [action: NotificationPayload["action"]];
67
76
  openSettings: [];
68
77
  home: [];
69
78
  }>();
@@ -75,6 +84,8 @@ const emit = defineEmits<{
75
84
  // attention signal is lost.
76
85
  const settingsLabel = computed(() => (props.geminiAvailable ? t("sidebarHeader.settings") : t("sidebarHeader.settingsGeminiMissing")));
77
86
 
87
+ const { openLatestDaily, loading: todayJournalLoading } = useLatestDaily();
88
+
78
89
  const lockPopupOpen = ref(false);
79
90
  const lockPopup = ref<{
80
91
  button: HTMLButtonElement | null;
@@ -266,7 +266,7 @@
266
266
  <PageChatComposer
267
267
  v-if="mode === 'page'"
268
268
  :placeholder="t('pluginManageSource.chatPlaceholder')"
269
- :prepend-text="`Before answering, read config/helps/sources.md for source-management conventions.`"
269
+ prepend-text="Before answering, read config/helps/sources.md for source-management conventions."
270
270
  :suggestions="SOURCE_SUGGESTIONS"
271
271
  test-id-prefix="sources-page-chat"
272
272
  />
@@ -281,6 +281,7 @@ import DOMPurify from "dompurify";
281
281
  import type { ManageSourceData, RebuildSummary, Source } from "../plugins/manageSource/index";
282
282
  import { apiGet, apiPost, apiDelete } from "../utils/api";
283
283
  import { API_ROUTES } from "../config/apiRoutes";
284
+ import { buildRouteUrl } from "../plugins/meta-types";
284
285
  import { SOURCE_FILTER_KEYS, countByFilter, matchesSourceFilter, type SourceFilterKey } from "../utils/sources/filter";
285
286
  import FilterChip from "./FilterChip.vue";
286
287
  import PageChatComposer from "./PageChatComposer.vue";
@@ -312,6 +313,7 @@ const props = defineProps<{
312
313
  }>();
313
314
 
314
315
  const localSources = ref<Source[] | null>(props.initialData?.sources ?? null);
316
+ const sources = computed<Source[]>(() => localSources.value ?? []);
315
317
  const lastRebuild = ref<RebuildSummary | null>(props.initialData?.lastRebuild ?? null);
316
318
  const highlightSlugLocal = ref<string | null>(props.initialData?.highlightSlug ?? null);
317
319
  const actionMessage = ref("");
@@ -431,7 +433,7 @@ function buildRegisterPayload(input: DraftState): RegisterPayload | BuildError {
431
433
  }
432
434
  let hostname: string;
433
435
  try {
434
- hostname = new URL(primary).hostname;
436
+ ({ hostname } = new URL(primary));
435
437
  } catch {
436
438
  return { errorKey: "pluginManageSource.errRssUrlInvalid" };
437
439
  }
@@ -479,7 +481,7 @@ async function commitAdd(): Promise<void> {
479
481
  }
480
482
  draftError.value = "";
481
483
  busy.value = "add";
482
- const response = await apiPost<unknown>(API_ROUTES.sources.create, payload);
484
+ const response = await apiPost<unknown>(API_ROUTES.sources.create.url, payload);
483
485
  if (!response.ok) {
484
486
  draftError.value = response.error || t("pluginManageSource.flashRegisterFailed");
485
487
  busy.value = null;
@@ -579,7 +581,7 @@ async function installPreset(preset: Preset): Promise<void> {
579
581
  }
580
582
  const failures: string[] = [];
581
583
  for (const entry of toRegister) {
582
- const response = await apiPost<unknown>(API_ROUTES.sources.create, {
584
+ const response = await apiPost<unknown>(API_ROUTES.sources.create.url, {
583
585
  slug: entry.slug,
584
586
  title: entry.title,
585
587
  url: entry.url,
@@ -620,7 +622,7 @@ async function installPreset(preset: Preset): Promise<void> {
620
622
  // Rebuild step extracted so commitAdd can chain it without recursing
621
623
  // into rebuild()'s own busy-state machine.
622
624
  async function rebuildInline(): Promise<void> {
623
- const response = await apiPost<RebuildSummary>(API_ROUTES.sources.rebuild);
625
+ const response = await apiPost<RebuildSummary>(API_ROUTES.sources.rebuild.url);
624
626
  if (!response.ok) {
625
627
  flash(t("pluginManageSource.flashRegisterSucceededRebuildFailed", { error: response.error }), true);
626
628
  return;
@@ -631,7 +633,6 @@ async function rebuildInline(): Promise<void> {
631
633
  await loadBrief(summary.isoDate);
632
634
  }
633
635
 
634
- const sources = computed<Source[]>(() => localSources.value ?? []);
635
636
  const highlightSlug = computed(() => highlightSlugLocal.value);
636
637
 
637
638
  // Filter chip state (#768). Single-select. `all` is the implicit
@@ -662,6 +663,10 @@ function filterChipLabel(key: SourceFilterKey): string {
662
663
  return t("pluginManageSource.filter.scheduleWeekly");
663
664
  case "schedule:manual":
664
665
  return t("pluginManageSource.filter.scheduleManual");
666
+ default: {
667
+ const exhaustive: never = key;
668
+ throw new Error(`unreachable SourceFilterKey: ${String(exhaustive)}`);
669
+ }
665
670
  }
666
671
  }
667
672
 
@@ -705,6 +710,10 @@ function kindLabel(kind: Source["fetcherKind"]): string {
705
710
  return t("pluginManageSource.kindGithubIss");
706
711
  case "arxiv":
707
712
  return t("pluginManageSource.kindArxiv");
713
+ default: {
714
+ const exhaustive: never = kind;
715
+ throw new Error(`unreachable fetcherKind: ${String(exhaustive)}`);
716
+ }
708
717
  }
709
718
  }
710
719
 
@@ -718,6 +727,10 @@ function kindBadgeClass(kind: Source["fetcherKind"]): string {
718
727
  return "bg-indigo-100 text-indigo-700";
719
728
  case "arxiv":
720
729
  return "bg-emerald-100 text-emerald-700";
730
+ default: {
731
+ const exhaustive: never = kind;
732
+ throw new Error(`unreachable fetcherKind: ${String(exhaustive)}`);
733
+ }
721
734
  }
722
735
  }
723
736
 
@@ -733,7 +746,7 @@ function flash(message: string, isError = false): void {
733
746
  // initial page-mode load) can keep that error visible in the UI
734
747
  // rather than rely on the transient flash toast.
735
748
  async function refreshList(): Promise<string | null> {
736
- const response = await apiGet<{ sources: Source[] }>(API_ROUTES.sources.list);
749
+ const response = await apiGet<{ sources: Source[] }>(API_ROUTES.sources.list.url);
737
750
  if (!response.ok) {
738
751
  flash(t("pluginManageSource.flashRefreshListFailed", { error: response.error }), true);
739
752
  return response.error || t("pluginManageSource.initialLoadFailed");
@@ -745,7 +758,7 @@ async function refreshList(): Promise<string | null> {
745
758
  async function remove(slug: string): Promise<void> {
746
759
  if (!confirm(t("pluginManageSource.confirmRemove", { slug }))) return;
747
760
  busy.value = slug;
748
- const response = await apiDelete<unknown>(API_ROUTES.sources.remove.replace(":slug", encodeURIComponent(slug)));
761
+ const response = await apiDelete<unknown>(buildRouteUrl(API_ROUTES.sources.remove, { slug }));
749
762
  busy.value = null;
750
763
  if (!response.ok) {
751
764
  flash(t("pluginManageSource.flashRemoveFailed", { error: response.error }), true);
@@ -757,7 +770,7 @@ async function remove(slug: string): Promise<void> {
757
770
 
758
771
  async function rebuild(): Promise<void> {
759
772
  busy.value = "rebuild";
760
- const response = await apiPost<RebuildSummary>(API_ROUTES.sources.rebuild);
773
+ const response = await apiPost<RebuildSummary>(API_ROUTES.sources.rebuild.url);
761
774
  if (!response.ok) {
762
775
  flash(t("pluginManageSource.flashRebuildFailed", { error: response.error }), true);
763
776
  busy.value = null;
@@ -811,6 +824,7 @@ async function loadBrief(isoDate: string): Promise<void> {
811
824
  const relPath = dailyPathFor(isoDate);
812
825
  briefFilePath.value = relPath;
813
826
  const response = await apiGet<{ content?: string; kind?: string }>(API_ROUTES.files.content, { path: relPath });
827
+ // eslint-disable-next-line security/detect-possible-timing-attacks -- in-memory race-token guard, not an auth compare
814
828
  if (token !== briefLoadToken) return;
815
829
  if (!response.ok) {
816
830
  if (response.status === 404) {
@@ -28,7 +28,7 @@ async function focusUrlSlug(slug: string): Promise<void> {
28
28
  }
29
29
 
30
30
  onMounted(() => {
31
- const slug = route.params.slug;
31
+ const { slug } = route.params;
32
32
  if (typeof slug === "string" && slug) {
33
33
  void focusUrlSlug(slug);
34
34
  }