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,284 @@
1
+ <template>
2
+ <div class="flex flex-col gap-4" data-testid="accounting-settings">
3
+ <section class="border border-gray-200 rounded p-3 flex flex-col gap-2">
4
+ <h4 class="text-sm font-semibold">{{ t("pluginAccounting.settings.bookInfo") }}</h4>
5
+ <p class="text-xs text-gray-500">{{ t("pluginAccounting.settings.bookInfoExplain") }}</p>
6
+ <label class="text-sm flex flex-col gap-1">
7
+ {{ t("pluginAccounting.bookSwitcher.nameLabel") }}
8
+ <input
9
+ v-model="selectedName"
10
+ type="text"
11
+ class="h-8 px-2 rounded border border-gray-300 text-sm bg-white"
12
+ data-testid="accounting-settings-name"
13
+ :disabled="updating"
14
+ maxlength="200"
15
+ />
16
+ </label>
17
+ <dl class="text-xs text-gray-700 grid grid-cols-[max-content_1fr] gap-x-3 gap-y-1">
18
+ <dt class="text-gray-500">{{ t("pluginAccounting.bookSwitcher.currencyLabel") }}</dt>
19
+ <dd>{{ currency }}</dd>
20
+ </dl>
21
+ <label class="text-sm flex flex-col gap-1 mt-1">
22
+ {{ t("pluginAccounting.bookSwitcher.countryLabel") }}
23
+ <select
24
+ v-model="selectedCountry"
25
+ class="h-8 px-2 rounded border border-gray-300 text-sm bg-white"
26
+ data-testid="accounting-settings-country"
27
+ :disabled="updating"
28
+ >
29
+ <option value="">{{ t("pluginAccounting.settings.countryUnset") }}</option>
30
+ <option v-for="opt in countryOptions" :key="opt.code" :value="opt.code">{{ opt.label }}</option>
31
+ </select>
32
+ </label>
33
+ <label class="text-sm flex flex-col gap-1 mt-1">
34
+ {{ t("pluginAccounting.bookSwitcher.fiscalYearEndLabel") }}
35
+ <select
36
+ v-model="selectedFiscalYearEnd"
37
+ class="h-8 px-2 rounded border border-gray-300 text-sm bg-white"
38
+ data-testid="accounting-settings-fiscal-year-end"
39
+ :disabled="updating"
40
+ >
41
+ <option v-for="opt in fiscalYearEndOptions" :key="opt.value" :value="opt.value">{{ opt.label }}</option>
42
+ </select>
43
+ </label>
44
+ <p class="text-xs text-gray-500">{{ t("pluginAccounting.settings.fiscalYearEndExplain") }}</p>
45
+ <p v-if="updateOk" class="text-xs text-green-600" data-testid="accounting-settings-update-ok">{{ updateOk }}</p>
46
+ <p v-if="updateError" class="text-xs text-red-500" data-testid="accounting-settings-update-error">{{ updateError }}</p>
47
+ <div>
48
+ <button
49
+ class="h-8 px-3 rounded bg-blue-600 hover:bg-blue-700 text-white text-sm disabled:opacity-50"
50
+ :disabled="updating || !hasPendingChanges"
51
+ data-testid="accounting-settings-save"
52
+ @click="onSaveBookInfo"
53
+ >
54
+ {{ updating ? t("pluginAccounting.common.loading") : t("pluginAccounting.settings.saveChanges") }}
55
+ </button>
56
+ </div>
57
+ </section>
58
+ <section class="border border-gray-200 rounded p-3 flex flex-col gap-2">
59
+ <h4 class="text-sm font-semibold">{{ t("pluginAccounting.settings.rebuild") }}</h4>
60
+ <p class="text-xs text-gray-500">{{ t("pluginAccounting.settings.rebuildExplain") }}</p>
61
+ <p v-if="rebuildOk" class="text-xs text-green-600" data-testid="accounting-settings-rebuild-ok">{{ rebuildOk }}</p>
62
+ <p v-if="rebuildError" class="text-xs text-red-500" data-testid="accounting-settings-rebuild-error">{{ rebuildError }}</p>
63
+ <div>
64
+ <button
65
+ class="h-8 px-3 rounded bg-blue-600 hover:bg-blue-700 text-white text-sm disabled:opacity-50"
66
+ :disabled="rebuilding"
67
+ data-testid="accounting-settings-rebuild"
68
+ @click="onRebuild"
69
+ >
70
+ {{ rebuilding ? t("pluginAccounting.common.loading") : t("pluginAccounting.settings.rebuild") }}
71
+ </button>
72
+ </div>
73
+ </section>
74
+ <div v-if="!showAdvanced">
75
+ <button
76
+ type="button"
77
+ 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"
78
+ data-testid="accounting-settings-advanced"
79
+ @click="showAdvanced = true"
80
+ >
81
+ <span class="material-icons text-base">expand_more</span>
82
+ <span>{{ t("pluginAccounting.settings.advanced") }}</span>
83
+ </button>
84
+ </div>
85
+ <section v-if="showAdvanced" class="border border-red-300 rounded p-3 flex flex-col gap-2">
86
+ <h4 class="text-sm font-semibold text-red-700">{{ t("pluginAccounting.settings.deleteBook") }}</h4>
87
+ <p class="text-xs text-gray-500">{{ t("pluginAccounting.settings.deleteBookExplain") }}</p>
88
+ <p v-if="deleteError" class="text-xs text-red-500" data-testid="accounting-settings-delete-error">{{ deleteError }}</p>
89
+ <label class="text-xs text-gray-500 flex flex-col gap-1">
90
+ {{ t("pluginAccounting.settings.deleteBookConfirm", { bookName: bookName }) }}
91
+ <input v-model="confirmName" class="h-8 px-2 rounded border border-gray-300 text-sm" data-testid="accounting-settings-delete-confirm" />
92
+ </label>
93
+ <div>
94
+ <button
95
+ class="h-8 px-3 rounded bg-red-600 hover:bg-red-700 text-white text-sm disabled:opacity-50"
96
+ :disabled="confirmName !== bookName || deleting"
97
+ data-testid="accounting-settings-delete"
98
+ @click="onDelete"
99
+ >
100
+ {{ deleting ? t("pluginAccounting.common.loading") : t("pluginAccounting.settings.deleteBookButton") }}
101
+ </button>
102
+ </div>
103
+ </section>
104
+ </div>
105
+ </template>
106
+
107
+ <script setup lang="ts">
108
+ import { computed, ref, watch } from "vue";
109
+ import { useI18n } from "vue-i18n";
110
+ import { deleteBook, rebuildSnapshots, updateBook } from "../api";
111
+ import { SUPPORTED_COUNTRY_CODES, isSupportedCountryCode, localizedCountryName, type SupportedCountryCode } from "../countries";
112
+ import { DEFAULT_FISCAL_YEAR_END, FISCAL_YEAR_ENDS, resolveFiscalYearEnd, type FiscalYearEnd } from "../fiscalYear";
113
+
114
+ const { t, locale } = useI18n();
115
+
116
+ const props = defineProps<{
117
+ bookId: string;
118
+ bookName: string;
119
+ currency: string;
120
+ country?: SupportedCountryCode;
121
+ fiscalYearEnd?: FiscalYearEnd;
122
+ }>();
123
+ const emit = defineEmits<{ deleted: [bookName: string]; "books-changed": [] }>();
124
+
125
+ const rebuilding = ref(false);
126
+ const rebuildOk = ref<string | null>(null);
127
+ const rebuildError = ref<string | null>(null);
128
+ const deleting = ref(false);
129
+ const deleteError = ref<string | null>(null);
130
+ const confirmName = ref("");
131
+ const updating = ref(false);
132
+ const updateOk = ref<string | null>(null);
133
+ const updateError = ref<string | null>(null);
134
+ const showAdvanced = ref(false);
135
+ const selectedName = ref<string>(props.bookName);
136
+ const selectedCountry = ref<string>(props.country ?? "");
137
+ // Resolved at the boundary so the dropdown always shows a concrete
138
+ // value — books without a `fiscalYearEnd` field on disk land here as
139
+ // the default Q4 (matches the back-compat read policy).
140
+ const selectedFiscalYearEnd = ref<FiscalYearEnd>(props.fiscalYearEnd ?? DEFAULT_FISCAL_YEAR_END);
141
+
142
+ interface CountryOption {
143
+ code: string;
144
+ label: string;
145
+ }
146
+
147
+ const countryOptions = computed<CountryOption[]>(() =>
148
+ SUPPORTED_COUNTRY_CODES.map((code) => ({
149
+ code,
150
+ label: `${code} — ${localizedCountryName(code, locale.value)}`,
151
+ })),
152
+ );
153
+
154
+ interface FiscalYearEndOption {
155
+ value: FiscalYearEnd;
156
+ label: string;
157
+ }
158
+
159
+ const fiscalYearEndOptions = computed<FiscalYearEndOption[]>(() =>
160
+ FISCAL_YEAR_ENDS.map((value) => ({
161
+ value,
162
+ label: t(`pluginAccounting.bookSwitcher.fiscalYearEnd${value}`),
163
+ })),
164
+ );
165
+
166
+ const hasPendingChanges = computed<boolean>(() => {
167
+ // Compare against the trimmed value so a no-op edit (typing then
168
+ // backspacing back to the original) doesn't keep the Save button
169
+ // hot. Server-side validateUpdateBookInput rejects empty / whitespace
170
+ // names with a 400 — the disabled binding below mirrors that contract
171
+ // so the button can't fire a doomed request.
172
+ const nameChanged = selectedName.value.trim() !== props.bookName;
173
+ const nameValid = selectedName.value.trim().length > 0;
174
+ const countryChanged = selectedCountry.value !== (props.country ?? "");
175
+ const fiscalChanged = selectedFiscalYearEnd.value !== resolveFiscalYearEnd(props.fiscalYearEnd);
176
+ return nameValid && (nameChanged || countryChanged || fiscalChanged);
177
+ });
178
+
179
+ async function onRebuild(): Promise<void> {
180
+ rebuilding.value = true;
181
+ rebuildOk.value = null;
182
+ rebuildError.value = null;
183
+ try {
184
+ const result = await rebuildSnapshots(props.bookId);
185
+ if (!result.ok) {
186
+ rebuildError.value = result.error;
187
+ return;
188
+ }
189
+ rebuildOk.value = t("pluginAccounting.settings.rebuildOk", { count: result.data.rebuilt.length });
190
+ } finally {
191
+ rebuilding.value = false;
192
+ }
193
+ }
194
+
195
+ async function onSaveBookInfo(): Promise<void> {
196
+ if (updating.value) return;
197
+ updating.value = true;
198
+ updateOk.value = null;
199
+ updateError.value = null;
200
+ try {
201
+ // The select v-model is a plain `string` (HTML form value); narrow
202
+ // it back to the union before handing it to the API helper. The
203
+ // empty string is the sentinel that clears the country server-side.
204
+ const rawCountry = selectedCountry.value;
205
+ const country: SupportedCountryCode | "" = rawCountry === "" || isSupportedCountryCode(rawCountry) ? rawCountry : "";
206
+ const result = await updateBook({
207
+ bookId: props.bookId,
208
+ name: selectedName.value.trim(),
209
+ country,
210
+ fiscalYearEnd: selectedFiscalYearEnd.value,
211
+ });
212
+ if (!result.ok) {
213
+ updateError.value = result.error;
214
+ return;
215
+ }
216
+ updateOk.value = t("pluginAccounting.settings.updateOk");
217
+ emit("books-changed");
218
+ } finally {
219
+ updating.value = false;
220
+ }
221
+ }
222
+
223
+ async function onDelete(): Promise<void> {
224
+ if (deleting.value) return;
225
+ deleting.value = true;
226
+ deleteError.value = null;
227
+ try {
228
+ const result = await deleteBook(props.bookId);
229
+ if (!result.ok) {
230
+ deleteError.value = result.error;
231
+ return;
232
+ }
233
+ emit("deleted", props.bookName);
234
+ emit("books-changed");
235
+ } finally {
236
+ deleting.value = false;
237
+ }
238
+ }
239
+
240
+ // Reset feedback / confirmation AND the dropdown selection when the
241
+ // user navigates between books while this tab is open. Without the
242
+ // `selectedCountry` reset, switching from book A (country=JP) to
243
+ // book B (also country=JP) leaves a previously-typed unsaved value
244
+ // staged on B — a save would then misattribute the edit.
245
+ watch(
246
+ () => props.bookId,
247
+ () => {
248
+ rebuildOk.value = null;
249
+ rebuildError.value = null;
250
+ deleteError.value = null;
251
+ confirmName.value = "";
252
+ updateOk.value = null;
253
+ updateError.value = null;
254
+ selectedName.value = props.bookName;
255
+ selectedCountry.value = props.country ?? "";
256
+ selectedFiscalYearEnd.value = props.fiscalYearEnd ?? DEFAULT_FISCAL_YEAR_END;
257
+ showAdvanced.value = false;
258
+ },
259
+ );
260
+
261
+ // Follow external bookName updates — e.g. an LLM-driven updateBook in
262
+ // another tab, or pubsub-driven refetch. Without this, an out-of-band
263
+ // rename leaves a stale draft staged in the input.
264
+ watch(
265
+ () => props.bookName,
266
+ (next) => {
267
+ selectedName.value = next;
268
+ },
269
+ );
270
+
271
+ watch(
272
+ () => props.country,
273
+ (next) => {
274
+ selectedCountry.value = next ?? "";
275
+ },
276
+ );
277
+
278
+ watch(
279
+ () => props.fiscalYearEnd,
280
+ (next) => {
281
+ selectedFiscalYearEnd.value = next ?? DEFAULT_FISCAL_YEAR_END;
282
+ },
283
+ );
284
+ </script>
@@ -0,0 +1,78 @@
1
+ <template>
2
+ <div class="flex items-center gap-2">
3
+ <label class="text-xs text-gray-500" for="accounting-book-select">{{ t("pluginAccounting.bookSwitcher.label") }}</label>
4
+ <select
5
+ id="accounting-book-select"
6
+ :value="modelValue"
7
+ class="h-8 px-2 rounded border border-gray-300 text-sm bg-white"
8
+ data-testid="accounting-book-select"
9
+ @change="onSelect"
10
+ >
11
+ <option v-if="modelValue === ''" value="" disabled>{{ t("pluginAccounting.bookSwitcher.placeholder") }}</option>
12
+ <option v-for="book in books" :key="book.id" :value="book.id">{{ formatBookOption(book) }}</option>
13
+ <!-- eslint-disable-next-line @intlify/vue-i18n/no-raw-text -- decorative separator inside the books <select>, not user copy -->
14
+ <option disabled>──────────</option>
15
+ <option :value="NEW_BOOK_SENTINEL" data-testid="accounting-new-book-option">+ {{ t("pluginAccounting.bookSwitcher.newBook") }}</option>
16
+ </select>
17
+ <NewBookForm v-if="showNewBook" @cancel="showNewBook = false" @created="onCreated" />
18
+ </div>
19
+ </template>
20
+
21
+ <script setup lang="ts">
22
+ import { ref } from "vue";
23
+ import { useI18n } from "vue-i18n";
24
+ import NewBookForm from "./NewBookForm.vue";
25
+ import type { BookSummary } from "../api";
26
+
27
+ const { t } = useI18n();
28
+
29
+ const props = defineProps<{ modelValue: string; books: BookSummary[] }>();
30
+ const emit = defineEmits<{
31
+ "update:modelValue": [bookId: string];
32
+ "books-changed": [];
33
+ "book-created": [book: BookSummary];
34
+ }>();
35
+
36
+ // Sentinel value for the "+ New book" option living inside the
37
+ // books <select>. Picking it opens the modal and reverts the
38
+ // select's displayed value to the current selection — the option
39
+ // must not collide with any real book id, which are nanoid-shaped.
40
+ const NEW_BOOK_SENTINEL = "__new__";
41
+
42
+ const showNewBook = ref(false);
43
+
44
+ function formatBookOption(book: BookSummary): string {
45
+ // `Name (CCY · Country)` when a country is set; otherwise fall back
46
+ // to `Name (CCY)`. Keeps the option label compact while surfacing
47
+ // the country so a multi-jurisdiction user can pick the right book
48
+ // by tax regime, not just currency.
49
+ const suffix = book.country ? `${book.currency} · ${book.country}` : book.currency;
50
+ return `${book.name} (${suffix})`;
51
+ }
52
+
53
+ function onSelect(event: Event): void {
54
+ const target = event.target as HTMLSelectElement;
55
+ const bookId = target.value;
56
+ if (bookId === NEW_BOOK_SENTINEL) {
57
+ target.value = props.modelValue;
58
+ showNewBook.value = true;
59
+ return;
60
+ }
61
+ if (bookId === props.modelValue) return;
62
+ // The View persists the new selection to localStorage; no server
63
+ // round-trip needed since there's no shared "active book" state.
64
+ emit("update:modelValue", bookId);
65
+ }
66
+
67
+ function onCreated(book: BookSummary): void {
68
+ // Hand the new book to the parent in one event so it can await
69
+ // its own refetch before setting the active selection. Splitting
70
+ // this into separate `books-changed` + `update:modelValue` emits
71
+ // races: the parent's async refetch runs concurrently with the
72
+ // selection update, and the stillExists guard inside refetch can
73
+ // snap the selection back to books[0] if the fetch happens to
74
+ // resolve before the new book is in the list.
75
+ showNewBook.value = false;
76
+ emit("book-created", book);
77
+ }
78
+ </script>
@@ -0,0 +1,140 @@
1
+ <template>
2
+ <!-- Reusable from/to + shortcut date range picker. Owns no state
3
+ beyond the v-model; the parent supplies an initial range and
4
+ the active book's fiscalYearEnd so quarter/year shortcuts
5
+ resolve under the right calendar. -->
6
+ <div class="flex flex-wrap items-end gap-2" data-testid="accounting-daterange">
7
+ <label class="text-xs text-gray-500 flex flex-col gap-1">
8
+ {{ t("pluginAccounting.dateRange.shortcutLabel") }}
9
+ <select
10
+ :value="selectedShortcut"
11
+ class="h-8 px-2 rounded border border-gray-300 text-sm bg-white"
12
+ data-testid="accounting-daterange-shortcut"
13
+ @change="onShortcutChange(($event.target as HTMLSelectElement).value)"
14
+ >
15
+ <!-- Sentinel for the "custom" state. Hidden from the menu
16
+ but bound when the current range doesn't match any
17
+ preset, which leaves the trigger displaying blank
18
+ instead of forcing a wrong-looking match. -->
19
+ <option value="" hidden></option>
20
+ <option value="currentQuarter">{{ t("pluginAccounting.dateRange.currentQuarter") }}</option>
21
+ <option value="previousQuarter">{{ t("pluginAccounting.dateRange.previousQuarter") }}</option>
22
+ <option value="currentYear">{{ t("pluginAccounting.dateRange.currentYear") }}</option>
23
+ <option value="previousYear">{{ t("pluginAccounting.dateRange.previousYear") }}</option>
24
+ <option v-if="hasOpeningDate" value="lifetime">{{ t("pluginAccounting.dateRange.lifetime") }}</option>
25
+ <option value="all">{{ t("pluginAccounting.dateRange.all") }}</option>
26
+ </select>
27
+ </label>
28
+ <label class="text-xs text-gray-500 flex flex-col gap-1">
29
+ {{ t("pluginAccounting.dateRange.fromLabel") }}
30
+ <input
31
+ :value="modelValue.from"
32
+ type="date"
33
+ class="h-8 px-2 rounded border border-gray-300 text-sm"
34
+ data-testid="accounting-daterange-from"
35
+ @input="onFromChange(($event.target as HTMLInputElement).value)"
36
+ />
37
+ </label>
38
+ <label class="text-xs text-gray-500 flex flex-col gap-1">
39
+ {{ t("pluginAccounting.dateRange.toLabel") }}
40
+ <input
41
+ :value="modelValue.to"
42
+ type="date"
43
+ class="h-8 px-2 rounded border border-gray-300 text-sm"
44
+ data-testid="accounting-daterange-to"
45
+ @input="onToChange(($event.target as HTMLInputElement).value)"
46
+ />
47
+ </label>
48
+ </div>
49
+ </template>
50
+
51
+ <script setup lang="ts">
52
+ import { computed } from "vue";
53
+ import { useI18n } from "vue-i18n";
54
+ import { currentFiscalYearRange, currentQuarterRange, previousFiscalYearRange, previousQuarterRange, type DateRange, type FiscalYearEnd } from "../fiscalYear";
55
+ import { localDateString } from "../dates";
56
+
57
+ const { t } = useI18n();
58
+
59
+ const props = defineProps<{
60
+ modelValue: DateRange;
61
+ fiscalYearEnd: FiscalYearEnd;
62
+ /** The active book's opening-balance date. Drives the "Lifetime"
63
+ * shortcut (from = openingDate, to = today). Optional — when
64
+ * absent the Lifetime option is hidden from the menu. The opening
65
+ * gate prevents the tabs that mount this picker from rendering
66
+ * before an opening exists, so in normal use this stays defined. */
67
+ openingDate?: string;
68
+ }>();
69
+
70
+ const emit = defineEmits<{
71
+ "update:modelValue": [DateRange];
72
+ }>();
73
+
74
+ const hasOpeningDate = computed<boolean>(() => Boolean(props.openingDate));
75
+
76
+ const UNBOUNDED_RANGE: DateRange = { from: "", to: "" };
77
+
78
+ /** From the book's opening date through today. Hidden from the menu
79
+ * when the parent hasn't supplied an opening. */
80
+ function lifetimeRange(): DateRange | null {
81
+ if (!props.openingDate) return null;
82
+ return { from: props.openingDate, to: localDateString() };
83
+ }
84
+
85
+ type Shortcut = "currentQuarter" | "previousQuarter" | "currentYear" | "previousYear" | "lifetime" | "all";
86
+ /** Empty string is the sentinel "no preset matches" value bound to
87
+ * the hidden option in the template — the trigger shows blank. */
88
+ type SelectedShortcut = Shortcut | "";
89
+
90
+ function rangesEqual(left: DateRange, right: DateRange): boolean {
91
+ return left.from === right.from && left.to === right.to;
92
+ }
93
+
94
+ // Resolve the dropdown's displayed value from the current
95
+ // modelValue. Re-evaluates today on every read — the picker is a
96
+ // short-lived UI surface so cache invalidation isn't a concern, and
97
+ // the user has no expectation that "current quarter" picked in the
98
+ // morning still labels correctly at midnight. Returns "" when no
99
+ // preset matches (custom range from manual from/to edits).
100
+ //
101
+ // Order matters when ranges collide: when no opening is on file the
102
+ // Lifetime option is hidden from the menu, but if it ever produced
103
+ // the same span as another preset (it can't — Lifetime spans years,
104
+ // presets span quarter/year), the earlier branch would win. We
105
+ // check the explicit ranges first and fall through to the unbounded
106
+ // "all" last so a manually-cleared input lands on "all" rather than
107
+ // blank when both sides happen to be empty.
108
+ const selectedShortcut = computed<SelectedShortcut>(() => {
109
+ const value = props.modelValue;
110
+ const today = new Date();
111
+ if (rangesEqual(value, currentQuarterRange(props.fiscalYearEnd, today))) return "currentQuarter";
112
+ if (rangesEqual(value, previousQuarterRange(props.fiscalYearEnd, today))) return "previousQuarter";
113
+ if (rangesEqual(value, currentFiscalYearRange(props.fiscalYearEnd, today))) return "currentYear";
114
+ if (rangesEqual(value, previousFiscalYearRange(props.fiscalYearEnd, today))) return "previousYear";
115
+ const lifetime = lifetimeRange();
116
+ if (lifetime && rangesEqual(value, lifetime)) return "lifetime";
117
+ if (rangesEqual(value, UNBOUNDED_RANGE)) return "all";
118
+ return "";
119
+ });
120
+
121
+ function onShortcutChange(raw: string): void {
122
+ const today = new Date();
123
+ if (raw === "currentQuarter") emit("update:modelValue", currentQuarterRange(props.fiscalYearEnd, today));
124
+ else if (raw === "previousQuarter") emit("update:modelValue", previousQuarterRange(props.fiscalYearEnd, today));
125
+ else if (raw === "currentYear") emit("update:modelValue", currentFiscalYearRange(props.fiscalYearEnd, today));
126
+ else if (raw === "previousYear") emit("update:modelValue", previousFiscalYearRange(props.fiscalYearEnd, today));
127
+ else if (raw === "lifetime") {
128
+ const lifetime = lifetimeRange();
129
+ if (lifetime) emit("update:modelValue", lifetime);
130
+ } else if (raw === "all") emit("update:modelValue", UNBOUNDED_RANGE);
131
+ }
132
+
133
+ function onFromChange(value: string): void {
134
+ emit("update:modelValue", { from: value, to: props.modelValue.to });
135
+ }
136
+
137
+ function onToChange(value: string): void {
138
+ emit("update:modelValue", { from: props.modelValue.from, to: value });
139
+ }
140
+ </script>