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,159 @@
1
+ <template>
2
+ <!-- eslint-disable-next-line vue/no-v-html -- marked.parse output of app-owned wiki page body; trusted in-process render -->
3
+ <div ref="rootRef" data-testid="wiki-page-body" class="px-6 py-4 prose prose-sm max-w-none wiki-content" @click="onClick" v-html="renderedHtml" />
4
+ </template>
5
+
6
+ <script setup lang="ts">
7
+ import { computed, ref } from "vue";
8
+ import { renderWikiPageHtml } from "../helpers";
9
+ import { handleExternalLinkClick } from "../../../utils/dom/externalLink";
10
+ import { classifyWorkspacePath, resolveWikiHref } from "../../../utils/path/workspaceLinkRouter";
11
+
12
+ const props = defineProps<{
13
+ body: string;
14
+ baseDir: string;
15
+ }>();
16
+
17
+ const emit = defineEmits<{
18
+ taskCheckboxClick: [event: MouseEvent, target: HTMLInputElement];
19
+ wikiLinkClick: [slug: string];
20
+ workspaceLinkClick: [path: string];
21
+ }>();
22
+
23
+ const rootRef = ref<HTMLElement | null>(null);
24
+
25
+ const renderedHtml = computed(() => renderWikiPageHtml(props.body, props.baseDir));
26
+
27
+ defineExpose({ rootRef });
28
+
29
+ function onClick(event: MouseEvent) {
30
+ const target = event.target as HTMLElement;
31
+ if (target instanceof HTMLInputElement && target.type === "checkbox" && target.classList.contains("md-task")) {
32
+ emit("taskCheckboxClick", event, target);
33
+ return;
34
+ }
35
+ const link = target.closest(".wiki-link") as HTMLElement | null;
36
+ if (link?.dataset.page) {
37
+ emit("wikiLinkClick", link.dataset.page);
38
+ return;
39
+ }
40
+ if (handleExternalLinkClick(event)) return;
41
+ if (event.button !== 0 || event.ctrlKey || event.metaKey || event.shiftKey) return;
42
+ const anchor = target.closest("a");
43
+ if (!anchor) return;
44
+ const href = anchor.getAttribute("href");
45
+ if (!href || href.startsWith("#")) return;
46
+ const resolved = resolveWikiHref(href, props.baseDir);
47
+ if (classifyWorkspacePath(resolved)) {
48
+ event.preventDefault();
49
+ emit("workspaceLinkClick", resolved);
50
+ }
51
+ }
52
+ </script>
53
+
54
+ <style scoped>
55
+ .wiki-content :deep(.wiki-link) {
56
+ color: #2563eb;
57
+ cursor: pointer;
58
+ text-decoration: underline;
59
+ text-decoration-style: dotted;
60
+ }
61
+ .wiki-content :deep(.wiki-link:hover) {
62
+ text-decoration-style: solid;
63
+ }
64
+ .wiki-content :deep(h1) {
65
+ font-size: 1.5rem;
66
+ font-weight: 700;
67
+ margin-top: 1.5rem;
68
+ margin-bottom: 0.75rem;
69
+ color: #111827;
70
+ }
71
+ .wiki-content :deep(h1:first-child),
72
+ .wiki-content :deep(h2:first-child),
73
+ .wiki-content :deep(h3:first-child),
74
+ .wiki-content :deep(p:first-child) {
75
+ margin-top: 0;
76
+ }
77
+ .wiki-content :deep(h2) {
78
+ font-size: 1.2rem;
79
+ font-weight: 600;
80
+ margin-top: 1.25rem;
81
+ margin-bottom: 0.5rem;
82
+ color: #1f2937;
83
+ border-bottom: 1px solid #e5e7eb;
84
+ padding-bottom: 0.25rem;
85
+ }
86
+ .wiki-content :deep(h3) {
87
+ font-size: 1rem;
88
+ font-weight: 600;
89
+ margin-top: 1rem;
90
+ margin-bottom: 0.5rem;
91
+ color: #374151;
92
+ }
93
+ .wiki-content :deep(p) {
94
+ margin-bottom: 0.75rem;
95
+ line-height: 1.6;
96
+ color: #374151;
97
+ }
98
+ .wiki-content :deep(ul),
99
+ .wiki-content :deep(ol) {
100
+ margin-left: 1.5rem;
101
+ margin-bottom: 0.75rem;
102
+ }
103
+ .wiki-content :deep(li) {
104
+ margin-bottom: 0.25rem;
105
+ line-height: 1.5;
106
+ color: #374151;
107
+ }
108
+ .wiki-content :deep(ul) {
109
+ list-style-type: disc;
110
+ }
111
+ .wiki-content :deep(ol) {
112
+ list-style-type: decimal;
113
+ }
114
+ .wiki-content :deep(hr) {
115
+ border: none;
116
+ border-top: 1px solid #e5e7eb;
117
+ margin: 1rem 0;
118
+ }
119
+ .wiki-content :deep(code) {
120
+ background: #f3f4f6;
121
+ padding: 0.1rem 0.3rem;
122
+ border-radius: 0.25rem;
123
+ font-size: 0.85em;
124
+ font-family: monospace;
125
+ }
126
+ .wiki-content :deep(pre) {
127
+ background: #f3f4f6;
128
+ padding: 0.75rem;
129
+ border-radius: 0.375rem;
130
+ overflow-x: auto;
131
+ margin-bottom: 0.75rem;
132
+ }
133
+ .wiki-content :deep(pre code) {
134
+ background: none;
135
+ padding: 0;
136
+ }
137
+ .wiki-content :deep(blockquote) {
138
+ border-left: 3px solid #d1d5db;
139
+ padding-left: 1rem;
140
+ color: #6b7280;
141
+ margin: 0.75rem 0;
142
+ }
143
+ .wiki-content :deep(table) {
144
+ border-collapse: collapse;
145
+ width: 100%;
146
+ margin-bottom: 0.75rem;
147
+ font-size: 0.875rem;
148
+ }
149
+ .wiki-content :deep(th),
150
+ .wiki-content :deep(td) {
151
+ border: 1px solid #e5e7eb;
152
+ padding: 0.5rem 0.75rem;
153
+ text-align: left;
154
+ }
155
+ .wiki-content :deep(th) {
156
+ background: #f9fafb;
157
+ font-weight: 600;
158
+ }
159
+ </style>
@@ -2,6 +2,23 @@
2
2
  // `/\[\[([^\]]+)\]\]/g` regex — flagged by `sonarjs/slow-regex`
