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.
- package/Dockerfile.sandbox +100 -0
- package/README.md +17 -4
- package/bin/mulmoclaude.js +46 -15
- package/bin/prepare-dist.js +18 -2
- package/client/assets/chunk-CernVdwh.js +1 -0
- package/client/assets/chunk-D8eiyYIV-C1eAZMzz.js +1 -0
- package/client/assets/html2canvas-CDGcmOD3-BbPeutDg.js +5 -0
- package/client/assets/index-BbgSjFQ8.js +4968 -0
- package/client/assets/index-ECD0lgIv.css +2 -0
- package/client/assets/{index.es-D4YyL_Dg-BgT6a3Nd.js → index.es-DqtpmBm8-DJdTPdnc.js} +5 -5
- package/client/assets/material-symbols-outlined-BLDfUw-_.woff2 +0 -0
- package/client/assets/runtime-protocol-vue-6WYa8hAs.js +1 -0
- package/client/assets/runtime-vue-BVUzgYGA.js +1 -0
- package/client/assets/typeof-DBp4T-Ny-C2xoZtcz.js +1 -0
- package/client/assets/vue-1e_vz2LW.js +1 -0
- package/client/assets/vue.runtime.esm-bundler-DQ8Kjjui.js +4 -0
- package/client/index.html +33 -2
- package/package.json +20 -18
- package/sandbox-entrypoint.sh +106 -0
- package/server/accounting/accountNormalize.ts +32 -0
- package/server/accounting/defaultAccounts.ts +87 -0
- package/server/accounting/eventPublisher.ts +51 -0
- package/server/accounting/journal.ts +252 -0
- package/server/accounting/openingBalances.ts +114 -0
- package/server/accounting/report.ts +237 -0
- package/server/accounting/service.ts +718 -0
- package/server/accounting/snapshotCache.ts +333 -0
- package/server/accounting/timeSeries.ts +265 -0
- package/server/accounting/types.ts +148 -0
- package/server/agent/activeTools.ts +128 -0
- package/server/agent/attachmentConverter.ts +10 -5
- package/server/agent/backend/claude-code.ts +8 -2
- package/server/agent/backend/types.ts +1 -1
- package/server/agent/config.ts +101 -31
- package/server/agent/index.ts +45 -33
- package/server/agent/mcp-server.ts +146 -69
- package/server/agent/mcp-tools/index.ts +1 -5
- package/server/agent/mcp-tools/notify.ts +2 -22
- package/server/agent/mcp-tools/x.ts +0 -4
- package/server/agent/mcpHealth.ts +168 -0
- package/server/agent/plugin-names.ts +20 -77
- package/server/agent/prompt.ts +259 -51
- package/server/agent/resumeFailover.ts +1 -1
- package/server/agent/stream.ts +0 -1
- package/server/api/auth/bearerAuth.ts +5 -5
- package/server/api/csrfGuard.ts +1 -1
- package/server/api/routes/accounting.ts +366 -0
- package/server/api/routes/agent.ts +509 -46
- package/server/api/routes/attachment.ts +104 -0
- package/server/api/routes/chart.ts +2 -1
- package/server/api/routes/config.ts +12 -12
- package/server/api/routes/files.ts +105 -48
- package/server/api/routes/image.ts +70 -25
- package/server/api/routes/journal.ts +35 -0
- package/server/api/routes/mulmo-script.ts +358 -118
- package/server/api/routes/mulmoScriptValidate.ts +1 -1
- package/server/api/routes/news.ts +1 -1
- package/server/api/routes/notifications.ts +92 -22
- package/server/api/routes/notifier.ts +98 -0
- package/server/api/routes/pdf.ts +188 -48
- package/server/api/routes/plugins.ts +34 -14
- package/server/api/routes/presentHtml.ts +58 -3
- package/server/api/routes/roles.ts +1 -8
- package/server/api/routes/runtime-plugin.ts +224 -0
- package/server/api/routes/scheduler.ts +7 -5
- package/server/api/routes/schedulerHandlers.ts +1 -1
- package/server/api/routes/schedulerTasks.ts +8 -7
- package/server/api/routes/sessions.ts +234 -121
- package/server/api/routes/skills.ts +56 -51
- package/server/api/routes/sources.ts +52 -45
- package/server/api/routes/translation.ts +44 -0
- package/server/api/routes/wiki/frontmatter.ts +13 -65
- package/server/api/routes/wiki/history.ts +261 -0
- package/server/api/routes/wiki/pageIndex.ts +1 -1
- package/server/api/routes/wiki.ts +50 -26
- package/server/events/file-change.ts +83 -0
- package/server/events/notifications.ts +247 -91
- package/server/events/pub-sub/index.ts +1 -1
- package/server/events/relay-client.ts +5 -5
- package/server/events/scheduler-adapter.ts +2 -2
- package/server/events/session-store/index.ts +110 -22
- package/server/events/task-manager/index.ts +10 -9
- package/server/index.ts +509 -33
- package/server/notifier/engine.ts +419 -0
- package/server/notifier/legacy-adapters.ts +76 -0
- package/server/notifier/runtime-api.ts +74 -0
- package/server/notifier/store.ts +70 -0
- package/server/notifier/types.ts +121 -0
- package/server/plugins/dev-loader.ts +171 -0
- package/server/plugins/dev-watcher.ts +150 -0
- package/server/plugins/diagnostics.ts +188 -0
- package/server/plugins/preset-list.ts +52 -0
- package/server/plugins/preset-loader.ts +112 -0
- package/server/plugins/runtime-chat-api.ts +38 -0
- package/server/plugins/runtime-loader.ts +430 -0
- package/server/plugins/runtime-registry.ts +112 -0
- package/server/plugins/runtime-tasks-api.ts +50 -0
- package/server/plugins/runtime.ts +378 -0
- package/server/services/translation/cache.ts +72 -0
- package/server/services/translation/index.ts +106 -0
- package/server/services/translation/llm.ts +140 -0
- package/server/services/translation/types.ts +35 -0
- package/server/system/credentials.ts +13 -2
- package/server/system/env.ts +6 -1
- package/server/system/logger/formatters.ts +46 -4
- package/server/system/logger/index.ts +4 -4
- package/server/system/logger/sinks.ts +26 -5
- package/server/system/logger/types.ts +2 -2
- package/server/utils/dev-plugin-args.d.mts +11 -0
- package/server/utils/dev-plugin-args.mjs +43 -0
- package/server/utils/errors.ts +13 -4
- package/server/utils/files/accounting-io.ts +295 -0
- package/server/utils/files/atomic.ts +17 -49
- package/server/utils/files/attachment-store.ts +182 -0
- package/server/utils/files/html-io.ts +1 -7
- package/server/utils/files/html-store.ts +19 -0
- package/server/utils/files/image-store.ts +20 -22
- package/server/utils/files/index.ts +5 -15
- package/server/utils/files/journal-io.ts +7 -35
- package/server/utils/files/json.ts +2 -29
- package/server/utils/files/markdown-image-fill.ts +6 -37
- package/server/utils/files/markdown-store.ts +6 -21
- package/server/utils/files/naming.ts +3 -39
- package/server/utils/files/plugins-io.ts +100 -0
- package/server/utils/files/reference-dirs-io.ts +1 -9
- package/server/utils/files/roles-io.ts +2 -10
- package/server/utils/files/safe.ts +17 -19
- package/server/utils/files/scheduler-io.ts +1 -7
- package/server/utils/files/scheduler-overrides-io.ts +3 -12
- package/server/utils/files/session-io.ts +21 -30
- package/server/utils/files/spreadsheet-store.ts +9 -22
- package/server/utils/files/translation-io.ts +46 -0
- package/server/utils/files/user-tasks-io.ts +1 -7
- package/server/utils/files/workspace-io.ts +3 -79
- package/server/utils/gemini.ts +33 -11
- package/server/utils/html/htmlArtifactSplicer.ts +41 -0
- package/server/utils/markdown/frontmatter.ts +112 -0
- package/server/utils/regex.ts +56 -0
- package/server/utils/router.ts +41 -0
- package/server/utils/slug.ts +5 -3
- package/server/utils/time.ts +12 -0
- package/server/workspace/chat-index/indexer.ts +15 -2
- package/server/workspace/chat-index/summarizer.ts +1 -1
- package/server/workspace/custom-dirs.ts +1 -1
- package/server/workspace/helps/gemini.md +1 -1
- package/server/workspace/helps/guide.md +61 -0
- package/server/workspace/helps/index.md +4 -0
- package/server/workspace/helps/presenthtml.md +80 -0
- package/server/workspace/helps/sandbox.md +7 -0
- package/server/workspace/helps/storyteller.md +101 -0
- package/server/workspace/helps/telegram.md +1 -0
- package/server/workspace/helps/wiki.md +9 -7
- package/server/workspace/journal/archivist-cli.ts +7 -33
- package/server/workspace/journal/archivist-schemas.ts +5 -43
- package/server/workspace/journal/dailyPass.ts +34 -187
- package/server/workspace/journal/diff.ts +3 -28
- package/server/workspace/journal/index.ts +10 -81
- package/server/workspace/journal/indexFile.ts +3 -24
- package/server/workspace/journal/latestDaily.ts +51 -0
- package/server/workspace/journal/memoryExtractor.ts +4 -20
- package/server/workspace/journal/optimizationPass.ts +4 -21
- package/server/workspace/journal/paths.ts +4 -23
- package/server/workspace/journal/state.ts +6 -29
- package/server/workspace/memory/io.ts +213 -0
- package/server/workspace/memory/llm-classifier.ts +158 -0
- package/server/workspace/memory/migrate.ts +263 -0
- package/server/workspace/memory/run.ts +84 -0
- package/server/workspace/memory/topic-cluster.ts +218 -0
- package/server/workspace/memory/topic-detect.ts +67 -0
- package/server/workspace/memory/topic-index-hook.ts +128 -0
- package/server/workspace/memory/topic-io.ts +180 -0
- package/server/workspace/memory/topic-migrate.ts +248 -0
- package/server/workspace/memory/topic-run.ts +172 -0
- package/server/workspace/memory/topic-swap.ts +135 -0
- package/server/workspace/memory/topic-types.ts +142 -0
- package/server/workspace/memory/types.ts +83 -0
- package/server/workspace/news/reader.ts +4 -5
- package/server/workspace/paths.ts +124 -47
- package/server/workspace/roles.ts +2 -11
- package/server/workspace/skills/parser.ts +38 -55
- package/server/workspace/skills/user-tasks.ts +1 -2
- package/server/workspace/skills-preset/mc-library/SKILL.md +188 -0
- package/server/workspace/skills-preset.ts +196 -0
- package/server/workspace/sources/fetchers/githubIssues.ts +13 -11
- package/server/workspace/sources/fetchers/index.ts +1 -1
- package/server/workspace/sources/fetchers/rssParser.ts +1 -1
- package/server/workspace/sources/pipeline/index.ts +2 -2
- package/server/workspace/sources/pipeline/notify.ts +3 -3
- package/server/workspace/sources/pipeline/write.ts +2 -2
- package/server/workspace/sources/registry.ts +39 -61
- package/server/workspace/sources/robots.ts +1 -1
- package/server/workspace/tool-trace/classify.ts +2 -1
- package/server/workspace/tool-trace/index.ts +1 -1
- package/server/workspace/tool-trace/writeSearch.ts +6 -1
- package/server/workspace/wiki-backlinks/index.ts +19 -7
- package/server/workspace/wiki-backlinks/sessionBacklinks.ts +1 -0
- package/server/workspace/wiki-history/hook/snapshot.mjs +98 -0
- package/server/workspace/wiki-history/hook/snapshot.ts +135 -0
- package/server/workspace/wiki-history/provision.ts +181 -0
- package/server/workspace/wiki-pages/io.ts +217 -0
- package/server/workspace/wiki-pages/snapshot.ts +380 -0
- package/server/workspace/workspace.ts +75 -13
- package/src/App.vue +115 -40
- package/src/_runtime/protocol-vue.ts +21 -0
- package/src/_runtime/vue.ts +22 -0
- package/src/components/ChatInput.vue +14 -10
- package/src/components/CopyChatButton.vue +76 -0
- package/src/components/FileContentRenderer.vue +67 -14
- package/src/components/FileTree.vue +2 -2
- package/src/components/FilesView.vue +17 -1
- package/src/components/NewsView.vue +16 -2
- package/src/components/NotificationBell.vue +320 -93
- package/src/components/PageChatComposer.vue +5 -4
- package/src/components/PluginLauncher.vue +42 -6
- package/src/components/PluginScopedRoot.vue +87 -0
- package/src/components/RoleSelector.vue +12 -1
- package/src/components/RolesView.vue +562 -0
- package/src/components/SentAttachmentChip.vue +102 -0
- package/src/components/SessionHistoryPanel.vue +109 -20
- package/src/components/SessionRoleIcon.vue +7 -4
- package/src/components/SessionSidebar.vue +20 -7
- package/src/components/SessionTabBar.vue +1 -1
- package/src/components/SettingsMcpTab.vue +4 -4
- package/src/components/SettingsModal.vue +2 -0
- package/src/components/SidebarHeader.vue +16 -5
- package/src/components/SourcesManager.vue +23 -9
- package/src/components/SourcesView.vue +1 -1
- package/src/components/StackView.vue +102 -6
- package/src/components/SuggestionsPanel.vue +105 -16
- package/src/components/SystemFileBanner.vue +1 -1
- package/src/components/TodoExplorer.vue +4 -5
- package/src/components/todo/TodoAddDialog.vue +2 -3
- package/src/components/todo/TodoEditDialog.vue +1 -2
- package/src/components/todo/TodoEditPanel.vue +2 -3
- package/src/components/todo/TodoKanbanView.vue +8 -5
- package/src/components/todo/TodoListView.vue +3 -5
- package/src/components/todo/TodoTableView.vue +7 -5
- package/src/composables/useAccountingChannel.ts +58 -0
- package/src/composables/useActiveSession.ts +4 -25
- package/src/composables/useAppApi.ts +6 -44
- package/src/composables/useClipboardCopy.ts +3 -20
- package/src/composables/useContentDisplay.ts +33 -2
- package/src/composables/useDevPluginReload.ts +23 -0
- package/src/composables/useDynamicFavicon.ts +5 -31
- package/src/composables/useEventListeners.ts +0 -20
- package/src/composables/useExpandedDirs.ts +4 -15
- package/src/composables/useFaviconState.ts +12 -46
- package/src/composables/useFileChange.ts +53 -0
- package/src/composables/useFreshPluginData.ts +6 -43
- package/src/composables/useHealth.ts +14 -43
- package/src/composables/useImageErrorRepair.ts +104 -0
- package/src/composables/useLatestDaily.ts +40 -0
- package/src/composables/useMarkdownDoc.ts +39 -0
- package/src/composables/useMarkdownLinkHandler.ts +1 -1
- package/src/composables/useMcpTools.ts +3 -16
- package/src/composables/useNotifications.ts +138 -112
- package/src/composables/usePdfDownload.ts +17 -3
- package/src/composables/usePendingCalls.ts +8 -26
- package/src/composables/usePluginErrorBoundary.ts +68 -0
- package/src/composables/usePubSub.ts +9 -17
- package/src/composables/useRunElapsed.ts +5 -22
- package/src/composables/useSandboxStatus.ts +4 -20
- package/src/composables/useSessionDerived.ts +7 -15
- package/src/composables/useSessionHistory.ts +70 -29
- package/src/composables/useSessionSync.ts +25 -3
- package/src/composables/useSkillsList.ts +59 -0
- package/src/composables/useTranslatedQueries.ts +109 -0
- package/src/config/apiRoutes.ts +181 -80
- package/src/config/historyFilters.ts +5 -3
- package/src/config/hostEvents.ts +17 -0
- package/src/config/mcpCatalog.ts +277 -5
- package/src/config/pubsubChannels.ts +134 -12
- package/src/config/roles.ts +212 -147
- package/src/config/systemFileDescriptors.ts +5 -5
- package/src/config/toolNames.ts +52 -30
- package/src/config/workspacePaths.ts +26 -2
- package/src/lang/de.ts +483 -27
- package/src/lang/en.ts +448 -27
- package/src/lang/es.ts +474 -27
- package/src/lang/fr.ts +476 -27
- package/src/lang/ja.ts +465 -27
- package/src/lang/ko.ts +466 -27
- package/src/lang/pt-BR.ts +473 -27
- package/src/lang/zh.ts +463 -27
- package/src/lib/vue-i18n.ts +1 -1
- package/src/lib/wiki-page/slug.ts +66 -0
- package/src/main.ts +85 -0
- package/src/plugins/_extras.ts +58 -0
- package/src/plugins/_generated/metas.ts +42 -0
- package/src/plugins/_generated/registrations.ts +44 -0
- package/src/plugins/_generated/server-bindings.ts +47 -0
- package/src/plugins/accounting/Preview.vue +106 -0
- package/src/plugins/accounting/View.vue +632 -0
- package/src/plugins/accounting/actions.ts +34 -0
- package/src/plugins/accounting/api.ts +301 -0
- package/src/plugins/accounting/components/AccountEditor.vue +250 -0
- package/src/plugins/accounting/components/AccountRow.vue +50 -0
- package/src/plugins/accounting/components/AccountsList.vue +102 -0
- package/src/plugins/accounting/components/AccountsModal.vue +300 -0
- package/src/plugins/accounting/components/BalanceSheet.vue +186 -0
- package/src/plugins/accounting/components/BookSettings.vue +284 -0
- package/src/plugins/accounting/components/BookSwitcher.vue +78 -0
- package/src/plugins/accounting/components/DateRangePicker.vue +140 -0
- package/src/plugins/accounting/components/JournalEntryForm.vue +504 -0
- package/src/plugins/accounting/components/JournalList.vue +553 -0
- package/src/plugins/accounting/components/Ledger.vue +206 -0
- package/src/plugins/accounting/components/NewBookForm.vue +211 -0
- package/src/plugins/accounting/components/OpeningBalancesForm.vue +271 -0
- package/src/plugins/accounting/components/ProfitLoss.vue +160 -0
- package/src/plugins/accounting/components/accountDraft.ts +13 -0
- package/src/plugins/accounting/components/accountNumbering.ts +103 -0
- package/src/plugins/accounting/components/accountValidation.ts +75 -0
- package/src/plugins/accounting/components/useLatestRequest.ts +44 -0
- package/src/plugins/accounting/countries.ts +158 -0
- package/src/plugins/accounting/currencies.ts +64 -0
- package/src/plugins/accounting/dates.ts +51 -0
- package/src/plugins/accounting/definition.ts +199 -0
- package/src/plugins/accounting/fiscalYear.ts +136 -0
- package/src/plugins/accounting/index.ts +49 -0
- package/src/plugins/accounting/meta.ts +91 -0
- package/src/plugins/accounting/timeSeriesEnums.ts +16 -0
- package/src/plugins/api.ts +125 -0
- package/src/plugins/canvas/View.vue +38 -28
- package/src/plugins/canvas/definition.ts +10 -8
- package/src/plugins/canvas/index.ts +15 -8
- package/src/plugins/canvas/meta.ts +12 -0
- package/src/plugins/chart/Preview.vue +1 -1
- package/src/plugins/chart/View.vue +2 -2
- package/src/plugins/chart/definition.ts +12 -2
- package/src/plugins/chart/index.ts +15 -7
- package/src/plugins/chart/meta.ts +18 -0
- package/src/plugins/editImages/definition.ts +44 -0
- package/src/plugins/editImages/index.ts +43 -0
- package/src/plugins/editImages/meta.ts +5 -0
- package/src/plugins/generateImage/View.vue +3 -1
- package/src/plugins/generateImage/definition.ts +2 -0
- package/src/plugins/generateImage/index.ts +13 -5
- package/src/plugins/generateImage/meta.ts +5 -0
- package/src/plugins/index.ts +35 -0
- package/src/plugins/manageRoles/Preview.vue +7 -4
- package/src/plugins/manageRoles/View.vue +12 -8
- package/src/plugins/manageRoles/definition.ts +6 -0
- package/src/plugins/manageRoles/index.ts +7 -6
- package/src/plugins/manageSkills/View.vue +11 -7
- package/src/plugins/manageSkills/definition.ts +4 -1
- package/src/plugins/manageSkills/index.ts +14 -7
- package/src/plugins/manageSkills/meta.ts +21 -0
- package/src/plugins/manageSource/definition.ts +4 -1
- package/src/plugins/manageSource/index.ts +15 -7
- package/src/plugins/manageSource/meta.ts +21 -0
- package/src/plugins/markdown/Preview.vue +10 -8
- package/src/plugins/markdown/View.vue +84 -17
- package/src/plugins/markdown/definition.ts +7 -1
- package/src/plugins/markdown/index.ts +15 -8
- package/src/plugins/markdown/meta.ts +16 -0
- package/src/plugins/meta-types.ts +97 -0
- package/src/plugins/metas.ts +224 -0
- package/src/plugins/presentForm/Preview.vue +4 -15
- package/src/plugins/presentForm/View.vue +35 -78
- package/src/plugins/presentForm/definition.ts +7 -6
- package/src/plugins/presentForm/index.ts +12 -5
- package/src/plugins/presentForm/meta.ts +11 -0
- package/src/plugins/presentForm/plugin.ts +8 -9
- package/src/plugins/presentForm/types.ts +0 -24
- package/src/plugins/presentHtml/Preview.vue +1 -8
- package/src/plugins/presentHtml/View.vue +401 -30
- package/src/plugins/presentHtml/definition.ts +8 -5
- package/src/plugins/presentHtml/index.ts +15 -8
- package/src/plugins/presentHtml/meta.ts +14 -0
- package/src/plugins/presentMulmoScript/View.vue +327 -107
- package/src/plugins/presentMulmoScript/definition.ts +34 -7
- package/src/plugins/presentMulmoScript/helpers.ts +4 -5
- package/src/plugins/presentMulmoScript/index.ts +20 -7
- package/src/plugins/presentMulmoScript/meta.ts +52 -0
- package/src/plugins/scheduler/AutomationsPreview.vue +2 -8
- package/src/plugins/scheduler/Preview.vue +5 -2
- package/src/plugins/scheduler/TasksTab.vue +16 -36
- package/src/plugins/scheduler/View.vue +22 -54
- package/src/plugins/scheduler/automationsDefinition.ts +14 -9
- package/src/plugins/scheduler/automationsMeta.ts +5 -0
- package/src/plugins/scheduler/calendarDefinition.ts +4 -7
- package/src/plugins/scheduler/calendarMeta.ts +28 -0
- package/src/plugins/scheduler/formatSchedule.ts +6 -24
- package/src/plugins/scheduler/index.ts +26 -52
- package/src/plugins/scope.ts +57 -0
- package/src/plugins/server-bindings-types.ts +38 -0
- package/src/plugins/server.ts +32 -0
- package/src/plugins/skill/Preview.vue +25 -0
- package/src/plugins/skill/View.vue +125 -0
- package/src/plugins/skill/definition.ts +23 -0
- package/src/plugins/skill/index.ts +36 -0
- package/src/plugins/skill/plugin.ts +31 -0
- package/src/plugins/skill/types.ts +21 -0
- package/src/plugins/spreadsheet/Preview.vue +1 -3
- package/src/plugins/spreadsheet/View.vue +29 -49
- package/src/plugins/spreadsheet/cellHighlights.ts +2 -3
- package/src/plugins/spreadsheet/definition.ts +5 -2
- package/src/plugins/spreadsheet/index.ts +15 -8
- package/src/plugins/spreadsheet/keyboardNav.ts +38 -0
- package/src/plugins/spreadsheet/meta.ts +14 -0
- package/src/plugins/textResponse/Preview.vue +9 -1
- package/src/plugins/textResponse/View.vue +59 -8
- package/src/plugins/textResponse/index.ts +11 -3
- package/src/plugins/textResponse/plugin.ts +8 -10
- package/src/plugins/textResponse/types.ts +28 -0
- package/src/plugins/wiki/Preview.vue +6 -4
- package/src/plugins/wiki/View.vue +463 -254
- package/src/plugins/wiki/components/WikiPageBody.vue +159 -0
- package/src/plugins/wiki/helpers.ts +17 -0
- package/src/plugins/wiki/history/HistoryDetail.vue +325 -0
- package/src/plugins/wiki/history/HistoryTab.vue +167 -0
- package/src/plugins/wiki/history/RestoreConfirm.vue +63 -0
- package/src/plugins/wiki/history/api.ts +52 -0
- package/src/plugins/wiki/history/diff.ts +145 -0
- package/src/plugins/wiki/index.ts +42 -32
- package/src/plugins/wiki/meta.ts +10 -0
- package/src/plugins/wiki/pageEditLoader.ts +53 -0
- package/src/plugins/wiki/route.ts +8 -0
- package/src/router/guards.ts +2 -1
- package/src/router/index.ts +19 -0
- package/src/router/pageRoutes.ts +1 -0
- package/src/tools/index.ts +50 -51
- package/src/tools/runtimeLoader.ts +141 -0
- package/src/tools/types.ts +44 -1
- package/src/types/notification.ts +23 -0
- package/src/types/pastedFile.ts +10 -0
- package/src/types/session.ts +61 -3
- package/src/types/sse.ts +21 -6
- package/src/utils/agent/eventDispatch.ts +12 -9
- package/src/utils/agent/pastedAttachment.ts +35 -0
- package/src/utils/agent/request.ts +32 -3
- package/src/utils/agent/toolCalls.ts +7 -1
- package/src/utils/api.ts +1 -1
- package/src/utils/chat/exportMarkdown.ts +243 -0
- package/src/utils/errors.ts +10 -2
- package/src/utils/files/expandedDirs.ts +1 -1
- package/src/utils/filesPreview/todoPreview.ts +13 -2
- package/src/utils/format/date.ts +1 -3
- package/src/utils/format/jsonSyntax.ts +5 -0
- package/src/utils/html/iframeHeightReporterScript.ts +62 -0
- package/src/utils/html/previewCsp.ts +29 -2
- package/src/utils/image/htmlSrcAttrs.ts +122 -0
- package/src/utils/image/imageRepairInlineScript.ts +115 -0
- package/src/utils/image/resolve.ts +17 -3
- package/src/utils/image/rewriteMarkdownImageRefs.ts +62 -9
- package/src/utils/markdown/frontmatter.ts +125 -0
- package/src/utils/markdown/taskList.ts +7 -2
- package/src/utils/plugin/runtime.ts +132 -0
- package/src/utils/session/mergeSessions.ts +40 -37
- package/src/utils/session/sessionEntries.ts +74 -18
- package/src/utils/session/sessionHelpers.ts +54 -10
- package/src/utils/tools/result.ts +76 -14
- package/src/vite-env.d.ts +6 -0
- package/client/assets/html2canvas-Cx501zZr-Bug0qRNv.js +0 -5
- package/client/assets/index-CY-WpQUm.css +0 -2
- package/client/assets/index-DbTz2Mfs.js +0 -4911
- package/client/assets/material-symbols-outlined-NzYEeyps.woff2 +0 -0
- package/server/api/routes/html.ts +0 -114
- package/server/api/routes/todos.ts +0 -293
- package/server/api/routes/todosColumnsHandlers.ts +0 -333
- package/server/api/routes/todosHandlers.ts +0 -274
- package/server/api/routes/todosItemsHandlers.ts +0 -386
- package/server/utils/files/todos-io.ts +0 -29
- package/src/components/NotificationToast.vue +0 -75
- package/src/plugins/editImage/definition.ts +0 -27
- package/src/plugins/editImage/index.ts +0 -37
- package/src/plugins/presentHtml/helpers.ts +0 -72
- package/src/plugins/scheduler/LegacySchedulerView.vue +0 -32
- package/src/plugins/scheduler/legacyShape.ts +0 -34
- package/src/plugins/todo/Preview.vue +0 -68
- package/src/plugins/todo/View.vue +0 -378
- package/src/plugins/todo/composables/useTodos.ts +0 -179
- package/src/plugins/todo/definition.ts +0 -45
- package/src/plugins/todo/index.ts +0 -62
- package/src/plugins/todo/labels.ts +0 -163
- package/src/plugins/todo/priority.ts +0 -98
- package/src/plugins/todo/viewModes.ts +0 -19
- package/src/plugins/wiki/definition.ts +0 -25
- package/src/tools/legacyPluginNames.ts +0 -13
- package/src/utils/format/frontmatter.ts +0 -80
- package/src/utils/image/rewriteHtmlImageRefs.ts +0 -50
- package/src/utils/notification/dispatch.ts +0 -58
- /package/client/assets/{purify.es-Fx1Nqyry-BwJECkqS.js → purify.es-Fx1Nqyry-BSVNht6S.js} +0 -0
- /package/src/plugins/{editImage → editImages}/Preview.vue +0 -0
- /package/src/plugins/{editImage → editImages}/View.vue +0 -0
- /package/src/{config/schedulerActions.ts → plugins/scheduler/actions.ts} +0 -0
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<!-- eslint-disable-next-line vue/no-v-html -- marked.parse output of app-owned wiki page body; trusted in-process render -->
|
|
3
|
+
<div ref="rootRef" data-testid="wiki-page-body" class="px-6 py-4 prose prose-sm max-w-none wiki-content" @click="onClick" v-html="renderedHtml" />
|
|
4
|
+
</template>
|
|
5
|
+
|
|
6
|
+
<script setup lang="ts">
|
|
7
|
+
import { computed, ref } from "vue";
|
|
8
|
+
import { renderWikiPageHtml } from "../helpers";
|
|
9
|
+
import { handleExternalLinkClick } from "../../../utils/dom/externalLink";
|
|
10
|
+
import { classifyWorkspacePath, resolveWikiHref } from "../../../utils/path/workspaceLinkRouter";
|
|
11
|
+
|
|
12
|
+
const props = defineProps<{
|
|
13
|
+
body: string;
|
|
14
|
+
baseDir: string;
|
|
15
|
+
}>();
|
|
16
|
+
|
|
17
|
+
const emit = defineEmits<{
|
|
18
|
+
taskCheckboxClick: [event: MouseEvent, target: HTMLInputElement];
|
|
19
|
+
wikiLinkClick: [slug: string];
|
|
20
|
+
workspaceLinkClick: [path: string];
|
|
21
|
+
}>();
|
|
22
|
+
|
|
23
|
+
const rootRef = ref<HTMLElement | null>(null);
|
|
24
|
+
|
|
25
|
+
const renderedHtml = computed(() => renderWikiPageHtml(props.body, props.baseDir));
|
|
26
|
+
|
|
27
|
+
defineExpose({ rootRef });
|
|
28
|
+
|
|
29
|
+
function onClick(event: MouseEvent) {
|
|
30
|
+
const target = event.target as HTMLElement;
|
|
31
|
+
if (target instanceof HTMLInputElement && target.type === "checkbox" && target.classList.contains("md-task")) {
|
|
32
|
+
emit("taskCheckboxClick", event, target);
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
const link = target.closest(".wiki-link") as HTMLElement | null;
|
|
36
|
+
if (link?.dataset.page) {
|
|
37
|
+
emit("wikiLinkClick", link.dataset.page);
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
if (handleExternalLinkClick(event)) return;
|
|
41
|
+
if (event.button !== 0 || event.ctrlKey || event.metaKey || event.shiftKey) return;
|
|
42
|
+
const anchor = target.closest("a");
|
|
43
|
+
if (!anchor) return;
|
|
44
|
+
const href = anchor.getAttribute("href");
|
|
45
|
+
if (!href || href.startsWith("#")) return;
|
|
46
|
+
const resolved = resolveWikiHref(href, props.baseDir);
|
|
47
|
+
if (classifyWorkspacePath(resolved)) {
|
|
48
|
+
event.preventDefault();
|
|
49
|
+
emit("workspaceLinkClick", resolved);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
</script>
|
|
53
|
+
|
|
54
|
+
<style scoped>
|
|
55
|
+
.wiki-content :deep(.wiki-link) {
|
|
56
|
+
color: #2563eb;
|
|
57
|
+
cursor: pointer;
|
|
58
|
+
text-decoration: underline;
|
|
59
|
+
text-decoration-style: dotted;
|
|
60
|
+
}
|
|
61
|
+
.wiki-content :deep(.wiki-link:hover) {
|
|
62
|
+
text-decoration-style: solid;
|
|
63
|
+
}
|
|
64
|
+
.wiki-content :deep(h1) {
|
|
65
|
+
font-size: 1.5rem;
|
|
66
|
+
font-weight: 700;
|
|
67
|
+
margin-top: 1.5rem;
|
|
68
|
+
margin-bottom: 0.75rem;
|
|
69
|
+
color: #111827;
|
|
70
|
+
}
|
|
71
|
+
.wiki-content :deep(h1:first-child),
|
|
72
|
+
.wiki-content :deep(h2:first-child),
|
|
73
|
+
.wiki-content :deep(h3:first-child),
|
|
74
|
+
.wiki-content :deep(p:first-child) {
|
|
75
|
+
margin-top: 0;
|
|
76
|
+
}
|
|
77
|
+
.wiki-content :deep(h2) {
|
|
78
|
+
font-size: 1.2rem;
|
|
79
|
+
font-weight: 600;
|
|
80
|
+
margin-top: 1.25rem;
|
|
81
|
+
margin-bottom: 0.5rem;
|
|
82
|
+
color: #1f2937;
|
|
83
|
+
border-bottom: 1px solid #e5e7eb;
|
|
84
|
+
padding-bottom: 0.25rem;
|
|
85
|
+
}
|
|
86
|
+
.wiki-content :deep(h3) {
|
|
87
|
+
font-size: 1rem;
|
|
88
|
+
font-weight: 600;
|
|
89
|
+
margin-top: 1rem;
|
|
90
|
+
margin-bottom: 0.5rem;
|
|
91
|
+
color: #374151;
|
|
92
|
+
}
|
|
93
|
+
.wiki-content :deep(p) {
|
|
94
|
+
margin-bottom: 0.75rem;
|
|
95
|
+
line-height: 1.6;
|
|
96
|
+
color: #374151;
|
|
97
|
+
}
|
|
98
|
+
.wiki-content :deep(ul),
|
|
99
|
+
.wiki-content :deep(ol) {
|
|
100
|
+
margin-left: 1.5rem;
|
|
101
|
+
margin-bottom: 0.75rem;
|
|
102
|
+
}
|
|
103
|
+
.wiki-content :deep(li) {
|
|
104
|
+
margin-bottom: 0.25rem;
|
|
105
|
+
line-height: 1.5;
|
|
106
|
+
color: #374151;
|
|
107
|
+
}
|
|
108
|
+
.wiki-content :deep(ul) {
|
|
109
|
+
list-style-type: disc;
|
|
110
|
+
}
|
|
111
|
+
.wiki-content :deep(ol) {
|
|
112
|
+
list-style-type: decimal;
|
|
113
|
+
}
|
|
114
|
+
.wiki-content :deep(hr) {
|
|
115
|
+
border: none;
|
|
116
|
+
border-top: 1px solid #e5e7eb;
|
|
117
|
+
margin: 1rem 0;
|
|
118
|
+
}
|
|
119
|
+
.wiki-content :deep(code) {
|
|
120
|
+
background: #f3f4f6;
|
|
121
|
+
padding: 0.1rem 0.3rem;
|
|
122
|
+
border-radius: 0.25rem;
|
|
123
|
+
font-size: 0.85em;
|
|
124
|
+
font-family: monospace;
|
|
125
|
+
}
|
|
126
|
+
.wiki-content :deep(pre) {
|
|
127
|
+
background: #f3f4f6;
|
|
128
|
+
padding: 0.75rem;
|
|
129
|
+
border-radius: 0.375rem;
|
|
130
|
+
overflow-x: auto;
|
|
131
|
+
margin-bottom: 0.75rem;
|
|
132
|
+
}
|
|
133
|
+
.wiki-content :deep(pre code) {
|
|
134
|
+
background: none;
|
|
135
|
+
padding: 0;
|
|
136
|
+
}
|
|
137
|
+
.wiki-content :deep(blockquote) {
|
|
138
|
+
border-left: 3px solid #d1d5db;
|
|
139
|
+
padding-left: 1rem;
|
|
140
|
+
color: #6b7280;
|
|
141
|
+
margin: 0.75rem 0;
|
|
142
|
+
}
|
|
143
|
+
.wiki-content :deep(table) {
|
|
144
|
+
border-collapse: collapse;
|
|
145
|
+
width: 100%;
|
|
146
|
+
margin-bottom: 0.75rem;
|
|
147
|
+
font-size: 0.875rem;
|
|
148
|
+
}
|
|
149
|
+
.wiki-content :deep(th),
|
|
150
|
+
.wiki-content :deep(td) {
|
|
151
|
+
border: 1px solid #e5e7eb;
|
|
152
|
+
padding: 0.5rem 0.75rem;
|
|
153
|
+
text-align: left;
|
|
154
|
+
}
|
|
155
|
+
.wiki-content :deep(th) {
|
|
156
|
+
background: #f9fafb;
|
|
157
|
+
font-weight: 600;
|
|
158
|
+
}
|
|
159
|
+
</style>
|
|
@@ -2,6 +2,23 @@
|
|
|
2
2
|
// `/\[\[([^\]]+)\]\]/g` regex — flagged by `sonarjs/slow-regex`
|
|
3
3
|
// for backtracking risk — with a linear walker.
|
|
4
4
|
|
|
5
|
+
import { marked } from "marked";
|
|
6
|
+
import { rewriteMarkdownImageRefs } from "../../utils/image/rewriteMarkdownImageRefs";
|
|
7
|
+
import { makeTasksInteractive } from "../../utils/markdown/taskList";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Pure markdown→HTML pipeline shared between the standalone /wiki
|
|
11
|
+
* view and the chat-inline preview (Stage 3a). Caller passes a body
|
|
12
|
+
* that already has frontmatter stripped, plus the workspace-relative
|
|
13
|
+
* base dir used to rewrite image refs (`data/wiki/pages` for a page,
|
|
14
|
+
* `data/wiki` for log/lint).
|
|
15
|
+
*/
|
|
16
|
+
export function renderWikiPageHtml(body: string, baseDir: string): string {
|
|
17
|
+
if (!body) return "";
|
|
18
|
+
const withImages = rewriteMarkdownImageRefs(body, baseDir);
|
|
19
|
+
return makeTasksInteractive(marked.parse(renderWikiLinks(withImages)) as string);
|
|
20
|
+
}
|
|
21
|
+
|
|
5
22
|
/**
|
|
6
23
|
* Replace every `[[page name]]` occurrence in `content` with a
|
|
7
24
|
* `<span class="wiki-link" data-page="…">…</span>` element. The
|
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed, onMounted, ref, watch } from "vue";
|
|
3
|
+
import { useI18n } from "vue-i18n";
|
|
4
|
+
import { formatSmartTime } from "../../../utils/format/date";
|
|
5
|
+
import { fetchHistorySnapshot, restoreHistorySnapshot, type SnapshotContent, type SnapshotSummary } from "./api";
|
|
6
|
+
import { DIFF_LINE_KIND, joinFrontmatterAndBody, renderUnifiedDiff, stripAutoStampKeys, type DiffHunk, type DiffLineKind } from "./diff";
|
|
7
|
+
import RestoreConfirm from "./RestoreConfirm.vue";
|
|
8
|
+
|
|
9
|
+
const props = defineProps<{
|
|
10
|
+
slug: string;
|
|
11
|
+
/** Summary of the snapshot being viewed (carries ts/editor/reason for the header). */
|
|
12
|
+
summary: SnapshotSummary;
|
|
13
|
+
/** Summary of the snapshot just before this one in the list (newest-first
|
|
14
|
+
* ordering = `summary` is at index N, `previousSummary` is at index N+1).
|
|
15
|
+
* null when this is the oldest entry. Used for the "compare with previous"
|
|
16
|
+
* toggle. */
|
|
17
|
+
previousSummary: SnapshotSummary | null;
|
|
18
|
+
/** Live page body + frontmatter, supplied by the parent so we don't
|
|
19
|
+
* re-fetch on tab switches. */
|
|
20
|
+
currentBody: string;
|
|
21
|
+
currentMeta: Record<string, unknown>;
|
|
22
|
+
}>();
|
|
23
|
+
|
|
24
|
+
const emit = defineEmits<{
|
|
25
|
+
back: [];
|
|
26
|
+
/** Fired after the server returns 200 on the restore POST. The
|
|
27
|
+
* parent (View.vue) handles the tab switch + success toast. */
|
|
28
|
+
restored: [];
|
|
29
|
+
}>();
|
|
30
|
+
|
|
31
|
+
const { t } = useI18n();
|
|
32
|
+
|
|
33
|
+
const COMPARE_TARGET = {
|
|
34
|
+
current: "current",
|
|
35
|
+
previous: "previous",
|
|
36
|
+
} as const;
|
|
37
|
+
type CompareTarget = (typeof COMPARE_TARGET)[keyof typeof COMPARE_TARGET];
|
|
38
|
+
|
|
39
|
+
const loading = ref(true);
|
|
40
|
+
const fetchError = ref<string | null>(null);
|
|
41
|
+
const snapshot = ref<SnapshotContent | null>(null);
|
|
42
|
+
const previousSnapshot = ref<SnapshotContent | null>(null);
|
|
43
|
+
// Separate error channel for the previous-compare fetch (codex
|
|
44
|
+
// iter-4 #946). Folding it into `fetchError` made a failed previous
|
|
45
|
+
// load take over the whole detail pane even after the user
|
|
46
|
+
// switched back to "current" — kept blocking valid state.
|
|
47
|
+
const previousFetchError = ref<string | null>(null);
|
|
48
|
+
|
|
49
|
+
const compareTarget = ref<CompareTarget>(COMPARE_TARGET.current);
|
|
50
|
+
const restoring = ref(false);
|
|
51
|
+
const restoreError = ref<string | null>(null);
|
|
52
|
+
const confirmOpen = ref(false);
|
|
53
|
+
|
|
54
|
+
// Stale-response guards (codex iter-1 #946). A user who clicks a
|
|
55
|
+
// different snapshot before the previous fetch returns would
|
|
56
|
+
// otherwise see the slow response overwrite the new selection.
|
|
57
|
+
// Two separate tokens because the "current" and "previous" fetches
|
|
58
|
+
// are independent.
|
|
59
|
+
let loadToken = 0;
|
|
60
|
+
let previousLoadToken = 0;
|
|
61
|
+
|
|
62
|
+
onMounted(async () => {
|
|
63
|
+
await loadThisSnapshot();
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
watch(
|
|
67
|
+
() => props.summary.stamp,
|
|
68
|
+
async (next, prev) => {
|
|
69
|
+
if (next === prev) return;
|
|
70
|
+
await loadThisSnapshot();
|
|
71
|
+
},
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
watch(compareTarget, async (target, prevTarget) => {
|
|
75
|
+
// Switching away from "previous" must invalidate any in-flight
|
|
76
|
+
// previous-snapshot load — otherwise its late failure would
|
|
77
|
+
// surface as `fetchError` and take over the detail pane even
|
|
78
|
+
// though the user is now looking at the (valid) current
|
|
79
|
+
// comparison (codex iter-3 #946).
|
|
80
|
+
if (prevTarget === COMPARE_TARGET.previous && target !== COMPARE_TARGET.previous) {
|
|
81
|
+
previousLoadToken += 1;
|
|
82
|
+
}
|
|
83
|
+
if (target === COMPARE_TARGET.previous && previousSnapshot.value === null && props.previousSummary !== null) {
|
|
84
|
+
await loadPreviousSnapshot();
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
async function loadThisSnapshot(): Promise<void> {
|
|
89
|
+
const myToken = ++loadToken;
|
|
90
|
+
// Invalidate any in-flight previous-snapshot load too — codex
|
|
91
|
+
// iter-2 #946: a slow `previousSnapshot` response was otherwise
|
|
92
|
+
// able to land after the user switched to a different snapshot,
|
|
93
|
+
// surfacing a comparison against the wrong base. Bumping
|
|
94
|
+
// `previousLoadToken` here guarantees that lingering response
|
|
95
|
+
// sees its token as stale and drops on the floor.
|
|
96
|
+
previousLoadToken += 1;
|
|
97
|
+
loading.value = true;
|
|
98
|
+
fetchError.value = null;
|
|
99
|
+
previousFetchError.value = null;
|
|
100
|
+
snapshot.value = null;
|
|
101
|
+
previousSnapshot.value = null;
|
|
102
|
+
compareTarget.value = COMPARE_TARGET.current;
|
|
103
|
+
restoreError.value = null;
|
|
104
|
+
const result = await fetchHistorySnapshot(props.slug, props.summary.stamp);
|
|
105
|
+
if (myToken !== loadToken) return;
|
|
106
|
+
loading.value = false;
|
|
107
|
+
if (!result.ok) {
|
|
108
|
+
fetchError.value = result.error;
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
snapshot.value = result.data.snapshot;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
async function loadPreviousSnapshot(): Promise<void> {
|
|
115
|
+
if (props.previousSummary === null) return;
|
|
116
|
+
const myToken = ++previousLoadToken;
|
|
117
|
+
previousFetchError.value = null;
|
|
118
|
+
const result = await fetchHistorySnapshot(props.slug, props.previousSummary.stamp);
|
|
119
|
+
if (myToken !== previousLoadToken) return;
|
|
120
|
+
if (!result.ok) {
|
|
121
|
+
previousFetchError.value = result.error;
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
previousSnapshot.value = result.data.snapshot;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const editorBadge = computed(() => editorBadgeFor(props.summary.editor));
|
|
128
|
+
|
|
129
|
+
function markerFor(kind: DiffLineKind): string {
|
|
130
|
+
if (kind === DIFF_LINE_KIND.add) return "+";
|
|
131
|
+
if (kind === DIFF_LINE_KIND.del) return "-";
|
|
132
|
+
return " "; // non-breaking space — keeps the column aligned
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function editorBadgeFor(editor: SnapshotSummary["editor"]): { label: string; className: string } {
|
|
136
|
+
if (editor === "llm") {
|
|
137
|
+
return { label: t("pluginWiki.history.editorBadgeLLM"), className: "bg-purple-50 text-purple-700" };
|
|
138
|
+
}
|
|
139
|
+
if (editor === "system") {
|
|
140
|
+
return { label: t("pluginWiki.history.editorBadgeSystem"), className: "bg-gray-100 text-gray-700" };
|
|
141
|
+
}
|
|
142
|
+
return { label: t("pluginWiki.history.editorBadgeUser"), className: "bg-blue-50 text-blue-700" };
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const hunks = computed<DiffHunk[]>(() => {
|
|
146
|
+
if (snapshot.value === null) return [];
|
|
147
|
+
// Right side = the snapshot we're viewing, with auto-stamps stripped.
|
|
148
|
+
const rightMeta = stripAutoStampKeys(stripSnapshotMetaPatchKeys(snapshot.value.meta));
|
|
149
|
+
const right = joinFrontmatterAndBody(rightMeta, snapshot.value.body);
|
|
150
|
+
|
|
151
|
+
if (compareTarget.value === COMPARE_TARGET.current) {
|
|
152
|
+
const leftMeta = stripAutoStampKeys(props.currentMeta);
|
|
153
|
+
const left = joinFrontmatterAndBody(leftMeta, props.currentBody);
|
|
154
|
+
return renderUnifiedDiff(left, right, 3);
|
|
155
|
+
}
|
|
156
|
+
// compare with previous
|
|
157
|
+
if (previousSnapshot.value === null) return [];
|
|
158
|
+
const leftMeta = stripAutoStampKeys(stripSnapshotMetaPatchKeys(previousSnapshot.value.meta));
|
|
159
|
+
const left = joinFrontmatterAndBody(leftMeta, previousSnapshot.value.body);
|
|
160
|
+
return renderUnifiedDiff(left, right, 3);
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
const showNoPreviousMessage = computed(() => compareTarget.value === COMPARE_TARGET.previous && props.previousSummary === null);
|
|
164
|
+
const showPreviousFetchError = computed(() => compareTarget.value === COMPARE_TARGET.previous && previousFetchError.value !== null);
|
|
165
|
+
const showNoChangesMessage = computed(() => {
|
|
166
|
+
if (loading.value) return false;
|
|
167
|
+
if (snapshot.value === null) return false;
|
|
168
|
+
if (compareTarget.value === COMPARE_TARGET.previous && (props.previousSummary === null || previousSnapshot.value === null)) return false;
|
|
169
|
+
return hunks.value.length === 0;
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
/** The snapshot's frontmatter still carries `_snapshot_*` keys
|
|
173
|
+
* (snapshot pipeline metadata). These are NOT user data — strip
|
|
174
|
+
* them before any diff so they don't show up as changes against
|
|
175
|
+
* the live page (which never carries them). */
|
|
176
|
+
function stripSnapshotMetaPatchKeys(meta: Record<string, unknown>): Record<string, unknown> {
|
|
177
|
+
const out: Record<string, unknown> = {};
|
|
178
|
+
for (const [key, value] of Object.entries(meta)) {
|
|
179
|
+
if (key.startsWith("_snapshot_")) continue;
|
|
180
|
+
out[key] = value;
|
|
181
|
+
}
|
|
182
|
+
return out;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function openConfirm(): void {
|
|
186
|
+
restoreError.value = null;
|
|
187
|
+
confirmOpen.value = true;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function cancelConfirm(): void {
|
|
191
|
+
if (restoring.value) return;
|
|
192
|
+
confirmOpen.value = false;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
async function performRestore(): Promise<void> {
|
|
196
|
+
restoring.value = true;
|
|
197
|
+
restoreError.value = null;
|
|
198
|
+
const result = await restoreHistorySnapshot(props.slug, props.summary.stamp);
|
|
199
|
+
restoring.value = false;
|
|
200
|
+
if (!result.ok) {
|
|
201
|
+
restoreError.value = result.error;
|
|
202
|
+
confirmOpen.value = false;
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
confirmOpen.value = false;
|
|
206
|
+
emit("restored");
|
|
207
|
+
}
|
|
208
|
+
</script>
|
|
209
|
+
|
|
210
|
+
<template>
|
|
211
|
+
<div class="flex-1 flex flex-col min-h-0" data-testid="wiki-history-detail">
|
|
212
|
+
<!-- Top bar: back + restore -->
|
|
213
|
+
<div class="shrink-0 flex items-center justify-between px-4 py-2 border-b border-gray-100">
|
|
214
|
+
<button
|
|
215
|
+
type="button"
|
|
216
|
+
class="h-8 px-2.5 flex items-center gap-1 rounded text-sm text-gray-600 hover:bg-gray-100 transition-colors"
|
|
217
|
+
data-testid="wiki-history-back-button"
|
|
218
|
+
@click="emit('back')"
|
|
219
|
+
>
|
|
220
|
+
<span class="material-icons text-base">arrow_back</span>
|
|
221
|
+
{{ t("pluginWiki.history.backToList") }}
|
|
222
|
+
</button>
|
|
223
|
+
<button
|
|
224
|
+
type="button"
|
|
225
|
+
class="h-8 px-2.5 flex items-center gap-1 rounded bg-blue-600 hover:bg-blue-700 text-white text-sm disabled:opacity-60 disabled:cursor-not-allowed transition-colors"
|
|
226
|
+
:disabled="loading || snapshot === null || restoring"
|
|
227
|
+
data-testid="wiki-history-restore-button"
|
|
228
|
+
@click="openConfirm"
|
|
229
|
+
>
|
|
230
|
+
<span class="material-icons text-base">restore</span>
|
|
231
|
+
{{ t("pluginWiki.history.restoreButton") }}
|
|
232
|
+
</button>
|
|
233
|
+
</div>
|
|
234
|
+
|
|
235
|
+
<!-- Header: snapshot metadata + diff target toggle -->
|
|
236
|
+
<div class="shrink-0 px-4 py-3 border-b border-gray-100 flex flex-wrap items-center justify-between gap-3">
|
|
237
|
+
<div class="flex flex-wrap items-center gap-2 text-sm text-gray-700 min-w-0">
|
|
238
|
+
<span :class="['inline-flex items-center px-2 h-5 rounded text-xs font-medium', editorBadge.className]" data-testid="wiki-history-detail-editor-badge">
|
|
239
|
+
{{ editorBadge.label }}
|
|
240
|
+
</span>
|
|
241
|
+
<span class="text-gray-500" data-testid="wiki-history-detail-ts">
|
|
242
|
+
{{ formatSmartTime(props.summary.ts) }}
|
|
243
|
+
</span>
|
|
244
|
+
<span v-if="props.summary.reason" class="text-gray-700 truncate" data-testid="wiki-history-detail-reason">{{ ` — ${props.summary.reason}` }}</span>
|
|
245
|
+
</div>
|
|
246
|
+
<div class="flex border border-gray-300 rounded overflow-hidden text-sm">
|
|
247
|
+
<button
|
|
248
|
+
:class="[
|
|
249
|
+
'h-8 px-2.5 transition-colors',
|
|
250
|
+
compareTarget === COMPARE_TARGET.current ? 'bg-blue-50 text-blue-600 font-medium' : 'bg-white text-gray-600 hover:bg-gray-50',
|
|
251
|
+
]"
|
|
252
|
+
data-testid="wiki-history-compare-current"
|
|
253
|
+
@click="compareTarget = COMPARE_TARGET.current"
|
|
254
|
+
>
|
|
255
|
+
{{ t("pluginWiki.history.compareCurrent") }}
|
|
256
|
+
</button>
|
|
257
|
+
<button
|
|
258
|
+
:class="[
|
|
259
|
+
'h-8 px-2.5 border-l border-gray-200 transition-colors',
|
|
260
|
+
compareTarget === COMPARE_TARGET.previous ? 'bg-blue-50 text-blue-600 font-medium' : 'bg-white text-gray-600 hover:bg-gray-50',
|
|
261
|
+
]"
|
|
262
|
+
data-testid="wiki-history-compare-previous"
|
|
263
|
+
@click="compareTarget = COMPARE_TARGET.previous"
|
|
264
|
+
>
|
|
265
|
+
{{ t("pluginWiki.history.comparePrevious") }}
|
|
266
|
+
</button>
|
|
267
|
+
</div>
|
|
268
|
+
</div>
|
|
269
|
+
|
|
270
|
+
<!-- Inline restore-failure banner (Q10=B) -->
|
|
271
|
+
<div
|
|
272
|
+
v-if="restoreError"
|
|
273
|
+
class="shrink-0 mx-4 mt-3 rounded border border-red-200 bg-red-50 px-3 py-2 text-sm text-red-700"
|
|
274
|
+
data-testid="wiki-history-restore-error"
|
|
275
|
+
>
|
|
276
|
+
{{ t("pluginWiki.history.restoreFailureBanner", { error: restoreError }) }}
|
|
277
|
+
</div>
|
|
278
|
+
|
|
279
|
+
<!-- Body: loading / fetch error / no-previous / no-changes / diff -->
|
|
280
|
+
<div class="flex-1 overflow-y-auto px-4 py-3 font-mono text-xs">
|
|
281
|
+
<div v-if="loading" class="flex items-center justify-center py-8 text-gray-400">
|
|
282
|
+
<span class="material-icons animate-spin text-base mr-2">progress_activity</span>
|
|
283
|
+
{{ t("pluginWiki.history.loading") }}
|
|
284
|
+
</div>
|
|
285
|
+
<div v-else-if="fetchError" class="text-red-600">{{ fetchError }}</div>
|
|
286
|
+
<div v-else-if="showNoPreviousMessage" class="text-gray-500">{{ t("pluginWiki.history.diffNoPrevious") }}</div>
|
|
287
|
+
<div v-else-if="showPreviousFetchError" class="text-red-600">{{ previousFetchError }}</div>
|
|
288
|
+
<div v-else-if="showNoChangesMessage" class="text-gray-500">{{ t("pluginWiki.history.diffNoChanges") }}</div>
|
|
289
|
+
<div v-else>
|
|
290
|
+
<template v-for="(hunk, hunkIdx) in hunks" :key="hunkIdx">
|
|
291
|
+
<div v-if="hunk.hiddenBefore > 0" class="text-gray-400 italic px-2 py-1 border-y border-gray-100 bg-gray-50">
|
|
292
|
+
{{ t("pluginWiki.history.hiddenLines", { count: hunk.hiddenBefore }) }}
|
|
293
|
+
</div>
|
|
294
|
+
<div
|
|
295
|
+
v-for="(line, lineIdx) in hunk.lines"
|
|
296
|
+
:key="`${hunkIdx}-${lineIdx}`"
|
|
297
|
+
:class="[
|
|
298
|
+
'whitespace-pre-wrap px-2 py-0.5 leading-snug',
|
|
299
|
+
line.kind === DIFF_LINE_KIND.add && 'bg-green-50 text-green-800',
|
|
300
|
+
line.kind === DIFF_LINE_KIND.del && 'bg-red-50 text-red-800',
|
|
301
|
+
line.kind === DIFF_LINE_KIND.context && 'text-gray-700',
|
|
302
|
+
]"
|
|
303
|
+
:data-testid="`wiki-history-diff-line-${line.kind}`"
|
|
304
|
+
>
|
|
305
|
+
<span
|
|
306
|
+
:class="[
|
|
307
|
+
'mr-1',
|
|
308
|
+
line.kind === DIFF_LINE_KIND.add && 'text-green-600',
|
|
309
|
+
line.kind === DIFF_LINE_KIND.del && 'text-red-600',
|
|
310
|
+
line.kind === DIFF_LINE_KIND.context && 'text-gray-300',
|
|
311
|
+
]"
|
|
312
|
+
>{{ markerFor(line.kind) }}</span
|
|
313
|
+
>{{ line.text }}
|
|
314
|
+
</div>
|
|
315
|
+
<div v-if="hunkIdx === hunks.length - 1 && hunk.hiddenAfter > 0" class="text-gray-400 italic px-2 py-1 border-y border-gray-100 bg-gray-50">
|
|
316
|
+
{{ t("pluginWiki.history.hiddenLines", { count: hunk.hiddenAfter }) }}
|
|
317
|
+
</div>
|
|
318
|
+
</template>
|
|
319
|
+
</div>
|
|
320
|
+
</div>
|
|
321
|
+
|
|
322
|
+
<!-- Confirm modal -->
|
|
323
|
+
<RestoreConfirm v-if="confirmOpen" :snapshot="props.summary" :restoring="restoring" @cancel="cancelConfirm" @confirm="performRestore" />
|
|
324
|
+
</div>
|
|
325
|
+
</template>
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed, onMounted, ref, watch } from "vue";
|
|
3
|
+
import { useI18n } from "vue-i18n";
|
|
4
|
+
import { formatRelativeTime, formatSmartTime } from "../../../utils/format/date";
|
|
5
|
+
import { fetchHistoryList, type SnapshotSummary } from "./api";
|
|
6
|
+
import HistoryDetail from "./HistoryDetail.vue";
|
|
7
|
+
|
|
8
|
+
const props = defineProps<{
|
|
9
|
+
slug: string;
|
|
10
|
+
/** Live page body + frontmatter, supplied by the parent so the
|
|
11
|
+
* detail view can diff against the current state without an
|
|
12
|
+
* extra fetch. */
|
|
13
|
+
currentBody: string;
|
|
14
|
+
currentMeta: Record<string, unknown>;
|
|
15
|
+
}>();
|
|
16
|
+
|
|
17
|
+
const emit = defineEmits<{
|
|
18
|
+
/** Bubbled up from the detail view after a successful restore.
|
|
19
|
+
* The parent (View.vue) handles the tab switch + success toast. */
|
|
20
|
+
restored: [];
|
|
21
|
+
}>();
|
|
22
|
+
|
|
23
|
+
const { t } = useI18n();
|
|
24
|
+
|
|
25
|
+
const loading = ref(true);
|
|
26
|
+
const fetchError = ref<string | null>(null);
|
|
27
|
+
const snapshots = ref<SnapshotSummary[]>([]);
|
|
28
|
+
/** Per Q15=B, this state persists across `Content` ↔ `History` tab
|
|
29
|
+
* switches as long as the slug doesn't change. The parent keeps
|
|
30
|
+
* the History tab mounted via `v-show`. */
|
|
31
|
+
const selectedStamp = ref<string | null>(null);
|
|
32
|
+
|
|
33
|
+
// Stale-response guard (codex iter-1 #946). A user who switches
|
|
34
|
+
// slugs faster than the network responds would otherwise see a
|
|
35
|
+
// late list arrive and overwrite the new slug's state. Each load
|
|
36
|
+
// bumps the counter; resolutions whose token has been superseded
|
|
37
|
+
// drop on the floor.
|
|
38
|
+
let loadToken = 0;
|
|
39
|
+
|
|
40
|
+
onMounted(async () => {
|
|
41
|
+
await loadList();
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
watch(
|
|
45
|
+
() => props.slug,
|
|
46
|
+
async (next, prev) => {
|
|
47
|
+
if (next === prev) return;
|
|
48
|
+
selectedStamp.value = null;
|
|
49
|
+
await loadList();
|
|
50
|
+
},
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
async function loadList(): Promise<void> {
|
|
54
|
+
const myToken = ++loadToken;
|
|
55
|
+
loading.value = true;
|
|
56
|
+
fetchError.value = null;
|
|
57
|
+
const result = await fetchHistoryList(props.slug);
|
|
58
|
+
if (myToken !== loadToken) return;
|
|
59
|
+
loading.value = false;
|
|
60
|
+
if (!result.ok) {
|
|
61
|
+
fetchError.value = result.error;
|
|
62
|
+
snapshots.value = [];
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
snapshots.value = result.data.snapshots;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const selectedIndex = computed(() => {
|
|
69
|
+
if (selectedStamp.value === null) return -1;
|
|
70
|
+
return snapshots.value.findIndex((entry) => entry.stamp === selectedStamp.value);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
const selectedSummary = computed<SnapshotSummary | null>(() => {
|
|
74
|
+
const idx = selectedIndex.value;
|
|
75
|
+
return idx === -1 ? null : snapshots.value[idx];
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
const previousSummary = computed<SnapshotSummary | null>(() => {
|
|
79
|
+
// List is newest-first; `previous` (older) is at index+1.
|
|
80
|
+
const idx = selectedIndex.value;
|
|
81
|
+
if (idx === -1) return null;
|
|
82
|
+
return snapshots.value[idx + 1] ?? null;
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
function selectStamp(stamp: string): void {
|
|
86
|
+
selectedStamp.value = stamp;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function clearSelection(): void {
|
|
90
|
+
selectedStamp.value = null;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function editorBadgeFor(editor: SnapshotSummary["editor"]): { label: string; className: string } {
|
|
94
|
+
if (editor === "llm") {
|
|
95
|
+
return { label: t("pluginWiki.history.editorBadgeLLM"), className: "bg-purple-50 text-purple-700" };
|
|
96
|
+
}
|
|
97
|
+
if (editor === "system") {
|
|
98
|
+
return { label: t("pluginWiki.history.editorBadgeSystem"), className: "bg-gray-100 text-gray-700" };
|
|
99
|
+
}
|
|
100
|
+
return { label: t("pluginWiki.history.editorBadgeUser"), className: "bg-blue-50 text-blue-700" };
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function onRestored(): void {
|
|
104
|
+
emit("restored");
|
|
105
|
+
// After restore we expect the parent to switch tabs and the new
|
|
106
|
+
// restore snapshot is now the newest entry. Refresh the list so
|
|
107
|
+
// returning to History reflects the new state.
|
|
108
|
+
void loadList();
|
|
109
|
+
selectedStamp.value = null;
|
|
110
|
+
}
|
|
111
|
+
</script>
|
|
112
|
+
|
|
113
|
+
<template>
|
|
114
|
+
<div class="flex-1 flex flex-col min-h-0" data-testid="wiki-history-tab">
|
|
115
|
+
<!-- Loading -->
|
|
116
|
+
<div v-if="loading" class="flex-1 flex items-center justify-center text-gray-400 text-sm">
|
|
117
|
+
<span class="material-icons animate-spin text-base mr-2">progress_activity</span>
|
|
118
|
+
{{ t("pluginWiki.history.loading") }}
|
|
119
|
+
</div>
|
|
120
|
+
|
|
121
|
+
<!-- Fetch error -->
|
|
122
|
+
<div v-else-if="fetchError" class="m-4 rounded border border-red-200 bg-red-50 px-3 py-2 text-sm text-red-700" data-testid="wiki-history-fetch-error">
|
|
123
|
+
{{ fetchError }}
|
|
124
|
+
</div>
|
|
125
|
+
|
|
126
|
+
<!-- Detail view (Q15=B: persists when user toggles tabs) -->
|
|
127
|
+
<HistoryDetail
|
|
128
|
+
v-else-if="selectedSummary !== null"
|
|
129
|
+
:slug="props.slug"
|
|
130
|
+
:summary="selectedSummary"
|
|
131
|
+
:previous-summary="previousSummary"
|
|
132
|
+
:current-body="props.currentBody"
|
|
133
|
+
:current-meta="props.currentMeta"
|
|
134
|
+
@back="clearSelection"
|
|
135
|
+
@restored="onRestored"
|
|
136
|
+
/>
|
|
137
|
+
|
|
138
|
+
<!-- Empty state -->
|
|
139
|
+
<div
|
|
140
|
+
v-else-if="snapshots.length === 0"
|
|
141
|
+
class="flex-1 flex items-center justify-center px-6 text-gray-400 text-sm text-center"
|
|
142
|
+
data-testid="wiki-history-empty"
|
|
143
|
+
>
|
|
144
|
+
<p>{{ t("pluginWiki.history.empty") }}</p>
|
|
145
|
+
</div>
|
|
146
|
+
|
|
147
|
+
<!-- List view -->
|
|
148
|
+
<div v-else class="flex-1 overflow-y-auto" data-testid="wiki-history-list">
|
|
149
|
+
<button
|
|
150
|
+
v-for="entry in snapshots"
|
|
151
|
+
:key="entry.stamp"
|
|
152
|
+
type="button"
|
|
153
|
+
class="w-full text-left px-4 py-2 border-b border-gray-100 hover:bg-blue-50 transition-colors flex items-baseline gap-3"
|
|
154
|
+
:data-testid="`wiki-history-row-${entry.stamp}`"
|
|
155
|
+
@click="selectStamp(entry.stamp)"
|
|
156
|
+
>
|
|
157
|
+
<span :class="['inline-flex items-center px-2 h-5 rounded text-xs font-medium shrink-0', editorBadgeFor(entry.editor).className]">
|
|
158
|
+
{{ editorBadgeFor(entry.editor).label }}
|
|
159
|
+
</span>
|
|
160
|
+
<span class="text-sm text-gray-700 shrink-0" :title="formatSmartTime(entry.ts)">
|
|
161
|
+
{{ formatRelativeTime(entry.ts) }}
|
|
162
|
+
</span>
|
|
163
|
+
<span v-if="entry.reason" class="text-sm text-gray-500 truncate">{{ ` — ${entry.reason}` }}</span>
|
|
164
|
+
</button>
|
|
165
|
+
</div>
|
|
166
|
+
</div>
|
|
167
|
+
</template>
|