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,553 @@
1
+ <template>
2
+ <div class="flex flex-col h-full gap-3">
3
+ <!-- Top-row toolbar slot. Renders the embedded entry form
4
+ in "+ New entry" mode here; Edit-mode for a row's existing
5
+ entry is rendered IN-PLACE inside that row's expanded
6
+ detail panel below. The date picker / account filter /
7
+ table below stay visible in either state. -->
8
+ <div v-if="showNewForm" class="border border-gray-200 rounded p-3" data-testid="accounting-journal-inline-form">
9
+ <JournalEntryForm
10
+ :book-id="bookId"
11
+ :accounts="accounts"
12
+ :currency="currency"
13
+ :country="country"
14
+ :entry-to-edit="null"
15
+ @submitted="onFormSubmitted"
16
+ @cancel="onFormCancel"
17
+ />
18
+ </div>
19
+ <div v-else class="flex items-center justify-end">
20
+ <button
21
+ type="button"
22
+ class="h-8 px-2.5 flex items-center gap-1 rounded border border-gray-300 text-sm text-gray-600 hover:bg-gray-50"
23
+ data-testid="accounting-journal-new-entry"
24
+ @click="onOpenNewEntry"
25
+ >
26
+ <span class="material-icons text-base">add</span>
27
+ <span>{{ t("pluginAccounting.tabs.newEntry") }}</span>
28
+ </button>
29
+ </div>
30
+ <div class="flex flex-wrap items-end gap-2">
31
+ <DateRangePicker v-model="range" :fiscal-year-end="resolvedFiscalYearEnd" :opening-date="openingDate" />
32
+ <label class="text-xs text-gray-500 flex flex-col gap-1">
33
+ {{ t("pluginAccounting.journalList.accountLabel") }}
34
+ <select v-model="accountCode" class="h-8 px-2 rounded border border-gray-300 text-sm bg-white" data-testid="accounting-journal-account">
35
+ <option value="">{{ t("pluginAccounting.journalList.allAccounts") }}</option>
36
+ <option v-for="account in accounts" :key="account.code" :value="account.code">{{ formatAccountLabel(account) }}</option>
37
+ </select>
38
+ </label>
39
+ <button class="h-8 px-2.5 rounded border border-gray-300 text-sm text-gray-600 hover:bg-gray-50" @click="refresh">
40
+ <span class="material-icons text-base align-middle">refresh</span>
41
+ </button>
42
+ </div>
43
+ <!-- Scrollable list area: only the entries list scrolls below
44
+ this point. The new-entry slot + filter bar above stay
45
+ pinned by virtue of NOT being inside this scroll container,
46
+ and the column-header row stays visible via `position:
47
+ sticky` on its <th>s. `min-h-0` is required for the flex-1
48
+ child to actually shrink below its content height in a
49
+ flex-col parent. -->
50
+ <div class="flex-1 min-h-0 overflow-auto">
51
+ <p v-if="loading" class="text-xs text-gray-400">{{ t("pluginAccounting.common.loading") }}</p>
52
+ <p v-else-if="error" class="text-xs text-red-500">{{ t("pluginAccounting.common.error", { error }) }}</p>
53
+ <p v-else-if="visibleEntries.length === 0" class="text-xs text-gray-400">{{ t("pluginAccounting.common.empty") }}</p>
54
+ <table v-else class="w-full text-sm" data-testid="accounting-journal-table">
55
+ <thead>
56
+ <tr class="text-xs text-gray-500 border-b border-gray-200">
57
+ <!-- Per-<th> sticky (rather than `<thead class="sticky">`)
58
+ for compatibility — `position: sticky` on the
59
+ table-header-group display is brittle in some
60
+ browsers, but on `<th>` it's universally supported.
61
+ `bg-white` is required so the scrolled rows beneath
62
+ don't bleed through. -->
63
+ <th class="sticky top-0 bg-white text-left py-1 px-2">{{ t("pluginAccounting.journalList.columns.date") }}</th>
64
+ <th class="sticky top-0 bg-white text-left py-1 px-2">{{ t("pluginAccounting.journalList.columns.kind") }}</th>
65
+ <th class="sticky top-0 bg-white text-left py-1 px-2">{{ t("pluginAccounting.journalList.columns.memo") }}</th>
66
+ <th class="sticky top-0 bg-white text-left py-1 px-2">{{ t("pluginAccounting.journalList.columns.lines") }}</th>
67
+ </tr>
68
+ </thead>
69
+ <tbody>
70
+ <template v-for="entry in visibleEntries" :key="entry.id">
71
+ <tr
72
+ :class="[
73
+ voidedEntryIds.has(entry.id) ? 'text-gray-400 line-through' : '',
74
+ expandedEntryId === entry.id ? 'row-selected' : '',
75
+ 'border-b border-gray-100 align-top cursor-pointer hover:bg-gray-50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-400',
76
+ ]"
77
+ :data-testid="voidedEntryIds.has(entry.id) ? `accounting-journal-row-voided-${entry.id}` : `accounting-journal-row-${entry.id}`"
78
+ tabindex="0"
79
+ role="button"
80
+ :aria-expanded="expandedEntryId === entry.id"
81
+ @click="toggleExpanded(entry.id)"
82
+ @keydown.enter.prevent.self="onKeyToggle($event, entry.id)"
83
+ @keydown.space.prevent.self="onKeyToggle($event, entry.id)"
84
+ >
85
+ <td class="py-1 px-2 whitespace-nowrap">{{ entry.date }}</td>
86
+ <td class="py-1 px-2 text-xs">{{ kindLabel(entry.kind) }}</td>
87
+ <td class="py-1 px-2">
88
+ <span v-if="entry.memo">{{ entry.memo }}</span>
89
+ </td>
90
+ <td class="py-1 px-2">
91
+ <template v-if="expandedEntryId !== entry.id">
92
+ <div v-for="(line, idx) in entry.lines" :key="idx" class="text-xs flex gap-2 items-baseline">
93
+ <span class="font-mono text-[10px] text-gray-400">{{ line.accountCode }}</span>
94
+ <span v-if="accountNameFor(line.accountCode)">{{ accountNameFor(line.accountCode) }}</span>
95
+ <span v-if="line.debit">{{ formatDebit(line.debit) }}</span>
96
+ <span v-if="line.credit">{{ formatCredit(line.credit) }}</span>
97
+ </div>
98
+ </template>
99
+ <div v-else class="flex items-center justify-between gap-2">
100
+ <span class="text-xs text-gray-400 font-mono">{{ formatCreatedAt(entry.createdAt) }}</span>
101
+ <button
102
+ type="button"
103
+ class="h-6 w-6 flex items-center justify-center rounded text-gray-500 hover:bg-gray-100"
104
+ :data-testid="`accounting-journal-detail-close-${entry.id}`"
105
+ :aria-label="t('common.close')"
106
+ @click.stop="onCloseDetail"
107
+ >
108
+ <span class="material-icons text-base">close</span>
109
+ </button>
110
+ </div>
111
+ </td>
112
+ </tr>
113
+ <tr v-if="expandedEntryId === entry.id" class="bg-gray-50 detail-selected" :data-testid="`accounting-journal-detail-${entry.id}`">
114
+ <td :colspan="4" class="px-6 py-2">
115
+ <!-- Edit-in-place: the JournalEntryForm replaces the
116
+ read-only detail content for this row when the
117
+ user clicks Edit. Submit / cancel collapses back
118
+ (submit also voids the original, so we clear the
119
+ selection); top-bar "+ New entry" stays a separate
120
+ path that opens the same form above the table. -->
121
+ <div v-if="entryBeingEdited?.id === entry.id" :data-testid="`accounting-journal-detail-edit-${entry.id}`">
122
+ <JournalEntryForm
123
+ :book-id="bookId"
124
+ :accounts="accounts"
125
+ :currency="currency"
126
+ :country="country"
127
+ :entry-to-edit="entryBeingEdited"
128
+ @submitted="onFormSubmitted"
129
+ @cancel="onFormCancel"
130
+ />
131
+ </div>
132
+ <template v-else>
133
+ <div class="flex items-center gap-3 mb-2">
134
+ <template v-if="entry.kind === 'normal' && !voidedEntryIds.has(entry.id)">
135
+ <button class="text-xs text-blue-600 hover:underline" :data-testid="`accounting-edit-${entry.id}`" @click="onEditEntry(entry)">
136
+ {{ t("pluginAccounting.journalList.edit") }}
137
+ </button>
138
+ <button class="text-xs text-red-500 hover:underline" :data-testid="`accounting-void-${entry.id}`" @click="onVoid(entry)">
139
+ {{ t("pluginAccounting.journalList.void") }}
140
+ </button>
141
+ </template>
142
+ <button
143
+ v-else-if="entry.kind === 'opening' && !voidedEntryIds.has(entry.id)"
144
+ class="text-xs text-blue-600 hover:underline"
145
+ :data-testid="`accounting-edit-opening-${entry.id}`"
146
+ @click="emit('editOpening')"
147
+ >
148
+ {{ t("pluginAccounting.journalList.edit") }}
149
+ </button>
150
+ </div>
151
+ <table class="w-full text-xs">
152
+ <thead>
153
+ <tr class="text-gray-500 border-b border-gray-200">
154
+ <th class="text-left py-1 px-2">{{ t("pluginAccounting.entryForm.accountLabel") }}</th>
155
+ <th class="text-right py-1 px-2">{{ t("pluginAccounting.entryForm.debitLabel") }}</th>
156
+ <th class="text-right py-1 px-2">{{ t("pluginAccounting.entryForm.creditLabel") }}</th>
157
+ <th class="text-left py-1 px-2">{{ t("pluginAccounting.entryForm.memoLabel") }}</th>
158
+ <th v-if="entryHasTaxIds(entry)" class="text-left py-1 px-2">{{ t("pluginAccounting.entryForm.taxRegistrationIdLabel") }}</th>
159
+ </tr>
160
+ </thead>
161
+ <tbody>
162
+ <tr v-for="(line, idx) in entry.lines" :key="idx" class="border-b border-gray-100 text-gray-700">
163
+ <td class="py-1 px-2">
164
+ <span class="font-mono text-[10px] text-gray-400 mr-2">{{ line.accountCode }}</span>
165
+ <span v-if="accountNameFor(line.accountCode)">{{ accountNameFor(line.accountCode) }}</span>
166
+ </td>
167
+ <td class="py-1 px-2 text-right font-mono">
168
+ <template v-if="line.debit">{{ formatAmount(line.debit, currency) }}</template>
169
+ </td>
170
+ <td class="py-1 px-2 text-right font-mono">
171
+ <template v-if="line.credit">{{ formatAmount(line.credit, currency) }}</template>
172
+ </td>
173
+ <td class="py-1 px-2">
174
+ <template v-if="line.memo">{{ line.memo }}</template>
175
+ </td>
176
+ <td v-if="entryHasTaxIds(entry)" class="py-1 px-2 font-mono text-[10px]">
177
+ <template v-if="line.taxRegistrationId">{{ line.taxRegistrationId }}</template>
178
+ </td>
179
+ </tr>
180
+ </tbody>
181
+ <tfoot>
182
+ <tr class="font-semibold border-t border-gray-300 text-gray-700">
183
+ <td class="py-1 px-2 text-gray-500">{{ t("pluginAccounting.balanceSheet.total") }}</td>
184
+ <td class="py-1 px-2 text-right font-mono">{{ formatAmount(entryDebitTotal(entry), currency) }}</td>
185
+ <td class="py-1 px-2 text-right font-mono">{{ formatAmount(entryCreditTotal(entry), currency) }}</td>
186
+ <td :colspan="entryHasTaxIds(entry) ? 2 : 1"></td>
187
+ </tr>
188
+ </tfoot>
189
+ </table>
190
+ </template>
191
+ </td>
192
+ </tr>
193
+ </template>
194
+ </tbody>
195
+ </table>
196
+ </div>
197
+ </div>
198
+ </template>
199
+
200
+ <script setup lang="ts">
201
+ import { computed, nextTick, ref, watch } from "vue";
202
+ import { useI18n } from "vue-i18n";
203
+ import { getJournalEntries, voidEntry, type Account, type JournalEntry, type JournalEntryKind, type JournalLine } from "../api";
204
+ import { formatAmount } from "../currencies";
205
+ import { currentFiscalYearRange, resolveFiscalYearEnd, type DateRange, type FiscalYearEnd } from "../fiscalYear";
206
+ import type { SupportedCountryCode } from "../countries";
207
+ import { useLatestRequest } from "./useLatestRequest";
208
+ import DateRangePicker from "./DateRangePicker.vue";
209
+ import JournalEntryForm from "./JournalEntryForm.vue";
210
+
211
+ const { t } = useI18n();
212
+
213
+ const props = defineProps<{
214
+ bookId: string;
215
+ accounts: Account[];
216
+ currency: string;
217
+ country?: SupportedCountryCode;
218
+ version: number;
219
+ fiscalYearEnd?: FiscalYearEnd;
220
+ /** Opening-balance date for the active book — drives the "Lifetime"
221
+ * shortcut in the date picker (from = openingDate, to = today).
222
+ * When absent, the picker hides Lifetime; "All" still works. */
223
+ openingDate?: string;
224
+ /** Entry id to auto-expand and scroll into view. Surfaced by the
225
+ * parent when an `addEntries` tool result lands so the user sees
226
+ * the freshly-posted row highlighted. Captured into
227
+ * `pendingPreselectId` and consumed once the entry actually
228
+ * appears in the fetched list — refetch can race the prop. */
229
+ preselectEntryId?: string;
230
+ }>();
231
+ const emit = defineEmits<{ editOpening: []; preselectConsumed: [] }>();
232
+
233
+ // Inline-form state. Two distinct surfaces, one component:
234
+ // • showNewForm = true → blank draft, rendered above the table
235
+ // where the "+ New entry" button used to be.
236
+ // • entryBeingEdited != null → edit mode, rendered IN-PLACE inside
237
+ // the matching row's expanded detail panel (replacing the read-
238
+ // only debit/credit table for that row).
239
+ // `<JournalEntryForm>` looks at `entryToEdit` to decide its title /
240
+ // submit label; the top-bar instance always passes null.
241
+ const showNewForm = ref(false);
242
+ const entryBeingEdited = ref<JournalEntry | null>(null);
243
+ // Single-selection detail expansion. Clicking a row swaps the
244
+ // selection (or collapses if it's already the selected row).
245
+ // Cleared on book switch via the closeForm watcher; entries deleted
246
+ // between fetches simply drop out of filteredEntries, so a stale id
247
+ // here just renders no detail row. Declared early so the
248
+ // onFormSubmitted / bookId-watcher callbacks below can reference it.
249
+ const expandedEntryId = ref<string | null>(null);
250
+
251
+ function onOpenNewEntry(): void {
252
+ entryBeingEdited.value = null;
253
+ showNewForm.value = true;
254
+ }
255
+
256
+ function onEditEntry(entry: JournalEntry): void {
257
+ showNewForm.value = false;
258
+ entryBeingEdited.value = entry;
259
+ }
260
+
261
+ function closeForm(): void {
262
+ showNewForm.value = false;
263
+ entryBeingEdited.value = null;
264
+ }
265
+
266
+ function onFormSubmitted(): void {
267
+ // Submit posts via the form. In production the server-side
268
+ // publishBookChange round-trips an SSE event that bumps
269
+ // `bookVersion` and re-runs `refresh` via the watcher below.
270
+ // We also kick a synchronous refetch here so the freshly-posted
271
+ // row shows up immediately — the SSE round-trip can race the
272
+ // tab repaint, and skipping it here also makes the e2e mock
273
+ // path (no pubsub replay) deterministic.
274
+ closeForm();
275
+ // After an in-place edit submit, the original entry is voided
276
+ // and replaced. Collapse the detail panel since it was pointing
277
+ // at an entry that's now superseded.
278
+ expandedEntryId.value = null;
279
+ void refresh();
280
+ }
281
+
282
+ function onFormCancel(): void {
283
+ closeForm();
284
+ }
285
+
286
+ // Switching books mid-edit would carry the prior book's draft into
287
+ // the new book. Force the panel closed so the next visit starts
288
+ // from a blank toolbar — the form's own bookId watcher would also
289
+ // reset its internal state, but we want the user back in the
290
+ // neutral "+ New entry" surface.
291
+ watch(
292
+ () => props.bookId,
293
+ () => {
294
+ closeForm();
295
+ expandedEntryId.value = null;
296
+ },
297
+ );
298
+
299
+ const resolvedFiscalYearEnd = computed<FiscalYearEnd>(() => resolveFiscalYearEnd(props.fiscalYearEnd));
300
+
301
+ // Default = current fiscal year. Reset by the bookId/fiscalYearEnd
302
+ // watcher so switching books or changing the FY-end in settings
303
+ // drops a stale custom range from the prior book.
304
+ const range = ref<DateRange>(currentFiscalYearRange(resolvedFiscalYearEnd.value));
305
+ const accountCode = ref("");
306
+ const entries = ref<JournalEntry[]>([]);
307
+ const serverVoidedIds = ref<string[]>([]);
308
+ const loading = ref(false);
309
+ const error = ref<string | null>(null);
310
+ const { begin: beginRequest, isCurrent } = useLatestRequest();
311
+
312
+ function kindLabel(kind: JournalEntryKind): string {
313
+ if (kind === "opening") return t("pluginAccounting.journalList.kind.opening");
314
+ if (kind === "void") return t("pluginAccounting.journalList.kind.void");
315
+ if (kind === "void-marker") return t("pluginAccounting.journalList.kind.voidMarker");
316
+ return t("pluginAccounting.journalList.kind.normal");
317
+ }
318
+
319
+ function formatDebit(value: number): string {
320
+ return `DR ${formatAmount(value, props.currency)}`;
321
+ }
322
+ function formatCredit(value: number): string {
323
+ return `CR ${formatAmount(value, props.currency)}`;
324
+ }
325
+ function formatAccountLabel(account: Account): string {
326
+ // Name first so type-to-search in the <select> matches the
327
+ // human-meaningful word; the code goes in trailing parens.
328
+ // Same convention used by JournalEntryForm and Ledger pickers.
329
+ return `${account.name} (${account.code})`;
330
+ }
331
+ // `entry.createdAt` is server-stamped ISO 8601. We render local
332
+ // date+time (no seconds, no timezone) in YYYY-MM-DD HH:MM form to
333
+ // match `entry.date`'s style and keep the line compact. Parens are
334
+ // baked in here so the template doesn't carry raw text (the
335
+ // vue-i18n/no-raw-text rule flags literal strings in mustache).
336
+ function formatCreatedAt(iso: string): string {
337
+ const date = new Date(iso);
338
+ if (Number.isNaN(date.getTime())) return `(${iso})`;
339
+ const pad = (num: number): string => String(num).padStart(2, "0");
340
+ return `(${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())})`;
341
+ }
342
+ const accountNameByCode = computed(() => {
343
+ const map = new Map<string, string>();
344
+ for (const account of props.accounts) map.set(account.code, account.name);
345
+ return map;
346
+ });
347
+ function accountNameFor(code: string): string | null {
348
+ return accountNameByCode.value.get(code) ?? null;
349
+ }
350
+
351
+ // Close button on the selected row's lines cell. Has to clear BOTH
352
+ // expandedEntryId AND entryBeingEdited — if the user clicks Edit
353
+ // (which sets entryBeingEdited) and then clicks Close, leaving
354
+ // entryBeingEdited stale would block reopening: toggleExpanded's
355
+ // edit-mode guard early-returns when entryBeingEdited.id matches the
356
+ // clicked row, so the user could never reopen that entry from the
357
+ // list. Issue surfaced by the CodeRabbit review on PR #1161.
358
+ function onCloseDetail(): void {
359
+ expandedEntryId.value = null;
360
+ entryBeingEdited.value = null;
361
+ }
362
+
363
+ function toggleExpanded(entryId: string): void {
364
+ // While the row is in edit mode for itself, ignore clicks on the
365
+ // row chrome (date / kind / memo / lines cells) — the user is
366
+ // actively typing into the form below and a stray cell click
367
+ // shouldn't collapse the panel. Cancel / Submit on the form, or
368
+ // clicking a different row, are the deliberate exits.
369
+ if (entryBeingEdited.value?.id === entryId) return;
370
+ expandedEntryId.value = expandedEntryId.value === entryId ? null : entryId;
371
+ // Switching to a different row (or collapsing) drops any
372
+ // in-progress edit on the prior row.
373
+ entryBeingEdited.value = null;
374
+ }
375
+
376
+ function onKeyToggle(event: KeyboardEvent, entryId: string): void {
377
+ if (event.repeat) return;
378
+ toggleExpanded(entryId);
379
+ }
380
+
381
+ function entryHasTaxIds(entry: JournalEntry): boolean {
382
+ return entry.lines.some((line) => Boolean(line.taxRegistrationId));
383
+ }
384
+
385
+ function sumLines(lines: JournalLine[], pick: (line: JournalLine) => number | undefined): number {
386
+ return lines.reduce((acc, line) => acc + (pick(line) ?? 0), 0);
387
+ }
388
+
389
+ function entryDebitTotal(entry: JournalEntry): number {
390
+ return sumLines(entry.lines, (line) => line.debit);
391
+ }
392
+
393
+ function entryCreditTotal(entry: JournalEntry): number {
394
+ return sumLines(entry.lines, (line) => line.credit);
395
+ }
396
+
397
+ async function refresh(): Promise<void> {
398
+ const token = beginRequest();
399
+ loading.value = true;
400
+ error.value = null;
401
+ try {
402
+ const result = await getJournalEntries({
403
+ bookId: props.bookId,
404
+ from: range.value.from || undefined,
405
+ to: range.value.to || undefined,
406
+ accountCode: accountCode.value || undefined,
407
+ });
408
+ if (!isCurrent(token)) return;
409
+ if (!result.ok) {
410
+ error.value = result.error;
411
+ entries.value = [];
412
+ serverVoidedIds.value = [];
413
+ return;
414
+ }
415
+ entries.value = result.data.entries;
416
+ serverVoidedIds.value = result.data.voidedEntryIds;
417
+ } finally {
418
+ if (isCurrent(token)) loading.value = false;
419
+ }
420
+ }
421
+
422
+ const filteredEntries = computed(() => entries.value);
423
+
424
+ // Visible-list view that pins the entry currently being edited at
425
+ // the top when a filter change or pubsub-driven refetch would
426
+ // otherwise drop it from `filteredEntries`. Without this, the
427
+ // in-place edit form (which is nested under the row's v-if /
428
+ // v-for) would unmount and silently discard the user's draft when:
429
+ // • the user adjusts the date range or account filter,
430
+ // • a sibling tab / LLM tool voids the entry out-of-band and the
431
+ // SSE pubsub bumps `bookVersion`, refetching this list,
432
+ // • or a sibling tab / LLM tool deletes the underlying book.
433
+ // Pinning the editing entry from the local snapshot (entryBeingEdited)
434
+ // keeps the form mounted across all three. The pinned row sits at
435
+ // the top of the table while editing; on submit / cancel the
436
+ // snapshot clears and the list reverts to filteredEntries.
437
+ const visibleEntries = computed<JournalEntry[]>(() => {
438
+ const list = filteredEntries.value;
439
+ const editing = entryBeingEdited.value;
440
+ if (editing && !list.some((entry) => entry.id === editing.id)) {
441
+ return [editing, ...list];
442
+ }
443
+ return list;
444
+ });
445
+
446
+ // Set of original entry ids that have been voided. The server
447
+ // computes this from the *unfiltered* journal (so an account-filtered
448
+ // query — which drops void-marker rows because they have no lines —
449
+ // still strikes out the cancelled original). Source of truth on the
450
+ // server is `voidedIdSet()` in journal.ts.
451
+ const voidedEntryIds = computed(() => new Set(serverVoidedIds.value));
452
+
453
+ async function onVoid(entry: JournalEntry): Promise<void> {
454
+ // Single dialog: the prompt is the confirmation. Cancelling
455
+ // (returning null) cancels the void; entering empty text or a
456
+ // reason proceeds.
457
+ const reason = window.prompt(t("pluginAccounting.journalList.voidReason"));
458
+ if (reason === null) return;
459
+ try {
460
+ const result = await voidEntry({ entryId: entry.id, reason: reason || undefined, bookId: props.bookId });
461
+ if (!result.ok) error.value = result.error;
462
+ } catch (err) {
463
+ error.value = err instanceof Error ? err.message : String(err);
464
+ }
465
+ }
466
+
467
+ // Reset to current-year window whenever the active book or its
468
+ // fiscal-year end changes. Keeps a custom range from leaking across
469
+ // books and follows a settings-driven shift in fiscalYearEnd.
470
+ watch(
471
+ () => [props.bookId, resolvedFiscalYearEnd.value],
472
+ () => {
473
+ range.value = currentFiscalYearRange(resolvedFiscalYearEnd.value);
474
+ },
475
+ );
476
+
477
+ watch(() => [props.bookId, props.version, range.value.from, range.value.to, accountCode.value], refresh, { immediate: true });
478
+
479
+ // Pending preselect: the parent hands us an id via `preselectEntryId`,
480
+ // but the matching entry may not be in `entries` yet (the SSE-driven
481
+ // refetch lands on its own clock). Stash it here, then the
482
+ // [pendingPreselectId, entries] watcher below consumes it once the
483
+ // row actually exists in the list — and clears it so subsequent
484
+ // unrelated refetches (void events, sibling-tab edits) don't
485
+ // re-expand a stale target.
486
+ const pendingPreselectId = ref<string | null>(null);
487
+
488
+ watch(
489
+ () => props.preselectEntryId,
490
+ (incoming) => {
491
+ if (incoming) pendingPreselectId.value = incoming;
492
+ },
493
+ // immediate: true so a late JournalList mount (the View defers our
494
+ // mount until refetchBooks resolves activeBookId) still captures
495
+ // a preselect the parent had already set — without this, a normal
496
+ // watcher misses the "initial value is the target value" case.
497
+ { immediate: true },
498
+ );
499
+
500
+ watch([pendingPreselectId, entries], async ([targetId, list]) => {
501
+ if (!targetId) return;
502
+ if (!list.some((entry) => entry.id === targetId)) return;
503
+ // Always emit `preselectConsumed` (whether we expand or bail) so
504
+ // the parent can drop its `journalPreselectEntryId` ref. Without
505
+ // this one-shot signal, leaving and returning to the journal tab
506
+ // (v-if remount) replays the immediate prop watcher against the
507
+ // stale value, re-expanding an old row the user has already moved
508
+ // past. Issue raised by the Codex automated review on PR #1158.
509
+ if (entryBeingEdited.value) {
510
+ // Don't overwrite an in-progress edit on another row — the
511
+ // user's draft matters more than the highlight. Drop pending so
512
+ // we don't keep retrying every refetch, and signal consumed so
513
+ // the parent doesn't keep re-handing us the same id.
514
+ pendingPreselectId.value = null;
515
+ emit("preselectConsumed");
516
+ return;
517
+ }
518
+ expandedEntryId.value = targetId;
519
+ await nextTick();
520
+ const row =
521
+ document.querySelector(`[data-testid="accounting-journal-row-${targetId}"]`) ??
522
+ document.querySelector(`[data-testid="accounting-journal-row-voided-${targetId}"]`);
523
+ row?.scrollIntoView({ behavior: "smooth", block: "center" });
524
+ pendingPreselectId.value = null;
525
+ emit("preselectConsumed");
526
+ });
527
+ </script>
528
+
529
+ <style scoped>
530
+ /* Selection frame for the expanded entry. Borders go on the cells
531
+ (not the <tr>) because border-collapse: collapse — Tailwind's
532
+ default — eats <tr>-level borders/box-shadows. The entry row owns
533
+ top/left/right; the detail-panel row directly below owns
534
+ left/right/bottom, so together they read as one rectangle around
535
+ the selection. Color matches the focus-ring blue used elsewhere
536
+ in this list. */
537
+ .row-selected > td {
538
+ background-color: rgb(239 246 255); /* tailwind blue-50 */
539
+ border-top: 2px solid rgb(59 130 246); /* tailwind blue-500 */
540
+ }
541
+ .row-selected > td:first-child {
542
+ border-left: 2px solid rgb(59 130 246);
543
+ }
544
+ .row-selected > td:last-child {
545
+ border-right: 2px solid rgb(59 130 246);
546
+ }
547
+ .detail-selected > td {
548
+ background-color: rgb(239 246 255);
549
+ border-left: 2px solid rgb(59 130 246);
550
+ border-right: 2px solid rgb(59 130 246);
551
+ border-bottom: 2px solid rgb(59 130 246);
552
+ }
553
+ </style>