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
|
@@ -1,35 +1,70 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
|
|
2
|
+
// Bell badge + popup, backed by the notifier engine (PR 4 of
|
|
3
|
+
// feat-encore). Active section on top, History below, single scroll,
|
|
4
|
+
// no tabs. Active rows use a bell icon (severity-colored) instead of
|
|
5
|
+
// a colored dot — at-a-glance "this is a notification" is more
|
|
6
|
+
// readable than a generic dot. History rows fall back to a faded
|
|
7
|
+
// dot so the row reads as past-tense.
|
|
8
|
+
//
|
|
9
|
+
// Body is displayed only as a native hover tooltip (`:title=`) — the
|
|
10
|
+
// row stays compact and the relative-time meta line is what the user
|
|
11
|
+
// scans for "when was this?".
|
|
12
|
+
//
|
|
13
|
+
// The dev-mode `NotifierDebugPopup` that ran in parallel during PR 3
|
|
14
|
+
// is gone — the production bell now serves both audiences.
|
|
15
|
+
// `NotificationToast.vue` is also gone; the worst-severity badge
|
|
16
|
+
// color (gray / amber / red) is the at-a-distance signal.
|
|
17
|
+
|
|
18
|
+
import { computed, onMounted, onUnmounted, ref, watch } from "vue";
|
|
3
19
|
import { useI18n } from "vue-i18n";
|
|
4
|
-
import {
|
|
20
|
+
import { useRouter } from "vue-router";
|
|
21
|
+
import { useNotifications, type NotifierEntry, type NotifierHistoryEntry } from "../composables/useNotifications";
|
|
5
22
|
import { formatRelativeTime } from "../utils/format/date";
|
|
6
|
-
import {
|
|
7
|
-
import
|
|
23
|
+
import type { NotificationI18n, NotificationKind, NotificationPriority } from "../types/notification";
|
|
24
|
+
import { isRecord } from "../utils/types";
|
|
8
25
|
|
|
9
26
|
const { t } = useI18n();
|
|
10
|
-
const
|
|
27
|
+
const router = useRouter();
|
|
28
|
+
|
|
29
|
+
const { entries, history, badgeCount, badgeColor, clear, cancel } = useNotifications();
|
|
30
|
+
|
|
31
|
+
const props = defineProps<{ forceClose?: boolean }>();
|
|
32
|
+
const emit = defineEmits<{ "update:open": [open: boolean] }>();
|
|
33
|
+
|
|
11
34
|
const open = ref(false);
|
|
12
35
|
const rootRef = ref<HTMLElement | null>(null);
|
|
13
36
|
|
|
37
|
+
// Bell mirrors the debug popup's "newest first" panel order — sort
|
|
38
|
+
// in reverse of the composable's oldest-first output.
|
|
39
|
+
const visibleEntries = computed(() => [...entries.value].reverse());
|
|
40
|
+
const visibleHistory = computed(() => history.value);
|
|
41
|
+
const fyiCount = computed(() => entries.value.filter((entry) => (entry.lifecycle ?? "fyi") === "fyi").length);
|
|
42
|
+
|
|
43
|
+
const badgeText = computed(() => (badgeCount.value > 99 ? "99+" : String(badgeCount.value)));
|
|
44
|
+
|
|
45
|
+
// Hover state on the bulk-clear button — every fyi row's hover-check
|
|
46
|
+
// icon renders while it's true, mirroring the debug popup's "this is
|
|
47
|
+
// what's about to get swept" affordance.
|
|
48
|
+
const hoveringClearAll = ref(false);
|
|
49
|
+
|
|
50
|
+
function close(): void {
|
|
51
|
+
open.value = false;
|
|
52
|
+
emit("update:open", false);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function toggle(): void {
|
|
56
|
+
open.value = !open.value;
|
|
57
|
+
emit("update:open", open.value);
|
|
58
|
+
}
|
|
59
|
+
|
|
14
60
|
function onDocumentClick(event: MouseEvent): void {
|
|
15
61
|
if (!open.value || !rootRef.value) return;
|
|
16
|
-
if (!rootRef.value.contains(event.target as Node))
|
|
17
|
-
close();
|
|
18
|
-
}
|
|
62
|
+
if (!rootRef.value.contains(event.target as Node)) close();
|
|
19
63
|
}
|
|
20
64
|
|
|
21
65
|
onMounted(() => document.addEventListener("mousedown", onDocumentClick));
|
|
22
66
|
onUnmounted(() => document.removeEventListener("mousedown", onDocumentClick));
|
|
23
67
|
|
|
24
|
-
const props = defineProps<{
|
|
25
|
-
forceClose?: boolean;
|
|
26
|
-
}>();
|
|
27
|
-
|
|
28
|
-
const emit = defineEmits<{
|
|
29
|
-
navigate: [action: NotificationPayload["action"]];
|
|
30
|
-
"update:open": [open: boolean];
|
|
31
|
-
}>();
|
|
32
|
-
|
|
33
68
|
watch(
|
|
34
69
|
() => props.forceClose,
|
|
35
70
|
(shouldClose) => {
|
|
@@ -37,52 +72,165 @@ watch(
|
|
|
37
72
|
},
|
|
38
73
|
);
|
|
39
74
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
75
|
+
// ── Legacy pluginData typing ────────────────────────────────
|
|
76
|
+
//
|
|
77
|
+
// `publishNotification()` stashes legacy fields under
|
|
78
|
+
// `pluginData.legacy = true`. The bell type-narrows here so it can
|
|
79
|
+
// preserve i18n localization for entries that came through the
|
|
80
|
+
// wrapper. Future direct callers of `notifier.publish()` don't set
|
|
81
|
+
// this shape; their entries fall back to the engine-level
|
|
82
|
+
// `entry.title` / `entry.body`.
|
|
83
|
+
|
|
84
|
+
interface LegacyPluginDataShape {
|
|
85
|
+
legacy: true;
|
|
86
|
+
legacyId: string;
|
|
87
|
+
kind: NotificationKind;
|
|
88
|
+
priority: NotificationPriority;
|
|
89
|
+
i18n?: NotificationI18n;
|
|
47
90
|
}
|
|
48
91
|
|
|
49
|
-
function
|
|
50
|
-
|
|
51
|
-
|
|
92
|
+
function asLegacy(entry: { pluginData?: unknown }): LegacyPluginDataShape | null {
|
|
93
|
+
const data = entry.pluginData;
|
|
94
|
+
if (!isRecord(data)) return null;
|
|
95
|
+
if (data.legacy !== true) return null;
|
|
96
|
+
if (typeof data.legacyId !== "string") return null;
|
|
97
|
+
if (typeof data.kind !== "string") return null;
|
|
98
|
+
if (typeof data.priority !== "string") return null;
|
|
99
|
+
return data as unknown as LegacyPluginDataShape;
|
|
52
100
|
}
|
|
53
101
|
|
|
54
|
-
function
|
|
55
|
-
|
|
102
|
+
function localizeTitle(entry: NotifierEntry | NotifierHistoryEntry): string {
|
|
103
|
+
const legacy = asLegacy(entry);
|
|
104
|
+
if (legacy?.i18n) return t(legacy.i18n.titleKey);
|
|
105
|
+
return entry.title;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function localizeBody(entry: NotifierEntry | NotifierHistoryEntry): string | undefined {
|
|
109
|
+
const legacy = asLegacy(entry);
|
|
110
|
+
if (legacy?.i18n?.bodyKey) return t(legacy.i18n.bodyKey, legacy.i18n.bodyParams ?? {});
|
|
111
|
+
return entry.body;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function severityIconColor(severity: NotifierEntry["severity"]): string {
|
|
115
|
+
switch (severity) {
|
|
116
|
+
case "urgent":
|
|
117
|
+
return "text-red-500";
|
|
118
|
+
case "nudge":
|
|
119
|
+
return "text-amber-500";
|
|
120
|
+
case "info":
|
|
121
|
+
default:
|
|
122
|
+
return "text-gray-300";
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// History rows keep the original colored-dot encoding (faded) — the
|
|
127
|
+
// bell icon is reserved for Active so the eye distinguishes "still
|
|
128
|
+
// open" from "already closed" at a glance.
|
|
129
|
+
function severityDotClassForHistory(severity: NotifierEntry["severity"]): string {
|
|
130
|
+
switch (severity) {
|
|
131
|
+
case "urgent":
|
|
132
|
+
return "bg-red-500";
|
|
133
|
+
case "nudge":
|
|
134
|
+
return "bg-amber-500";
|
|
135
|
+
case "info":
|
|
136
|
+
default:
|
|
137
|
+
return "bg-gray-300";
|
|
138
|
+
}
|
|
56
139
|
}
|
|
57
140
|
|
|
58
141
|
function formatTime(iso: string): string {
|
|
59
142
|
return formatRelativeTime(iso);
|
|
60
143
|
}
|
|
61
144
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
145
|
+
/** Strip the leading `@scope/` from a scoped npm package name for
|
|
146
|
+
* display. The `@mulmoclaude/` prefix on plugin pluginPkgs is noise
|
|
147
|
+
* in a meta line — what readers want is the suffix. Unscoped names
|
|
148
|
+
* (the legacy `host` / `todo` / `agent` / … pluginPkgs that the
|
|
149
|
+
* wrapper assigns) pass through unchanged. */
|
|
150
|
+
function shortPkg(pluginPkg: string): string {
|
|
151
|
+
return pluginPkg.startsWith("@") ? pluginPkg.split("/").slice(1).join("/") || pluginPkg : pluginPkg;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/** Splice `notificationId=<id>` into the navigateTarget so the landing
|
|
155
|
+
* page (action-lifecycle plugin views, e.g. the debug page's
|
|
156
|
+
* `?mode=auto-clear` / `?mode=manual-clear` modes) can identify which
|
|
157
|
+
* entry triggered the navigation and call `clear(id)` accordingly.
|
|
158
|
+
* Harmless on legacy fyi targets that don't read the query.
|
|
159
|
+
*
|
|
160
|
+
* Inserts before any `#hash` so the fragment stays at the end —
|
|
161
|
+
* `?a=1#frag` becomes `?a=1¬ificationId=…#frag`, not
|
|
162
|
+
* `?a=1#frag¬ificationId=…`. */
|
|
163
|
+
function appendNotificationId(target: string, entryId: string): string {
|
|
164
|
+
const hashIdx = target.indexOf("#");
|
|
165
|
+
const beforeHash = hashIdx >= 0 ? target.slice(0, hashIdx) : target;
|
|
166
|
+
const hash = hashIdx >= 0 ? target.slice(hashIdx) : "";
|
|
167
|
+
const separator = beforeHash.includes("?") ? "&" : "?";
|
|
168
|
+
return `${beforeHash}${separator}notificationId=${encodeURIComponent(entryId)}${hash}`;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
async function navigateAndClose(target: string, entryId: string): Promise<void> {
|
|
172
|
+
close();
|
|
173
|
+
await router.push(appendNotificationId(target, entryId)).catch(() => {});
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/** Lifecycle-correct primary action for an Active row. The keyboard
|
|
177
|
+
* Enter/Space handler routes through here so keyboard users get a
|
|
178
|
+
* single, predictable activation regardless of where focus is —
|
|
179
|
+
* separate from the mouse two-tier UX that distinguishes body click
|
|
180
|
+
* vs row-padding click. fyi: navigate (if target) then clear.
|
|
181
|
+
* action: navigate only (plugin owns the clear). */
|
|
182
|
+
function performPrimaryAction(entry: NotifierEntry): void {
|
|
183
|
+
if (entry.navigateTarget) void navigateAndClose(entry.navigateTarget, entry.id);
|
|
184
|
+
if ((entry.lifecycle ?? "fyi") === "fyi") void clear(entry.id);
|
|
71
185
|
}
|
|
72
186
|
|
|
73
|
-
|
|
187
|
+
// True when activating the row (click, Enter, Space) does anything
|
|
188
|
+
// useful — either clears a fyi notification or navigates to the
|
|
189
|
+
// action's target. Action notifications without a `navigateTarget`
|
|
190
|
+
// fall through both branches, so their row is not a button and
|
|
191
|
+
// should not advertise as one to assistive technology.
|
|
192
|
+
function hasPrimaryAction(entry: NotifierEntry): boolean {
|
|
193
|
+
return Boolean(entry.navigateTarget) || (entry.lifecycle ?? "fyi") === "fyi";
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Body click on an Active row. Stop propagation so the outer <li>'s
|
|
197
|
+
// fyi-clear handler doesn't double-fire when a fyi click already
|
|
198
|
+
// lands here (matches the debug popup's two-layer click handling).
|
|
199
|
+
function onActiveRowBodyClick(entry: NotifierEntry, event: MouseEvent): void {
|
|
200
|
+
event.stopPropagation();
|
|
201
|
+
performPrimaryAction(entry);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Outer-row click — fyi only. Action rows must use the body div or
|
|
205
|
+
// the trailing × so the user can't accidentally cancel by clicking
|
|
206
|
+
// padding. Keyboard activation goes through `performPrimaryAction`
|
|
207
|
+
// instead, which works for both lifecycles.
|
|
208
|
+
function onActiveRowClick(entry: NotifierEntry): void {
|
|
209
|
+
const lifecycle = entry.lifecycle ?? "fyi";
|
|
210
|
+
if (lifecycle !== "fyi") return;
|
|
211
|
+
performPrimaryAction(entry);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
async function handleDismiss(event: Event, entry: NotifierEntry): Promise<void> {
|
|
74
215
|
event.stopPropagation();
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
216
|
+
const lifecycle = entry.lifecycle ?? "fyi";
|
|
217
|
+
if (lifecycle === "fyi") await clear(entry.id);
|
|
218
|
+
else await cancel(entry.id);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
async function handleHistoryClick(entry: NotifierHistoryEntry): Promise<void> {
|
|
222
|
+
if (!entry.navigateTarget) return;
|
|
223
|
+
await navigateAndClose(entry.navigateTarget, entry.id);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
async function clearAllFyi(): Promise<void> {
|
|
227
|
+
const ids = entries.value.filter((entry) => (entry.lifecycle ?? "fyi") === "fyi").map((entry) => entry.id);
|
|
228
|
+
for (const entryId of ids) await clear(entryId);
|
|
80
229
|
}
|
|
81
230
|
</script>
|
|
82
231
|
|
|
83
232
|
<template>
|
|
84
233
|
<div ref="rootRef" class="relative">
|
|
85
|
-
<!-- Bell button -->
|
|
86
234
|
<button
|
|
87
235
|
class="relative h-8 w-8 flex items-center justify-center rounded text-gray-400 hover:text-gray-700"
|
|
88
236
|
data-testid="notification-bell"
|
|
@@ -91,66 +239,145 @@ function handleDismiss(event: Event, notificationId: string): void {
|
|
|
91
239
|
>
|
|
92
240
|
<span class="material-icons">notifications</span>
|
|
93
241
|
<span
|
|
94
|
-
v-if="
|
|
95
|
-
class="
|
|
242
|
+
v-if="badgeCount > 0"
|
|
243
|
+
:class="[
|
|
244
|
+
'absolute -top-1.5 -right-1.5 min-w-[1rem] h-4 px-0.5 text-white text-[10px] font-bold rounded-full flex items-center justify-center leading-none',
|
|
245
|
+
badgeColor,
|
|
246
|
+
]"
|
|
96
247
|
data-testid="notification-badge"
|
|
97
248
|
>
|
|
98
|
-
{{
|
|
249
|
+
{{ badgeText }}
|
|
99
250
|
</span>
|
|
100
251
|
</button>
|
|
101
252
|
|
|
102
|
-
<!-- Dropdown panel -->
|
|
103
253
|
<div
|
|
104
254
|
v-if="open"
|
|
105
|
-
class="absolute left-0 top-full mt-1 w-
|
|
255
|
+
class="absolute left-0 top-full mt-1 w-96 max-h-[80vh] bg-white border border-gray-200 rounded-lg shadow-lg z-50 flex flex-col text-xs overflow-hidden"
|
|
106
256
|
data-testid="notification-panel"
|
|
107
257
|
>
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
<span class="text-sm font-semibold text-gray-700">{{ t("notificationBell.notifications") }}</span>
|
|
111
|
-
<button class="text-xs text-blue-500 hover:text-blue-700" data-testid="notification-mark-all-read" @click="markAllRead">
|
|
112
|
-
{{ t("notificationBell.markAllRead") }}
|
|
113
|
-
</button>
|
|
258
|
+
<div class="flex items-center gap-2 px-3 py-2 border-b border-gray-100">
|
|
259
|
+
<span class="font-semibold text-gray-700">{{ t("notificationBell.notifications") }}</span>
|
|
114
260
|
</div>
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
:data-testid="`notification-item-${n.id}`"
|
|
129
|
-
:data-unread="isRead(n.id) ? 'false' : 'true'"
|
|
130
|
-
:aria-label="n.title"
|
|
131
|
-
@click="handleClick(n)"
|
|
132
|
-
@keydown.enter.prevent.self="(e) => !e.repeat && handleClick(n)"
|
|
133
|
-
@keydown.space.prevent.self="(e) => !e.repeat && handleClick(n)"
|
|
134
|
-
>
|
|
135
|
-
<!-- Unread dot — small leading marker so the user can scan
|
|
136
|
-
the panel for what's new. Hidden once `markRead` fires. -->
|
|
137
|
-
<span class="w-2 h-2 mt-2 shrink-0 rounded-full" :class="isRead(n.id) ? 'bg-transparent' : 'bg-blue-500'" aria-hidden="true" />
|
|
138
|
-
<span class="material-icons text-lg mt-0.5 shrink-0" :class="n.priority === NOTIFICATION_PRIORITIES.high ? 'text-red-500' : 'text-gray-400'">
|
|
139
|
-
{{ iconName(n) }}
|
|
140
|
-
</span>
|
|
141
|
-
<div class="flex-1 min-w-0">
|
|
142
|
-
<p class="text-sm truncate" :class="isRead(n.id) ? 'text-gray-600 font-normal' : 'text-gray-900 font-semibold'">{{ n.title }}</p>
|
|
143
|
-
<p v-if="n.body" class="text-xs text-gray-500 truncate mt-0.5">
|
|
144
|
-
{{ n.body }}
|
|
145
|
-
</p>
|
|
146
|
-
<p class="text-xs text-gray-400 mt-0.5">
|
|
147
|
-
{{ formatTime(n.firedAt) }}
|
|
148
|
-
</p>
|
|
149
|
-
</div>
|
|
150
|
-
<button class="text-gray-300 hover:text-gray-500 shrink-0 mt-0.5" :aria-label="t('notificationBell.dismiss')" @click="handleDismiss($event, n.id)">
|
|
151
|
-
<span class="material-icons text-sm">close</span>
|
|
261
|
+
<div class="flex-1 overflow-y-auto">
|
|
262
|
+
<div class="flex items-center px-3 py-1.5 border-b border-gray-100 bg-gray-50">
|
|
263
|
+
<span class="text-gray-500 font-medium">{{ t("notificationBell.activeSection") }} ({{ visibleEntries.length }})</span>
|
|
264
|
+
<button
|
|
265
|
+
v-if="fyiCount > 0"
|
|
266
|
+
type="button"
|
|
267
|
+
class="ml-auto text-gray-500 font-medium hover:text-green-600"
|
|
268
|
+
data-testid="notification-clear-all"
|
|
269
|
+
@click="clearAllFyi"
|
|
270
|
+
@mouseenter="hoveringClearAll = true"
|
|
271
|
+
@mouseleave="hoveringClearAll = false"
|
|
272
|
+
>
|
|
273
|
+
{{ t("notificationBell.clearAll") }}
|
|
152
274
|
</button>
|
|
153
275
|
</div>
|
|
276
|
+
<p v-if="visibleEntries.length === 0" class="px-3 py-3 text-gray-400 italic" data-testid="notification-empty-active">
|
|
277
|
+
{{ t("notificationBell.noActive") }}
|
|
278
|
+
</p>
|
|
279
|
+
<ul v-else class="divide-y divide-gray-100">
|
|
280
|
+
<li
|
|
281
|
+
v-for="entry in visibleEntries"
|
|
282
|
+
:key="entry.id"
|
|
283
|
+
:data-testid="`notification-item-${entry.id}`"
|
|
284
|
+
:data-lifecycle="entry.lifecycle ?? 'fyi'"
|
|
285
|
+
:role="hasPrimaryAction(entry) ? 'button' : undefined"
|
|
286
|
+
:tabindex="hasPrimaryAction(entry) ? 0 : undefined"
|
|
287
|
+
:aria-label="hasPrimaryAction(entry) ? localizeTitle(entry) : undefined"
|
|
288
|
+
:class="['px-3 py-2 group focus:bg-gray-100 focus:outline-none', (entry.lifecycle ?? 'fyi') === 'fyi' ? 'cursor-pointer hover:bg-gray-50' : '']"
|
|
289
|
+
@click="onActiveRowClick(entry)"
|
|
290
|
+
@keydown.enter.prevent.self="(e) => hasPrimaryAction(entry) && !e.repeat && performPrimaryAction(entry)"
|
|
291
|
+
@keydown.space.prevent.self="(e) => hasPrimaryAction(entry) && !e.repeat && performPrimaryAction(entry)"
|
|
292
|
+
>
|
|
293
|
+
<div class="flex items-start gap-2">
|
|
294
|
+
<span
|
|
295
|
+
:class="['material-icons text-sm shrink-0 leading-none mt-0.5', severityIconColor(entry.severity)]"
|
|
296
|
+
:title="entry.severity"
|
|
297
|
+
aria-hidden="true"
|
|
298
|
+
>
|
|
299
|
+
notifications
|
|
300
|
+
</span>
|
|
301
|
+
<div
|
|
302
|
+
:class="[
|
|
303
|
+
'flex-1 min-w-0',
|
|
304
|
+
entry.lifecycle === 'action' && entry.navigateTarget
|
|
305
|
+
? 'cursor-pointer hover:underline'
|
|
306
|
+
: (entry.lifecycle ?? 'fyi') === 'fyi'
|
|
307
|
+
? 'cursor-pointer'
|
|
308
|
+
: '',
|
|
309
|
+
]"
|
|
310
|
+
:title="localizeBody(entry) || undefined"
|
|
311
|
+
@click="onActiveRowBodyClick(entry, $event)"
|
|
312
|
+
>
|
|
313
|
+
<div class="flex items-baseline gap-2">
|
|
314
|
+
<span class="font-medium text-gray-800 truncate">{{ localizeTitle(entry) }}</span>
|
|
315
|
+
<span v-if="entry.lifecycle" class="text-[10px] uppercase tracking-wide text-gray-400 shrink-0">{{ entry.lifecycle }}</span>
|
|
316
|
+
</div>
|
|
317
|
+
<div class="text-gray-400 mt-0.5 font-mono text-[10px]">{{ formatTime(entry.createdAt) }} · {{ shortPkg(entry.pluginPkg) }}</div>
|
|
318
|
+
</div>
|
|
319
|
+
<button
|
|
320
|
+
v-if="entry.lifecycle === 'action'"
|
|
321
|
+
type="button"
|
|
322
|
+
class="text-gray-400 hover:text-red-500 shrink-0"
|
|
323
|
+
:title="t('notificationBell.cancel')"
|
|
324
|
+
:aria-label="t('notificationBell.cancel')"
|
|
325
|
+
data-testid="notification-dismiss"
|
|
326
|
+
@click="handleDismiss($event, entry)"
|
|
327
|
+
>
|
|
328
|
+
<span class="material-icons text-sm">close</span>
|
|
329
|
+
</button>
|
|
330
|
+
<span
|
|
331
|
+
v-else
|
|
332
|
+
:class="['text-green-500 shrink-0 transition-opacity', hoveringClearAll ? 'opacity-100' : 'opacity-0 group-hover:opacity-100']"
|
|
333
|
+
aria-hidden="true"
|
|
334
|
+
data-testid="notification-fyi-hover-check"
|
|
335
|
+
>
|
|
336
|
+
<span class="material-icons text-sm">check</span>
|
|
337
|
+
</span>
|
|
338
|
+
</div>
|
|
339
|
+
</li>
|
|
340
|
+
</ul>
|
|
341
|
+
<div class="px-3 py-1.5 text-gray-500 font-medium border-y border-gray-100 bg-gray-50">
|
|
342
|
+
{{ t("notificationBell.historySection") }} ({{ visibleHistory.length }})
|
|
343
|
+
</div>
|
|
344
|
+
<p v-if="visibleHistory.length === 0" class="px-3 py-3 text-gray-400 italic" data-testid="notification-empty-history">
|
|
345
|
+
{{ t("notificationBell.noHistory") }}
|
|
346
|
+
</p>
|
|
347
|
+
<ul v-else class="divide-y divide-gray-100">
|
|
348
|
+
<li
|
|
349
|
+
v-for="entry in visibleHistory"
|
|
350
|
+
:key="`${entry.id}-${entry.terminalAt}`"
|
|
351
|
+
:data-testid="`notification-history-${entry.id}`"
|
|
352
|
+
:role="entry.navigateTarget ? 'button' : undefined"
|
|
353
|
+
:tabindex="entry.navigateTarget ? 0 : undefined"
|
|
354
|
+
:aria-label="entry.navigateTarget ? localizeTitle(entry) : undefined"
|
|
355
|
+
:class="['px-3 py-2 focus:bg-gray-100 focus:outline-none', entry.navigateTarget ? 'cursor-pointer hover:bg-gray-50' : '']"
|
|
356
|
+
@click="entry.navigateTarget && handleHistoryClick(entry)"
|
|
357
|
+
@keydown.enter.prevent.self="(e) => entry.navigateTarget && !e.repeat && handleHistoryClick(entry)"
|
|
358
|
+
@keydown.space.prevent.self="(e) => entry.navigateTarget && !e.repeat && handleHistoryClick(entry)"
|
|
359
|
+
>
|
|
360
|
+
<div class="flex items-start gap-2">
|
|
361
|
+
<!-- eslint-disable @intlify/vue-i18n/no-raw-text --
|
|
362
|
+
Symbolic glyph (✓ / ✗) — language-neutral terminal-state
|
|
363
|
+
marker, identical for every locale. Not a translatable
|
|
364
|
+
string. -->
|
|
365
|
+
<span class="mt-0.5 shrink-0 font-bold text-gray-400">
|
|
366
|
+
{{ entry.terminalType === "cleared" ? "✓" : "✗" }}
|
|
367
|
+
</span>
|
|
368
|
+
<!-- eslint-enable @intlify/vue-i18n/no-raw-text -->
|
|
369
|
+
<span :class="['mt-1 inline-block w-2 h-2 rounded-full shrink-0 opacity-30', severityDotClassForHistory(entry.severity)]" aria-hidden="true" />
|
|
370
|
+
<div :class="['flex-1 min-w-0', entry.navigateTarget ? 'hover:underline' : '']">
|
|
371
|
+
<div class="flex items-baseline gap-2">
|
|
372
|
+
<span class="text-gray-700 truncate">{{ localizeTitle(entry) }}</span>
|
|
373
|
+
</div>
|
|
374
|
+
<div class="text-gray-400 mt-0.5 font-mono text-[10px]">
|
|
375
|
+
{{ formatTime(entry.terminalAt) }} · {{ entry.terminalType }} · {{ shortPkg(entry.pluginPkg) }}
|
|
376
|
+
</div>
|
|
377
|
+
</div>
|
|
378
|
+
</div>
|
|
379
|
+
</li>
|
|
380
|
+
</ul>
|
|
154
381
|
</div>
|
|
155
382
|
</div>
|
|
156
383
|
</div>
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div class="border-t border-gray-200 shrink-0 bg-gray-50">
|
|
3
3
|
<SuggestionsPanel
|
|
4
|
-
v-if="suggestions && suggestions.length > 0"
|
|
5
4
|
v-model:expanded="suggestionsExpanded"
|
|
6
5
|
:queries="suggestions"
|
|
6
|
+
:trigger-ref="suggestionsBtnRef"
|
|
7
7
|
@send="onSuggestionSend"
|
|
8
8
|
@edit="onSuggestionEdit"
|
|
9
9
|
/>
|
|
@@ -22,12 +22,12 @@
|
|
|
22
22
|
/>
|
|
23
23
|
<div class="flex flex-col gap-1 shrink-0">
|
|
24
24
|
<button
|
|
25
|
-
|
|
25
|
+
ref="suggestionsBtnRef"
|
|
26
26
|
:data-testid="`${testIdPrefix}-suggestions`"
|
|
27
27
|
class="rounded w-8 h-8 flex items-center justify-center"
|
|
28
28
|
:class="suggestionsExpanded ? 'text-blue-600 bg-blue-50' : 'text-gray-400 hover:text-gray-600'"
|
|
29
|
-
:title="t('suggestionsPanel.
|
|
30
|
-
:aria-label="t('suggestionsPanel.
|
|
29
|
+
:title="t('suggestionsPanel.tooltip')"
|
|
30
|
+
:aria-label="t('suggestionsPanel.tooltip')"
|
|
31
31
|
@click="suggestionsExpanded = !suggestionsExpanded"
|
|
32
32
|
>
|
|
33
33
|
<span class="material-icons text-base leading-none">lightbulb</span>
|
|
@@ -71,6 +71,7 @@ const appApi = useAppApi();
|
|
|
71
71
|
const draft = ref("");
|
|
72
72
|
const textareaRef = ref<HTMLTextAreaElement | null>(null);
|
|
73
73
|
const suggestionsExpanded = ref(false);
|
|
74
|
+
const suggestionsBtnRef = ref<HTMLButtonElement | null>(null);
|
|
74
75
|
|
|
75
76
|
const canSend = computed(() => !props.disabled && (props.allowEmpty || draft.value.trim().length > 0));
|
|
76
77
|
|
|
@@ -1,26 +1,26 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div ref="rootRef" class="inline-flex w-fit border border-gray-300 rounded overflow-hidden text-xs" data-testid="plugin-launcher">
|
|
3
|
-
<template v-for="(target, idx) in
|
|
3
|
+
<template v-for="(target, idx) in visibleTargets" :key="target.key">
|
|
4
4
|
<!-- Visual separator between data plugins and management plugins -->
|
|
5
|
-
<div v-if="idx ===
|
|
5
|
+
<div v-if="idx === separatorAfterIndex" class="w-px bg-gray-300 my-0.5" />
|
|
6
6
|
<button
|
|
7
7
|
:class="[
|
|
8
8
|
'h-8 px-2.5 flex items-center gap-1 border-r border-gray-200 last:border-r-0 transition-colors',
|
|
9
9
|
isActive(target) ? 'bg-blue-50 text-blue-600 font-medium' : 'bg-white text-gray-600 hover:bg-gray-50',
|
|
10
10
|
]"
|
|
11
|
-
:title="t(`pluginLauncher.${target.key}.title`)"
|
|
11
|
+
:title="target.literalTitle ?? t(`pluginLauncher.${target.key}.title`)"
|
|
12
12
|
:data-testid="`plugin-launcher-${target.key}`"
|
|
13
13
|
@click="emit('navigate', target)"
|
|
14
14
|
>
|
|
15
15
|
<span class="material-icons text-sm">{{ target.icon }}</span>
|
|
16
|
-
<span v-if="!compact">{{ t(`pluginLauncher.${target.key}.label`) }}</span>
|
|
16
|
+
<span v-if="!compact">{{ target.literalLabel ?? t(`pluginLauncher.${target.key}.label`) }}</span>
|
|
17
17
|
</button>
|
|
18
18
|
</template>
|
|
19
19
|
</div>
|
|
20
20
|
</template>
|
|
21
21
|
|
|
22
22
|
<script setup lang="ts">
|
|
23
|
-
import { onBeforeUnmount, onMounted, ref } from "vue";
|
|
23
|
+
import { computed, onBeforeUnmount, onMounted, ref } from "vue";
|
|
24
24
|
import { useI18n } from "vue-i18n";
|
|
25
25
|
|
|
26
26
|
const { t } = useI18n();
|
|
@@ -43,10 +43,21 @@ export type PluginLauncherKind = "view"; // Switch the canvas to a dedicated vie
|
|
|
43
43
|
// strings out of this file avoids duplication across locales.
|
|
44
44
|
export interface PluginLauncherTarget {
|
|
45
45
|
/** Stable key for testid + dispatch in App.vue. */
|
|
46
|
-
key: "todos" | "calendar" | "automations" | "wiki" | "sources" | "news" | "skills" | "roles" | "files";
|
|
46
|
+
key: "todos" | "calendar" | "automations" | "wiki" | "sources" | "news" | "skills" | "roles" | "files" | "debug";
|
|
47
47
|
kind: PluginLauncherKind;
|
|
48
48
|
/** Material-icons glyph. */
|
|
49
49
|
icon: string;
|
|
50
|
+
/** When true, only visible if `VITE_DEV_MODE=1`. The corresponding
|
|
51
|
+
* page itself is still reachable via direct URL (`/debug`) — only
|
|
52
|
+
* the launcher button is gated. */
|
|
53
|
+
devOnly?: boolean;
|
|
54
|
+
/** Literal label / tooltip used in place of the i18n lookup. Set on
|
|
55
|
+
* dev-only targets so the host's 8-locale bundle doesn't carry
|
|
56
|
+
* strings that only English-speaking developers ever see. When
|
|
57
|
+
* unset (the production case), label/title come from
|
|
58
|
+
* `pluginLauncher.<key>.{label,title}` in `src/lang/*.ts`. */
|
|
59
|
+
literalLabel?: string;
|
|
60
|
+
literalTitle?: string;
|
|
50
61
|
}
|
|
51
62
|
|
|
52
63
|
const TARGETS: PluginLauncherTarget[] = [
|
|
@@ -67,6 +78,13 @@ const TARGETS: PluginLauncherTarget[] = [
|
|
|
67
78
|
{ key: "skills", kind: "view", icon: "psychology" },
|
|
68
79
|
{ key: "roles", kind: "view", icon: "manage_accounts" },
|
|
69
80
|
{ key: "files", kind: "view", icon: "folder" },
|
|
81
|
+
// ─── Dev-only ───
|
|
82
|
+
// Encore plan PR 1 follow-up. Hidden in production builds; the
|
|
83
|
+
// /debug route stays reachable by typing the URL even with the
|
|
84
|
+
// button hidden. Owned by `@mulmoclaude/debug-plugin`. Literal
|
|
85
|
+
// label/title — the debug surface is dev-only, so we deliberately
|
|
86
|
+
// keep the strings out of the 8-locale i18n bundle.
|
|
87
|
+
{ key: "debug", kind: "view", icon: "bug_report", devOnly: true, literalLabel: "Debug", literalTitle: "Open debug playground (dev mode only)" },
|
|
70
88
|
];
|
|
71
89
|
|
|
72
90
|
// Index AFTER which the visual separator is inserted (between data
|
|
@@ -75,6 +93,24 @@ const TARGETS: PluginLauncherTarget[] = [
|
|
|
75
93
|
// 0-5), so the divider renders before index 6 (skills).
|
|
76
94
|
const SEPARATOR_AFTER_INDEX = 6;
|
|
77
95
|
|
|
96
|
+
// Dev-mode flag — set `VITE_DEV_MODE=1` in `.env`. Anything else
|
|
97
|
+
// (including unset) hides any target with `devOnly: true`.
|
|
98
|
+
const DEV_MODE = import.meta.env.VITE_DEV_MODE === "1";
|
|
99
|
+
|
|
100
|
+
// Targets that should render given the current dev-mode flag.
|
|
101
|
+
const visibleTargets = computed(() => TARGETS.filter((target) => !target.devOnly || DEV_MODE));
|
|
102
|
+
|
|
103
|
+
// Recompute the separator index after the dev-only filter — without
|
|
104
|
+
// this, hiding a dev-only target before the separator would shift the
|
|
105
|
+
// divider one slot to the left. Today the only dev-only target sits
|
|
106
|
+
// at the end, so this matches the static constant; the computed keeps
|
|
107
|
+
// future entries safe.
|
|
108
|
+
const separatorAfterIndex = computed(() => {
|
|
109
|
+
const fullIndexOfSeparator = SEPARATOR_AFTER_INDEX;
|
|
110
|
+
const hiddenBefore = TARGETS.slice(0, fullIndexOfSeparator).filter((target) => target.devOnly && !DEV_MODE).length;
|
|
111
|
+
return fullIndexOfSeparator - hiddenBefore;
|
|
112
|
+
});
|
|
113
|
+
|
|
78
114
|
function isActive(target: PluginLauncherTarget): boolean {
|
|
79
115
|
return props.activeViewMode === target.key;
|
|
80
116
|
}
|