3
3
  // for backtracking risk — with a linear walker.
4
4
 
5
+ import { marked } from "marked";
6
+ import { rewriteMarkdownImageRefs } from "../../utils/image/rewriteMarkdownImageRefs";
7
+ import { makeTasksInteractive } from "../../utils/markdown/taskList";
8
+
9
+ /**
10
+ * Pure markdown→HTML pipeline shared between the standalone /wiki
11
+ * view and the chat-inline preview (Stage 3a). Caller passes a body
12
+ * that already has frontmatter stripped, plus the workspace-relative
13
+ * base dir used to rewrite image refs (`data/wiki/pages` for a page,
14
+ * `data/wiki` for log/lint).
15
+ */
16
+ export function renderWikiPageHtml(body: string, baseDir: string): string {
17
+ if (!body) return "";
18
+ const withImages = rewriteMarkdownImageRefs(body, baseDir);
19
+ return makeTasksInteractive(marked.parse(renderWikiLinks(withImages)) as string);
20
+ }
21
+
5
22
  /**
6
23
  * Replace every `[[page name]]` occurrence in `content` with a
7
24
  * `<span class="wiki-link" data-page="…">…</span>` element. The
@@ -0,0 +1,325 @@
1
+ <script setup lang="ts">
2
+ import { computed, onMounted, ref, watch } from "vue";
3
+ import { useI18n } from "vue-i18n";
4
+ import { formatSmartTime } from "../../../utils/format/date";
5
+ import { fetchHistorySnapshot, restoreHistorySnapshot, type SnapshotContent, type SnapshotSummary } from "./api";
6
+ import { DIFF_LINE_KIND, joinFrontmatterAndBody, renderUnifiedDiff, stripAutoStampKeys, type DiffHunk, type DiffLineKind } from "./diff";
7
+ import RestoreConfirm from "./RestoreConfirm.vue";
8
+
9
+ const props = defineProps<{
10
+ slug: string;
11
+ /** Summary of the snapshot being viewed (carries ts/editor/reason for the header). */
12
+ summary: SnapshotSummary;
13
+ /** Summary of the snapshot just before this one in the list (newest-first
14
+ * ordering = `summary` is at index N, `previousSummary` is at index N+1).
15
+ * null when this is the oldest entry. Used for the "compare with previous"
16
+ * toggle. */
17
+ previousSummary: SnapshotSummary | null;
18
+ /** Live page body + frontmatter, supplied by the parent so we don't
19
+ * re-fetch on tab switches. */
20
+ currentBody: string;
21
+ currentMeta: Record<string, unknown>;
22
+ }>();
23
+
24
+ const emit = defineEmits<{
25
+ back: [];
26
+ /** Fired after the server returns 200 on the restore POST. The
27
+ * parent (View.vue) handles the tab switch + success toast. */
28
+ restored: [];
29
+ }>();
30
+
31
+ const { t } = useI18n();
32
+
33
+ const COMPARE_TARGET = {
34
+ current: "current",
35
+ previous: "previous",
36
+ } as const;
37
+ type CompareTarget = (typeof COMPARE_TARGET)[keyof typeof COMPARE_TARGET];
38
+
39
+ const loading = ref(true);
40
+ const fetchError = ref<string | null>(null);
41
+ const snapshot = ref<SnapshotContent | null>(null);
42
+ const previousSnapshot = ref<SnapshotContent | null>(null);
43
+ // Separate error channel for the previous-compare fetch (codex
44
+ // iter-4 #946). Folding it into `fetchError` made a failed previous
45
+ // load take over the whole detail pane even after the user
46
+ // switched back to "current" — kept blocking valid state.
47
+ const previousFetchError = ref<string | null>(null);
48
+
49
+ const compareTarget = ref<CompareTarget>(COMPARE_TARGET.current);
50
+ const restoring = ref(false);
51
+ const restoreError = ref<string | null>(null);
52
+ const confirmOpen = ref(false);
53
+
54
+ // Stale-response guards (codex iter-1 #946). A user who clicks a
55
+ // different snapshot before the previous fetch returns would
56
+ // otherwise see the slow response overwrite the new selection.
57
+ // Two separate tokens because the "current" and "previous" fetches
58
+ // are independent.
59
+ let loadToken = 0;
60
+ let previousLoadToken = 0;
61
+
62
+ onMounted(async () => {
63
+ await loadThisSnapshot();
64
+ });
65
+
66
+ watch(
67
+ () => props.summary.stamp,
68
+ async (next, prev) => {
69
+ if (next === prev) return;
70
+ await loadThisSnapshot();
71
+ },
72
+ );
73
+
74
+ watch(compareTarget, async (target, prevTarget) => {
75
+ // Switching away from "previous" must invalidate any in-flight
76
+ // previous-snapshot load — otherwise its late failure would
77
+ // surface as `fetchError` and take over the detail pane even
78
+ // though the user is now looking at the (valid) current
79
+ // comparison (codex iter-3 #946).
80
+ if (prevTarget === COMPARE_TARGET.previous && target !== COMPARE_TARGET.previous) {
81
+ previousLoadToken += 1;
82
+ }
83
+ if (target === COMPARE_TARGET.previous && previousSnapshot.value === null && props.previousSummary !== null) {
84
+ await loadPreviousSnapshot();
85
+ }
86
+ });
87
+
88
+ async function loadThisSnapshot(): Promise<void> {
89
+ const myToken = ++loadToken;
90
+ // Invalidate any in-flight previous-snapshot load too — codex
91
+ // iter-2 #946: a slow `previousSnapshot` response was otherwise
92
+ // able to land after the user switched to a different snapshot,
93
+ // surfacing a comparison against the wrong base. Bumping
94
+ // `previousLoadToken` here guarantees that lingering response
95
+ // sees its token as stale and drops on the floor.
96
+ previousLoadToken += 1;
97
+ loading.value = true;
98
+ fetchError.value = null;
99
+ previousFetchError.value = null;
100
+ snapshot.value = null;
101
+ previousSnapshot.value = null;
102
+ compareTarget.value = COMPARE_TARGET.current;
103
+ restoreError.value = null;
104
+ const result = await fetchHistorySnapshot(props.slug, props.summary.stamp);
105
+ if (myToken !== loadToken) return;
106
+ loading.value = false;
107
+ if (!result.ok) {
108
+ fetchError.value = result.error;
109
+ return;
110
+ }
111
+ snapshot.value = result.data.snapshot;
112
+ }
113
+
114
+ async function loadPreviousSnapshot(): Promise<void> {
115
+ if (props.previousSummary === null) return;
116
+ const myToken = ++previousLoadToken;
117
+ previousFetchError.value = null;
118
+ const result = await fetchHistorySnapshot(props.slug, props.previousSummary.stamp);
119
+ if (myToken !== previousLoadToken) return;
120
+ if (!result.ok) {
121
+ previousFetchError.value = result.error;
122
+ return;
123
+ }
124
+ previousSnapshot.value = result.data.snapshot;
125
+ }
126
+
127
+ const editorBadge = computed(() => editorBadgeFor(props.summary.editor));
128
+
129
+ function markerFor(kind: DiffLineKind): string {
130
+ if (kind === DIFF_LINE_KIND.add) return "+";
131
+ if (kind === DIFF_LINE_KIND.del) return "-";
132
+ return " "; // non-breaking space — keeps the column aligned
133
+ }
134
+
135
+ function editorBadgeFor(editor: SnapshotSummary["editor"]): { label: string; className: string } {
136
+ if (editor === "llm") {
137
+ return { label: t("pluginWiki.history.editorBadgeLLM"), className: "bg-purple-50 text-purple-700" };
138
+ }
139
+ if (editor === "system") {
140
+ return { label: t("pluginWiki.history.editorBadgeSystem"), className: "bg-gray-100 text-gray-700" };
141
+ }
142
+ return { label: t("pluginWiki.history.editorBadgeUser"), className: "bg-blue-50 text-blue-700" };
143
+ }
144
+
145
+ const hunks = computed<DiffHunk[]>(() => {
146
+ if (snapshot.value === null) return [];
147
+ // Right side = the snapshot we're viewing, with auto-stamps stripped.
148
+ const rightMeta = stripAutoStampKeys(stripSnapshotMetaPatchKeys(snapshot.value.meta));
149
+ const right = joinFrontmatterAndBody(rightMeta, snapshot.value.body);
150
+
151
+ if (compareTarget.value === COMPARE_TARGET.current) {
152
+ const leftMeta = stripAutoStampKeys(props.currentMeta);
153
+ const left = joinFrontmatterAndBody(leftMeta, props.currentBody);
154
+ return renderUnifiedDiff(left, right, 3);
155
+ }
156
+ // compare with previous
157
+ if (previousSnapshot.value === null) return [];
158
+ const leftMeta = stripAutoStampKeys(stripSnapshotMetaPatchKeys(previousSnapshot.value.meta));
159
+ const left = joinFrontmatterAndBody(leftMeta, previousSnapshot.value.body);
160
+ return renderUnifiedDiff(left, right, 3);
161
+ });
162
+
163
+ const showNoPreviousMessage = computed(() => compareTarget.value === COMPARE_TARGET.previous && props.previousSummary === null);
164
+ const showPreviousFetchError = computed(() => compareTarget.value === COMPARE_TARGET.previous && previousFetchError.value !== null);
165
+ const showNoChangesMessage = computed(() => {
166
+ if (loading.value) return false;
167
+ if (snapshot.value === null) return false;
168
+ if (compareTarget.value === COMPARE_TARGET.previous && (props.previousSummary === null || previousSnapshot.value === null)) return false;
169
+ return hunks.value.length === 0;
170
+ });
171
+
172
+ /** The snapshot's frontmatter still carries `_snapshot_*` keys
173
+ * (snapshot pipeline metadata). These are NOT user data — strip
174
+ * them before any diff so they don't show up as changes against
175
+ * the live page (which never carries them). */
176
+ function stripSnapshotMetaPatchKeys(meta: Record<string, unknown>): Record<string, unknown> {
177
+ const out: Record<string, unknown> = {};
178
+ for (const [key, value] of Object.entries(meta)) {
179
+ if (key.startsWith("_snapshot_")) continue;
180
+ out[key] = value;
181
+ }
182
+ return out;
183
+ }
184
+
185
+ function openConfirm(): void {
186
+ restoreError.value = null;
187
+ confirmOpen.value = true;
188
+ }
189
+
190
+ function cancelConfirm(): void {
191
+ if (restoring.value) return;
192
+ confirmOpen.value = false;
193
+ }
194
+
195
+ async function performRestore(): Promise<void> {
196
+ restoring.value = true;
197
+ restoreError.value = null;
198
+ const result = await restoreHistorySnapshot(props.slug, props.summary.stamp);
199
+ restoring.value = false;
200
+ if (!result.ok) {
201
+ restoreError.value = result.error;
202
+ confirmOpen.value = false;
203
+ return;
204
+ }
205
+ confirmOpen.value = false;
206
+ emit("restored");
207
+ }
208
+ </script>
209
+
210
+ <template>
211
+ <div class="flex-1 flex flex-col min-h-0" data-testid="wiki-history-detail">
212
+ <!-- Top bar: back + restore -->
213
+ <div class="shrink-0 flex items-center justify-between px-4 py-2 border-b border-gray-100">
214
+ <button
215
+ type="button"
216
+ class="h-8 px-2.5 flex items-center gap-1 rounded text-sm text-gray-600 hover:bg-gray-100 transition-colors"
217
+ data-testid="wiki-history-back-button"
218
+ @click="emit('back')"
219
+ >
220
+ <span class="material-icons text-base">arrow_back</span>
221
+ {{ t("pluginWiki.history.backToList") }}
222
+ </button>
223
+ <button
224
+ type="button"
225
+ class="h-8 px-2.5 flex items-center gap-1 rounded bg-blue-600 hover:bg-blue-700 text-white text-sm disabled:opacity-60 disabled:cursor-not-allowed transition-colors"
226
+ :disabled="loading || snapshot === null || restoring"
227
+ data-testid="wiki-history-restore-button"
228
+ @click="openConfirm"
229
+ >
230
+ <span class="material-icons text-base">restore</span>
231
+ {{ t("pluginWiki.history.restoreButton") }}
232
+ </button>
233
+ </div>
234
+
235
+ <!-- Header: snapshot metadata + diff target toggle -->
236
+ <div class="shrink-0 px-4 py-3 border-b border-gray-100 flex flex-wrap items-center justify-between gap-3">
237
+ <div class="flex flex-wrap items-center gap-2 text-sm text-gray-700 min-w-0">
238
+ <span :class="['inline-flex items-center px-2 h-5 rounded text-xs font-medium', editorBadge.className]" data-testid="wiki-history-detail-editor-badge">
239
+ {{ editorBadge.label }}
240
+ </span>
241
+ <span class="text-gray-500" data-testid="wiki-history-detail-ts">
242
+ {{ formatSmartTime(props.summary.ts) }}
243
+ </span>
244
+ <span v-if="props.summary.reason" class="text-gray-700 truncate" data-testid="wiki-history-detail-reason">{{ ` — ${props.summary.reason}` }}</span>
245
+ </div>
246
+ <div class="flex border border-gray-300 rounded overflow-hidden text-sm">
247
+ <button
248
+ :class="[
249
+ 'h-8 px-2.5 transition-colors',
250
+ compareTarget === COMPARE_TARGET.current ? 'bg-blue-50 text-blue-600 font-medium' : 'bg-white text-gray-600 hover:bg-gray-50',
251
+ ]"
252
+ data-testid="wiki-history-compare-current"
253
+ @click="compareTarget = COMPARE_TARGET.current"
254
+ >
255
+ {{ t("pluginWiki.history.compareCurrent") }}
256
+ </button>
257
+ <button
258
+ :class="[
259
+ 'h-8 px-2.5 border-l border-gray-200 transition-colors',
260
+ compareTarget === COMPARE_TARGET.previous ? 'bg-blue-50 text-blue-600 font-medium' : 'bg-white text-gray-600 hover:bg-gray-50',
261
+ ]"
262
+ data-testid="wiki-history-compare-previous"
263
+ @click="compareTarget = COMPARE_TARGET.previous"
264
+ >
265
+ {{ t("pluginWiki.history.comparePrevious") }}
266
+ </button>
267
+ </div>
268
+ </div>
269
+
270
+ <!-- Inline restore-failure banner (Q10=B) -->
271
+ <div
272
+ v-if="restoreError"
273
+ class="shrink-0 mx-4 mt-3 rounded border border-red-200 bg-red-50 px-3 py-2 text-sm text-red-700"
274
+ data-testid="wiki-history-restore-error"
275
+ >
276
+ {{ t("pluginWiki.history.restoreFailureBanner", { error: restoreError }) }}
277
+ </div>
278
+
279
+ <!-- Body: loading / fetch error / no-previous / no-changes / diff -->
280
+ <div class="flex-1 overflow-y-auto px-4 py-3 font-mono text-xs">
281
+ <div v-if="loading" class="flex items-center justify-center py-8 text-gray-400">
282
+ <span class="material-icons animate-spin text-base mr-2">progress_activity</span>
283
+ {{ t("pluginWiki.history.loading") }}
284
+ </div>
285
+ <div v-else-if="fetchError" class="text-red-600">{{ fetchError }}</div>
286
+ <div v-else-if="showNoPreviousMessage" class="text-gray-500">{{ t("pluginWiki.history.diffNoPrevious") }}</div>
287
+ <div v-else-if="showPreviousFetchError" class="text-red-600">{{ previousFetchError }}</div>
288
+ <div v-else-if="showNoChangesMessage" class="text-gray-500">{{ t("pluginWiki.history.diffNoChanges") }}</div>
289
+ <div v-else>
290
+ <template v-for="(hunk, hunkIdx) in hunks" :key="hunkIdx">
291
+ <div v-if="hunk.hiddenBefore > 0" class="text-gray-400 italic px-2 py-1 border-y border-gray-100 bg-gray-50">
292
+ {{ t("pluginWiki.history.hiddenLines", { count: hunk.hiddenBefore }) }}
293
+ </div>
294
+ <div
295
+ v-for="(line, lineIdx) in hunk.lines"
296
+ :key="`${hunkIdx}-${lineIdx}`"
297
+ :class="[
298
+ 'whitespace-pre-wrap px-2 py-0.5 leading-snug',
299
+ line.kind === DIFF_LINE_KIND.add && 'bg-green-50 text-green-800',
300
+ line.kind === DIFF_LINE_KIND.del && 'bg-red-50 text-red-800',
301
+ line.kind === DIFF_LINE_KIND.context && 'text-gray-700',
302
+ ]"
303
+ :data-testid="`wiki-history-diff-line-${line.kind}`"
304
+ >
305
+ <span
306
+ :class="[
307
+ 'mr-1',
308
+ line.kind === DIFF_LINE_KIND.add && 'text-green-600',
309
+ line.kind === DIFF_LINE_KIND.del && 'text-red-600',
310
+ line.kind === DIFF_LINE_KIND.context && 'text-gray-300',
311
+ ]"
312
+ >{{ markerFor(line.kind) }}</span
313
+ >{{ line.text }}
314
+ </div>
315
+ <div v-if="hunkIdx === hunks.length - 1 && hunk.hiddenAfter > 0" class="text-gray-400 italic px-2 py-1 border-y border-gray-100 bg-gray-50">
316
+ {{ t("pluginWiki.history.hiddenLines", { count: hunk.hiddenAfter }) }}
317
+ </div>
318
+ </template>
319
+ </div>
320
+ </div>
321
+
322
+ <!-- Confirm modal -->
323
+ <RestoreConfirm v-if="confirmOpen" :snapshot="props.summary" :restoring="restoring" @cancel="cancelConfirm" @confirm="performRestore" />
324
+ </div>
325
+ </template>
@@ -0,0 +1,167 @@
1
+ <script setup lang="ts">
2
+ import { computed, onMounted, ref, watch } from "vue";
3
+ import { useI18n } from "vue-i18n";
4
+ import { formatRelativeTime, formatSmartTime } from "../../../utils/format/date";
5
+ import { fetchHistoryList, type SnapshotSummary } from "./api";
6
+ import HistoryDetail from "./HistoryDetail.vue";
7
+
8
+ const props = defineProps<{
9
+ slug: string;
10
+ /** Live page body + frontmatter, supplied by the parent so the
11
+ * detail view can diff against the current state without an
12
+ * extra fetch. */
13
+ currentBody: string;
14
+ currentMeta: Record<string, unknown>;
15
+ }>();
16
+
17
+ const emit = defineEmits<{
18
+ /** Bubbled up from the detail view after a successful restore.
19
+ * The parent (View.vue) handles the tab switch + success toast. */
20
+ restored: [];
21
+ }>();
22
+
23
+ const { t } = useI18n();
24
+
25
+ const loading = ref(true);
26
+ const fetchError = ref<string | null>(null);
27
+ const snapshots = ref<SnapshotSummary[]>([]);
28
+ /** Per Q15=B, this state persists across `Content` ↔ `History` tab
29
+ * switches as long as the slug doesn't change. The parent keeps
30
+ * the History tab mounted via `v-show`. */
31
+ const selectedStamp = ref<string | null>(null);
32
+
33
+ // Stale-response guard (codex iter-1 #946). A user who switches
34
+ // slugs faster than the network responds would otherwise see a
35
+ // late list arrive and overwrite the new slug's state. Each load
36
+ // bumps the counter; resolutions whose token has been superseded
37
+ // drop on the floor.
38
+ let loadToken = 0;
39
+
40
+ onMounted(async () => {
41
+ await loadList();
42
+ });
43
+
44
+ watch(
45
+ () => props.slug,
46
+ async (next, prev) => {
47
+ if (next === prev) return;
48
+ selectedStamp.value = null;
49
+ await loadList();
50
+ },
51
+ );
52
+
53
+ async function loadList(): Promise<void> {
54
+ const myToken = ++loadToken;
55
+ loading.value = true;
56
+ fetchError.value = null;
57
+ const result = await fetchHistoryList(props.slug);
58
+ if (myToken !== loadToken) return;
59
+ loading.value = false;
60
+ if (!result.ok) {
61
+ fetchError.value = result.error;
62
+ snapshots.value = [];
63
+ return;
64
+ }
65
+ snapshots.value = result.data.snapshots;
66
+ }
67
+
68
+ const selectedIndex = computed(() => {
69
+ if (selectedStamp.value === null) return -1;
70
+ return snapshots.value.findIndex((entry) => entry.stamp === selectedStamp.value);
71
+ });
72
+
73
+ const selectedSummary = computed<SnapshotSummary | null>(() => {
74
+ const idx = selectedIndex.value;
75
+ return idx === -1 ? null : snapshots.value[idx];
76
+ });
77
+
78
+ const previousSummary = computed<SnapshotSummary | null>(() => {
79
+ // List is newest-first; `previous` (older) is at index+1.
80
+ const idx = selectedIndex.value;
81
+ if (idx === -1) return null;
82
+ return snapshots.value[idx + 1] ?? null;
83
+ });
84
+
85
+ function selectStamp(stamp: string): void {
86
+ selectedStamp.value = stamp;
87
+ }
88
+
89
+ function clearSelection(): void {
90
+ selectedStamp.value = null;
91
+ }
92
+
93
+ function editorBadgeFor(editor: SnapshotSummary["editor"]): { label: string; className: string } {
94
+ if (editor === "llm") {
95
+ return { label: t("pluginWiki.history.editorBadgeLLM"), className: "bg-purple-50 text-purple-700" };
96
+ }
97
+ if (editor === "system") {
98
+ return { label: t("pluginWiki.history.editorBadgeSystem"), className: "bg-gray-100 text-gray-700" };
99
+ }
100
+ return { label: t("pluginWiki.history.editorBadgeUser"), className: "bg-blue-50 text-blue-700" };
101
+ }
102
+
103
+ function onRestored(): void {
104
+ emit("restored");
105
+ // After restore we expect the parent to switch tabs and the new
106
+ // restore snapshot is now the newest entry. Refresh the list so
107
+ // returning to History reflects the new state.
108
+ void loadList();
109
+ selectedStamp.value = null;
110
+ }
111
+ </script>
112
+
113
+ <template>
114
+ <div class="flex-1 flex flex-col min-h-0" data-testid="wiki-history-tab">
115
+ <!-- Loading -->
116
+ <div v-if="loading" class="flex-1 flex items-center justify-center text-gray-400 text-sm">
117
+ <span class="material-icons animate-spin text-base mr-2">progress_activity</span>
118
+ {{ t("pluginWiki.history.loading") }}
119
+ </div>
120
+
121
+ <!-- Fetch error -->
122
+ <div v-else-if="fetchError" class="m-4 rounded border border-red-200 bg-red-50 px-3 py-2 text-sm text-red-700" data-testid="wiki-history-fetch-error">
123
+ {{ fetchError }}
124
+ </div>
125
+
126
+ <!-- Detail view (Q15=B: persists when user toggles tabs) -->
127
+ <HistoryDetail
128
+ v-else-if="selectedSummary !== null"
129
+ :slug="props.slug"
130
+ :summary="selectedSummary"
131
+ :previous-summary="previousSummary"
132
+ :current-body="props.currentBody"
133
+ :current-meta="props.currentMeta"
134
+ @back="clearSelection"
135
+ @restored="onRestored"
136
+ />
137
+
138
+ <!-- Empty state -->
139
+ <div
140
+ v-else-if="snapshots.length === 0"
141
+ class="flex-1 flex items-center justify-center px-6 text-gray-400 text-sm text-center"
142
+ data-testid="wiki-history-empty"
143
+ >
144
+ <p>{{ t("pluginWiki.history.empty") }}</p>
145
+ </div>
146
+
147
+ <!-- List view -->
148
+ <div v-else class="flex-1 overflow-y-auto" data-testid="wiki-history-list">
149
+ <button
150
+ v-for="entry in snapshots"
151
+ :key="entry.stamp"
152
+ type="button"
153
+ class="w-full text-left px-4 py-2 border-b border-gray-100 hover:bg-blue-50 transition-colors flex items-baseline gap-3"
154
+ :data-testid="`wiki-history-row-${entry.stamp}`"
155
+ @click="selectStamp(entry.stamp)"
156
+ >
157
+ <span :class="['inline-flex items-center px-2 h-5 rounded text-xs font-medium shrink-0', editorBadgeFor(entry.editor).className]">
158
+ {{ editorBadgeFor(entry.editor).label }}
159
+ </span>
160
+ <span class="text-sm text-gray-700 shrink-0" :title="formatSmartTime(entry.ts)">
161
+ {{ formatRelativeTime(entry.ts) }}
162
+ </span>
163
+ <span v-if="entry.reason" class="text-sm text-gray-500 truncate">{{ ` — ${entry.reason}` }}</span>
164
+ </button>
165
+ </div>
166
+ </div>
167
+ </template>