mulmoclaude 0.1.2 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (251) hide show
  1. package/bin/mulmoclaude.js +7 -24
  2. package/client/assets/html2canvas-Cx501zZr-Cv5snK9D.js +5 -0
  3. package/client/assets/index-CubzmCVK.css +2 -0
  4. package/client/assets/{index-D8rhwXLq.js → index-DtcyExH9.js} +80 -61
  5. package/client/assets/{index.es-D4YyL_Dg-BfRHLTZV.js → index.es-D4YyL_Dg-DnizuhIY.js} +5 -5
  6. package/client/index.html +2 -4
  7. package/package.json +13 -13
  8. package/server/agent/attachmentConverter.ts +2 -2
  9. package/server/agent/config.ts +12 -12
  10. package/server/agent/index.ts +9 -3
  11. package/server/agent/mcp-server.ts +19 -19
  12. package/server/agent/mcp-tools/index.ts +6 -6
  13. package/server/agent/mcp-tools/x.ts +7 -6
  14. package/server/agent/prompt.ts +195 -29
  15. package/server/agent/resumeFailover.ts +5 -5
  16. package/server/agent/sandboxMounts.ts +10 -10
  17. package/server/agent/stream.ts +4 -4
  18. package/server/api/auth/bearerAuth.ts +3 -3
  19. package/server/api/auth/token.ts +2 -2
  20. package/server/api/routes/agent.ts +21 -3
  21. package/server/api/routes/config.ts +1 -1
  22. package/server/api/routes/files.ts +22 -21
  23. package/server/api/routes/html.ts +2 -2
  24. package/server/api/routes/image.ts +7 -7
  25. package/server/api/routes/mulmo-script.ts +33 -31
  26. package/server/api/routes/pdf.ts +2 -2
  27. package/server/api/routes/plugins.ts +16 -6
  28. package/server/api/routes/roles.ts +2 -2
  29. package/server/api/routes/scheduler.ts +14 -12
  30. package/server/api/routes/schedulerHandlers.ts +12 -12
  31. package/server/api/routes/schedulerTasks.ts +19 -17
  32. package/server/api/routes/sessions.ts +26 -26
  33. package/server/api/routes/sessionsCursor.ts +4 -4
  34. package/server/api/routes/skills.ts +5 -5
  35. package/server/api/routes/sources.ts +3 -3
  36. package/server/api/routes/todosColumnsHandlers.ts +30 -30
  37. package/server/api/routes/todosHandlers.ts +1 -1
  38. package/server/api/routes/todosItemsHandlers.ts +14 -14
  39. package/server/api/routes/wiki.ts +36 -22
  40. package/server/api/sandboxStatus.ts +1 -1
  41. package/server/events/notifications.ts +6 -6
  42. package/server/events/pub-sub/index.ts +3 -3
  43. package/server/events/relay-client.ts +17 -16
  44. package/server/events/scheduler-adapter.ts +20 -20
  45. package/server/events/session-store/index.ts +10 -10
  46. package/server/events/task-manager/index.ts +7 -7
  47. package/server/index.ts +59 -65
  48. package/server/system/config.ts +5 -5
  49. package/server/system/credentials.ts +7 -5
  50. package/server/system/env.ts +5 -5
  51. package/server/utils/date.ts +18 -18
  52. package/server/utils/files/atomic.ts +16 -16
  53. package/server/utils/files/html-io.ts +5 -5
  54. package/server/utils/files/image-store.ts +19 -8
  55. package/server/utils/files/journal-io.ts +4 -4
  56. package/server/utils/files/json.ts +5 -5
  57. package/server/utils/files/markdown-store.ts +4 -4
  58. package/server/utils/files/naming.ts +2 -2
  59. package/server/utils/files/reference-dirs-io.ts +3 -3
  60. package/server/utils/files/roles-io.ts +12 -12
  61. package/server/utils/files/safe.ts +14 -14
  62. package/server/utils/files/scheduler-io.ts +5 -5
  63. package/server/utils/files/scheduler-overrides-io.ts +2 -2
  64. package/server/utils/files/session-io.ts +35 -35
  65. package/server/utils/files/spreadsheet-store.ts +7 -7
  66. package/server/utils/files/todos-io.ts +9 -9
  67. package/server/utils/files/user-tasks-io.ts +5 -5
  68. package/server/utils/files/workspace-io.ts +12 -12
  69. package/server/utils/gemini.ts +2 -2
  70. package/server/utils/gitignore.ts +9 -9
  71. package/server/utils/json.ts +5 -5
  72. package/server/utils/logBackgroundError.ts +12 -3
  73. package/server/utils/markdown.ts +5 -5
  74. package/server/utils/port.d.mts +6 -0
  75. package/server/utils/port.mjs +48 -0
  76. package/server/utils/request.ts +12 -6
  77. package/server/utils/spawn.ts +1 -1
  78. package/server/utils/types.ts +2 -2
  79. package/server/workspace/chat-index/indexer.ts +15 -15
  80. package/server/workspace/chat-index/summarizer.ts +4 -4
  81. package/server/workspace/custom-dirs.ts +16 -16
  82. package/server/workspace/journal/archivist.ts +35 -35
  83. package/server/workspace/journal/dailyPass.ts +31 -28
  84. package/server/workspace/journal/diff.ts +2 -2
  85. package/server/workspace/journal/index.ts +4 -4
  86. package/server/workspace/journal/indexFile.ts +29 -25
  87. package/server/workspace/journal/optimizationPass.ts +2 -2
  88. package/server/workspace/journal/state.ts +6 -6
  89. package/server/workspace/paths.ts +3 -3
  90. package/server/workspace/reference-dirs.ts +20 -20
  91. package/server/workspace/roles.ts +6 -6
  92. package/server/workspace/skills/discovery.ts +4 -4
  93. package/server/workspace/skills/parser.ts +6 -6
  94. package/server/workspace/skills/scheduler.ts +3 -3
  95. package/server/workspace/skills/user-tasks.ts +34 -34
  96. package/server/workspace/skills/writer.ts +3 -3
  97. package/server/workspace/sources/arxivDiscovery.ts +10 -10
  98. package/server/workspace/sources/classifier.ts +7 -7
  99. package/server/workspace/sources/fetchers/arxiv.ts +7 -7
  100. package/server/workspace/sources/fetchers/githubIssues.ts +7 -7
  101. package/server/workspace/sources/fetchers/githubReleases.ts +7 -7
  102. package/server/workspace/sources/fetchers/rss.ts +5 -5
  103. package/server/workspace/sources/fetchers/rssParser.ts +4 -4
  104. package/server/workspace/sources/interests.ts +12 -12
  105. package/server/workspace/sources/paths.ts +6 -6
  106. package/server/workspace/sources/pipeline/fetch.ts +36 -13
  107. package/server/workspace/sources/pipeline/index.ts +8 -13
  108. package/server/workspace/sources/pipeline/notify.ts +3 -3
  109. package/server/workspace/sources/pipeline/plan.ts +15 -13
  110. package/server/workspace/sources/pipeline/write.ts +5 -5
  111. package/server/workspace/sources/rateLimiter.ts +1 -1
  112. package/server/workspace/sources/registry.ts +16 -16
  113. package/server/workspace/sources/robots.ts +14 -14
  114. package/server/workspace/sources/sourceState.ts +17 -10
  115. package/server/workspace/sources/types.ts +9 -0
  116. package/server/workspace/sources/urls.ts +1 -1
  117. package/server/workspace/tool-trace/classify.ts +4 -4
  118. package/server/workspace/tool-trace/index.ts +1 -1
  119. package/server/workspace/tool-trace/writeSearch.ts +26 -16
  120. package/server/workspace/wiki-backlinks/index.ts +8 -8
  121. package/server/workspace/wiki-backlinks/sessionBacklinks.ts +15 -15
  122. package/server/workspace/workspace.ts +7 -7
  123. package/src/App.vue +315 -141
  124. package/src/components/CanvasViewToggle.vue +10 -7
  125. package/src/components/ChatInput.vue +67 -33
  126. package/src/components/FileContentHeader.vue +7 -4
  127. package/src/components/FileContentRenderer.vue +20 -6
  128. package/src/components/FileTree.vue +6 -3
  129. package/src/components/FileTreePane.vue +11 -8
  130. package/src/components/FilesView.vue +5 -3
  131. package/src/components/LockStatusPopup.vue +17 -14
  132. package/src/components/NotificationBell.vue +14 -5
  133. package/src/components/NotificationToast.vue +6 -3
  134. package/src/components/PluginLauncher.vue +19 -56
  135. package/src/components/RightSidebar.vue +13 -10
  136. package/src/components/RoleSelector.vue +2 -2
  137. package/src/components/SessionHistoryPanel.vue +38 -34
  138. package/src/components/SessionTabBar.vue +8 -10
  139. package/src/components/SettingsMcpTab.vue +49 -36
  140. package/src/components/SettingsModal.vue +24 -22
  141. package/src/components/SettingsReferenceDirsTab.vue +39 -34
  142. package/src/components/SettingsWorkspaceDirsTab.vue +37 -27
  143. package/src/components/SidebarHeader.vue +25 -4
  144. package/src/components/StackView.vue +4 -1
  145. package/src/components/SuggestionsPanel.vue +7 -4
  146. package/src/components/TodoExplorer.vue +26 -15
  147. package/src/components/ToolResultsPanel.vue +27 -13
  148. package/src/components/todo/TodoAddDialog.vue +19 -14
  149. package/src/components/todo/TodoEditDialog.vue +7 -2
  150. package/src/components/todo/TodoEditPanel.vue +17 -12
  151. package/src/components/todo/TodoKanbanView.vue +10 -5
  152. package/src/components/todo/TodoListView.vue +10 -7
  153. package/src/components/todo/TodoTableView.vue +5 -2
  154. package/src/composables/useAppApi.ts +9 -0
  155. package/src/composables/useClickOutside.ts +2 -2
  156. package/src/composables/useDynamicFavicon.ts +172 -37
  157. package/src/composables/useEventListeners.ts +7 -8
  158. package/src/composables/useFaviconState.ts +13 -2
  159. package/src/composables/useFileSelection.ts +24 -6
  160. package/src/composables/useFreshPluginData.ts +3 -3
  161. package/src/composables/useKeyNavigation.ts +11 -11
  162. package/src/composables/useLayoutMode.ts +32 -0
  163. package/src/composables/useMcpTools.ts +2 -2
  164. package/src/composables/useNotifications.ts +3 -3
  165. package/src/composables/usePdfDownload.ts +4 -4
  166. package/src/composables/usePendingCalls.ts +1 -1
  167. package/src/composables/usePubSub.ts +10 -10
  168. package/src/composables/useRoles.ts +1 -1
  169. package/src/composables/useSandboxStatus.ts +1 -1
  170. package/src/composables/useSessionDerived.ts +3 -3
  171. package/src/composables/useSessionHistory.ts +7 -17
  172. package/src/composables/useSessionSync.ts +8 -8
  173. package/src/composables/useViewLayout.ts +20 -34
  174. package/src/config/roles.ts +2 -2
  175. package/src/lang/de.ts +536 -0
  176. package/src/lang/en.ts +558 -0
  177. package/src/lang/es.ts +543 -0
  178. package/src/lang/fr.ts +536 -0
  179. package/src/lang/ja.ts +536 -0
  180. package/src/lang/ko.ts +540 -0
  181. package/src/lang/pt-BR.ts +534 -0
  182. package/src/lang/zh.ts +537 -0
  183. package/src/lib/vue-i18n.ts +97 -0
  184. package/src/main.ts +2 -0
  185. package/src/plugins/canvas/View.vue +102 -186
  186. package/src/plugins/canvas/definition.ts +0 -8
  187. package/src/plugins/chart/Preview.vue +5 -5
  188. package/src/plugins/chart/View.vue +9 -4
  189. package/src/plugins/manageRoles/Preview.vue +4 -1
  190. package/src/plugins/manageRoles/View.vue +59 -43
  191. package/src/plugins/manageSkills/Preview.vue +8 -3
  192. package/src/plugins/manageSkills/View.vue +29 -25
  193. package/src/plugins/manageSource/Preview.vue +2 -2
  194. package/src/plugins/manageSource/View.vue +73 -52
  195. package/src/plugins/markdown/Preview.vue +1 -1
  196. package/src/plugins/markdown/View.vue +26 -36
  197. package/src/plugins/presentHtml/Preview.vue +1 -1
  198. package/src/plugins/presentHtml/View.vue +7 -4
  199. package/src/plugins/presentHtml/helpers.ts +8 -8
  200. package/src/plugins/presentMulmoScript/Preview.vue +1 -1
  201. package/src/plugins/presentMulmoScript/View.vue +40 -30
  202. package/src/plugins/presentMulmoScript/helpers.ts +1 -1
  203. package/src/plugins/scheduler/Preview.vue +13 -10
  204. package/src/plugins/scheduler/TasksTab.vue +57 -28
  205. package/src/plugins/scheduler/View.vue +28 -19
  206. package/src/plugins/scheduler/formatSchedule.ts +93 -0
  207. package/src/plugins/spreadsheet/Preview.vue +8 -3
  208. package/src/plugins/spreadsheet/View.vue +21 -12
  209. package/src/plugins/textResponse/Preview.vue +15 -58
  210. package/src/plugins/textResponse/View.vue +29 -9
  211. package/src/plugins/todo/Preview.vue +13 -8
  212. package/src/plugins/todo/View.vue +38 -24
  213. package/src/plugins/todo/composables/useTodos.ts +5 -5
  214. package/src/plugins/ui-image/ImagePreview.vue +6 -3
  215. package/src/plugins/ui-image/ImageView.vue +7 -4
  216. package/src/plugins/wiki/Preview.vue +10 -7
  217. package/src/plugins/wiki/View.vue +202 -81
  218. package/src/plugins/wiki/helpers.ts +4 -4
  219. package/src/plugins/wiki/route.ts +112 -0
  220. package/src/router/guards.ts +46 -28
  221. package/src/router/index.ts +41 -26
  222. package/src/types/session.ts +4 -3
  223. package/src/types/vue-i18n.d.ts +20 -0
  224. package/src/utils/agent/request.ts +22 -3
  225. package/src/utils/canvas/layoutMode.ts +26 -0
  226. package/src/utils/dom/scrollable.ts +2 -2
  227. package/src/utils/files/expandedDirs.ts +1 -1
  228. package/src/utils/files/sortChildren.ts +6 -6
  229. package/src/utils/format/frontmatter.ts +6 -6
  230. package/src/utils/image/cacheBust.ts +16 -0
  231. package/src/utils/image/resolve.ts +16 -0
  232. package/src/utils/image/rewriteMarkdownImageRefs.ts +5 -5
  233. package/src/utils/markdown/extractFirstH1.ts +2 -2
  234. package/src/utils/path/relativeLink.ts +15 -15
  235. package/src/utils/path/workspaceLinkRouter.ts +81 -0
  236. package/src/utils/role/icon.ts +2 -2
  237. package/src/utils/role/merge.ts +2 -2
  238. package/src/utils/role/plugins.ts +1 -1
  239. package/src/utils/session/sessionFactory.ts +2 -2
  240. package/src/utils/session/sessionHelpers.ts +2 -2
  241. package/src/utils/tools/dedup.ts +4 -4
  242. package/src/utils/tools/result.ts +3 -3
  243. package/src/utils/types.ts +2 -2
  244. package/src/vite-env.d.ts +9 -0
  245. package/client/assets/chunk-vKJrgz-R-C_I3GbVV.js +0 -1
  246. package/client/assets/html2canvas-Cx501zZr-BF5dYYkY.js +0 -5
  247. package/client/assets/index-KNLBjwuh.css +0 -1
  248. package/client/assets/typeof-DBp4T-Ny-BC0P-2DM.js +0 -1
  249. package/src/composables/useCanvasViewMode.ts +0 -121
  250. package/src/utils/canvas/viewMode.ts +0 -46
  251. /package/client/assets/{purify.es-Fx1Nqyry-PeS5RUhs.js → purify.es-Fx1Nqyry-BwJECkqS.js} +0 -0
@@ -3,19 +3,21 @@
3
3
  <!-- Header -->
4
4
  <div class="flex items-center justify-between px-4 py-2 border-b border-gray-100 shrink-0 gap-3">
5
5
  <div class="flex items-center gap-3 min-w-0">
6
- <h2 class="text-base font-semibold text-gray-800 shrink-0">Todo</h2>
7
- <span class="text-xs text-gray-500 shrink-0">{{ completedCount }}/{{ items.length }} done</span>
6
+ <h2 class="text-base font-semibold text-gray-800 shrink-0">{{ t("todoExplorer.heading") }}</h2>
7
+ <span class="text-xs text-gray-500 shrink-0">{{ t("todoExplorer.doneRatio", { done: completedCount, total: items.length }) }}</span>
8
8
  <input
9
9
  v-model="search"
10
10
  data-testid="todo-search"
11
11
  type="text"
12
- placeholder="Search..."
12
+ :placeholder="t('todoExplorer.searchPlaceholder')"
13
13
  class="px-2 py-1 text-xs border border-gray-200 rounded w-44 focus:outline-none focus:border-blue-400"
14
14
  />
15
15
  </div>
16
16
  <div class="flex items-center gap-2">
17
17
  <!-- Add button -->
18
- <button data-testid="todo-add-btn" class="px-2 py-1 text-xs rounded bg-blue-500 text-white hover:bg-blue-600" @click="addOpen = true">+ Add</button>
18
+ <button data-testid="todo-add-btn" class="px-2 py-1 text-xs rounded bg-blue-500 text-white hover:bg-blue-600" @click="addOpen = true">
19
+ {{ t("todoExplorer.addButton") }}
20
+ </button>
19
21
  <!-- Add column button (kanban only) -->
20
22
  <button
21
23
  v-if="viewMode === TODO_VIEW.kanban"
@@ -23,7 +25,7 @@
23
25
  class="px-2 py-1 text-xs rounded border border-gray-300 text-gray-600 hover:bg-gray-50"
24
26
  @click="addColumnOpen = true"
25
27
  >
26
- + Column
28
+ {{ t("todoExplorer.addColumnButton") }}
27
29
  </button>
28
30
  <!-- View mode toggle -->
29
31
  <div class="flex border border-gray-300 rounded overflow-hidden text-xs">
@@ -44,7 +46,7 @@
44
46
 
45
47
  <!-- Label filter chips -->
46
48
  <div v-if="labelInventory.length > 0" class="flex flex-wrap items-center gap-1.5 px-4 py-1.5 border-b border-gray-100 bg-gray-50 shrink-0">
47
- <span class="text-[11px] text-gray-500 mr-1">Labels:</span>
49
+ <span class="text-[11px] text-gray-500 mr-1">{{ t("todoExplorer.labels") }}</span>
48
50
  <button
49
51
  v-for="entry in labelInventory"
50
52
  :key="entry.label"
@@ -59,8 +61,13 @@
59
61
  {{ entry.label }}
60
62
  <span class="opacity-60">{{ entry.count }}</span>
61
63
  </button>
62
- <button v-if="activeFilters.size > 0" class="ml-auto text-[11px] text-gray-500 hover:text-gray-700" title="Clear label filters" @click="clearFilters">
63
- Clear
64
+ <button
65
+ v-if="activeFilters.size > 0"
66
+ class="ml-auto text-[11px] text-gray-500 hover:text-gray-700"
67
+ :title="t('todoExplorer.clearFiltersTitle')"
68
+ @click="clearFilters"
69
+ >
70
+ {{ t("todoExplorer.clearButton") }}
64
71
  </button>
65
72
  </div>
66
73
 
@@ -71,7 +78,7 @@
71
78
 
72
79
  <!-- Body -->
73
80
  <div class="flex-1 min-h-0">
74
- <div v-if="items.length === 0" class="h-full flex items-center justify-center text-gray-400 text-sm">No todo items yet. Click "+ Add" to create one.</div>
81
+ <div v-if="items.length === 0" class="h-full flex items-center justify-center text-gray-400 text-sm">{{ t("todoExplorer.emptyHint") }}</div>
75
82
  <template v-else>
76
83
  <TodoKanbanView
77
84
  v-if="viewMode === TODO_VIEW.kanban"
@@ -122,20 +129,22 @@
122
129
  <!-- Add column dialog -->
123
130
  <div v-if="addColumnOpen" class="fixed inset-0 z-50 bg-black/30 flex items-center justify-center" @click="addColumnOpen = false">
124
131
  <div class="bg-white rounded-lg shadow-xl w-80 p-5 space-y-3" role="dialog" aria-modal="true" aria-labelledby="todo-add-column-title" @click.stop>
125
- <h3 id="todo-add-column-title" class="text-base font-semibold text-gray-800">Add Column</h3>
132
+ <h3 id="todo-add-column-title" class="text-base font-semibold text-gray-800">{{ t("todoExplorer.addColumn") }}</h3>
126
133
  <label class="block text-xs text-gray-600">
127
- Label
134
+ {{ t("todoExplorer.newColumnLabelField") }}
128
135
  <input
129
136
  v-model="newColumnLabel"
130
137
  type="text"
131
- placeholder="Review"
138
+ :placeholder="t('todoExplorer.newColumnPlaceholder')"
132
139
  class="mt-1 w-full px-2 py-1.5 text-sm border border-gray-300 rounded focus:outline-none focus:border-blue-400"
133
140
  @keydown.enter="commitNewColumn"
134
141
  />
135
142
  </label>
136
143
  <div class="flex justify-end gap-2 pt-1">
137
- <button class="px-3 py-1.5 text-sm rounded border border-gray-300 text-gray-600 hover:bg-gray-50" @click="addColumnOpen = false">Cancel</button>
138
- <button class="px-3 py-1.5 text-sm rounded bg-blue-500 text-white hover:bg-blue-600" @click="commitNewColumn">Add</button>
144
+ <button class="px-3 py-1.5 text-sm rounded border border-gray-300 text-gray-600 hover:bg-gray-50" @click="addColumnOpen = false">
145
+ {{ t("common.cancel") }}
146
+ </button>
147
+ <button class="px-3 py-1.5 text-sm rounded bg-blue-500 text-white hover:bg-blue-600" @click="commitNewColumn">{{ t("common.add") }}</button>
139
148
  </div>
140
149
  </div>
141
150
  </div>
@@ -144,6 +153,7 @@
144
153
 
145
154
  <script setup lang="ts">
146
155
  import { computed, onMounted, onUnmounted, ref, watch } from "vue";
156
+ import { useI18n } from "vue-i18n";
147
157
  import type { ToolResultComplete } from "gui-chat-protocol/vue";
148
158
  import type { TodoData, TodoItem } from "../plugins/todo/index";
149
159
  import { colorForLabel, filterByLabels, listLabelsWithCount } from "../plugins/todo/labels";
@@ -153,9 +163,10 @@ import TodoTableView from "./todo/TodoTableView.vue";
153
163
  import TodoListView from "./todo/TodoListView.vue";
154
164
  import TodoAddDialog from "./todo/TodoAddDialog.vue";
155
165
  import TodoEditDialog from "./todo/TodoEditDialog.vue";
156
-
157
166
  import { TODO_VIEW, TODO_VIEW_MODES as VIEW_MODES, type TodoViewMode as ViewMode } from "../plugins/todo/viewModes";
158
167
 
168
+ const { t } = useI18n();
169
+
159
170
  const VIEW_MODE_KEY = "todo_explorer_view_mode";
160
171
 
161
172
  const props = defineProps<{
@@ -9,22 +9,21 @@
9
9
  <div
10
10
  v-for="result in results"
11
11
  :key="result.uuid"
12
- class="cursor-pointer rounded border border-gray-300 p-2 text-sm text-gray-900 hover:opacity-75 transition-opacity"
12
+ class="relative cursor-pointer rounded border border-gray-300 text-sm text-gray-900 hover:opacity-75 transition-opacity"
13
13
  :class="result.uuid === selectedUuid ? 'ring-2 ring-blue-500' : ''"
14
14
  @click="emit('select', result.uuid)"
15
15
  >
16
- <div class="flex items-center gap-1">
17
- <component
18
- :is="getPlugin(result.toolName)?.previewComponent"
19
- v-if="getPlugin(result.toolName)?.previewComponent"
20
- :result="result"
21
- class="flex-1 min-w-0"
22
- />
23
- <span v-else class="flex-1 min-w-0 truncate">{{ result.title || result.toolName }}</span>
24
- <span v-if="resultTimestamps.get(result.uuid)" class="text-[10px] text-gray-400 shrink-0">{{
25
- formatSmartTime(resultTimestamps.get(result.uuid)!)
26
- }}</span>
27
- </div>
16
+ <span class="absolute top-0 left-2 -translate-y-1/2 bg-gray-100 px-1 text-[10px] text-gray-400 leading-none pointer-events-none">
17
+ {{ sourceLabel(result) }}
18
+ </span>
19
+ <span
20
+ v-if="resultTimestamps.get(result.uuid)"
21
+ class="absolute top-0 right-2 -translate-y-1/2 bg-gray-100 px-1 text-[10px] text-gray-400 leading-none pointer-events-none"
22
+ >
23
+ {{ formatSmartTime(resultTimestamps.get(result.uuid)!) }}
24
+ </span>
25
+ <component :is="getPlugin(result.toolName)?.previewComponent" v-if="getPlugin(result.toolName)?.previewComponent" :result="result" />
26
+ <span v-else class="block truncate p-2">{{ result.title || result.toolName }}</span>
28
27
  </div>
29
28
 
30
29
  <!-- Thinking indicator -->
@@ -53,6 +52,11 @@ import type { ToolResultComplete } from "gui-chat-protocol/vue";
53
52
  import { getPlugin } from "../tools";
54
53
  import { formatSmartTime } from "../utils/format/date";
55
54
 
55
+ function sourceLabel(result: ToolResultComplete): string {
56
+ if (result.toolName === "text-response") return result.title ?? "Assistant";
57
+ return result.toolName;
58
+ }
59
+
56
60
  interface PendingCall {
57
61
  toolUseId: string;
58
62
  toolName: string;
@@ -75,3 +79,13 @@ const emit = defineEmits<{
75
79
  const root = ref<HTMLDivElement | null>(null);
76
80
  defineExpose({ root });
77
81
  </script>
82
+
83
+ <style scoped>
84
+ /* Prevent rendered markdown links inside preview cards from navigating.
85
+ Clicking a sidebar card should select the result, not follow links. */
86
+ :deep(a) {
87
+ pointer-events: none;
88
+ color: inherit;
89
+ text-decoration: none;
90
+ }
91
+ </style>
@@ -7,20 +7,20 @@
7
7
  aria-labelledby="todo-add-dialog-title"
8
8
  @click.stop
9
9
  >
10
- <h3 id="todo-add-dialog-title" class="text-base font-semibold text-gray-800">Add Todo</h3>
10
+ <h3 id="todo-add-dialog-title" class="text-base font-semibold text-gray-800">{{ t("todoDialogs.addTitle") }}</h3>
11
11
  <label class="block text-xs text-gray-600">
12
- Text
12
+ {{ t("todoDialogs.fieldText") }}
13
13
  <input
14
14
  ref="textInput"
15
15
  v-model="text"
16
16
  type="text"
17
- placeholder="What needs doing?"
17
+ :placeholder="t('todoDialogs.textPlaceholder')"
18
18
  class="mt-1 w-full px-2 py-1.5 text-sm border border-gray-300 rounded focus:outline-none focus:border-blue-400"
19
19
  @keydown.enter="submit"
20
20
  />
21
21
  </label>
22
22
  <label class="block text-xs text-gray-600">
23
- Note
23
+ {{ t("todoDialogs.fieldNote") }}
24
24
  <textarea
25
25
  v-model="note"
26
26
  rows="2"
@@ -29,7 +29,7 @@
29
29
  </label>
30
30
  <div class="grid grid-cols-2 gap-3">
31
31
  <label class="block text-xs text-gray-600">
32
- Status
32
+ {{ t("todoDialogs.fieldStatus") }}
33
33
  <select v-model="status" class="mt-1 w-full px-2 py-1.5 text-sm border border-gray-300 rounded focus:outline-none focus:border-blue-400">
34
34
  <option v-for="col in columns" :key="col.id" :value="col.id">
35
35
  {{ col.label }}
@@ -37,16 +37,16 @@
37
37
  </select>
38
38
  </label>
39
39
  <label class="block text-xs text-gray-600">
40
- Priority
40
+ {{ t("todoDialogs.fieldPriority") }}
41
41
  <select v-model="priority" class="mt-1 w-full px-2 py-1.5 text-sm border border-gray-300 rounded focus:outline-none focus:border-blue-400">
42
- <option value="">— None —</option>
42
+ <option value="">{{ t("todoDialogs.noneOption") }}</option>
43
43
  <option v-for="p in PRIORITIES" :key="p" :value="p">
44
44
  {{ PRIORITY_LABELS[p] }}
45
45
  </option>
46
46
  </select>
47
47
  </label>
48
48
  <label class="block text-xs text-gray-600">
49
- Due date
49
+ {{ t("todoDialogs.fieldDueDate") }}
50
50
  <input
51
51
  v-model="dueDate"
52
52
  type="date"
@@ -54,18 +54,20 @@
54
54
  />
55
55
  </label>
56
56
  <label class="block text-xs text-gray-600">
57
- Labels
57
+ {{ t("todoDialogs.fieldLabels") }}
58
58
  <input
59
59
  v-model="labelsText"
60
60
  type="text"
61
- placeholder="work, urgent"
61
+ :placeholder="t('todoDialogs.labelsPlaceholder')"
62
62
  class="mt-1 w-full px-2 py-1.5 text-sm border border-gray-300 rounded focus:outline-none focus:border-blue-400"
63
63
  />
64
64
  </label>
65
65
  </div>
66
66
  <div class="flex justify-end gap-2 pt-1">
67
- <button class="px-3 py-1.5 text-sm rounded border border-gray-300 text-gray-600 hover:bg-gray-50" @click="emit('cancel')">Cancel</button>
68
- <button class="px-3 py-1.5 text-sm rounded bg-blue-500 text-white hover:bg-blue-600" @click="submit">Add</button>
67
+ <button class="px-3 py-1.5 text-sm rounded border border-gray-300 text-gray-600 hover:bg-gray-50" @click="emit('cancel')">
68
+ {{ t("common.cancel") }}
69
+ </button>
70
+ <button class="px-3 py-1.5 text-sm rounded bg-blue-500 text-white hover:bg-blue-600" @click="submit">{{ t("common.add") }}</button>
69
71
  </div>
70
72
  </div>
71
73
  </div>
@@ -73,10 +75,13 @@
73
75
 
74
76
  <script setup lang="ts">
75
77
  import { onMounted, onUnmounted, ref } from "vue";
78
+ import { useI18n } from "vue-i18n";
76
79
  import type { StatusColumn } from "../../plugins/todo/index";
77
80
  import { PRIORITIES, PRIORITY_LABELS } from "../../plugins/todo/priority";
78
81
  import type { CreateItemInput } from "../../plugins/todo/composables/useTodos";
79
82
 
83
+ const { t } = useI18n();
84
+
80
85
  const props = defineProps<{
81
86
  columns: StatusColumn[];
82
87
  defaultStatus?: string;
@@ -123,8 +128,8 @@ function submit(): void {
123
128
  if (dueDate.value !== "") input.dueDate = dueDate.value;
124
129
  const labels = labelsText.value
125
130
  .split(",")
126
- .map((s) => s.trim())
127
- .filter((s) => s.length > 0);
131
+ .map((item) => item.trim())
132
+ .filter((item) => item.length > 0);
128
133
  if (labels.length > 0) input.labels = labels;
129
134
  emit("create", input);
130
135
  }
@@ -8,8 +8,10 @@
8
8
  @click.stop
9
9
  >
10
10
  <div class="flex items-center justify-between px-4 py-2 border-b border-gray-100">
11
- <h3 id="todo-edit-dialog-title" class="text-base font-semibold text-gray-800">Edit Todo</h3>
12
- <button class="text-gray-400 hover:text-red-500 text-xs px-2 py-0.5" title="Delete this item" @click="emit('delete', item.id)">Delete</button>
11
+ <h3 id="todo-edit-dialog-title" class="text-base font-semibold text-gray-800">{{ t("todoDialogs.editTitle") }}</h3>
12
+ <button class="text-gray-400 hover:text-red-500 text-xs px-2 py-0.5" :title="t('todoDialogs.deleteTitle')" @click="emit('delete', item.id)">
13
+ {{ t("todoDialogs.deleteButton") }}
14
+ </button>
13
15
  </div>
14
16
  <TodoEditPanel :item="item" :columns="columns" @save="(input) => emit('save', input)" @cancel="emit('cancel')" />
15
17
  </div>
@@ -18,10 +20,13 @@
18
20
 
19
21
  <script setup lang="ts">
20
22
  import { onMounted, onUnmounted } from "vue";
23
+ import { useI18n } from "vue-i18n";
21
24
  import type { StatusColumn, TodoItem } from "../../plugins/todo/index";
22
25
  import type { PatchItemInput } from "../../plugins/todo/composables/useTodos";
23
26
  import TodoEditPanel from "./TodoEditPanel.vue";
24
27
 
28
+ const { t } = useI18n();
29
+
25
30
  defineProps<{
26
31
  item: TodoItem;
27
32
  columns: StatusColumn[];
@@ -2,7 +2,7 @@
2
2
  <div class="border-t border-blue-100 bg-blue-50 p-4 space-y-3 rounded-b-lg">
3
3
  <div class="grid grid-cols-1 sm:grid-cols-2 gap-3">
4
4
  <label class="block text-xs text-gray-600 sm:col-span-2">
5
- Text
5
+ {{ t("todoDialogs.fieldText") }}
6
6
  <input
7
7
  v-model="text"
8
8
  type="text"
@@ -10,7 +10,7 @@
10
10
  />
11
11
  </label>
12
12
  <label class="block text-xs text-gray-600 sm:col-span-2">
13
- Note
13
+ {{ t("todoDialogs.fieldNote") }}
14
14
  <textarea
15
15
  v-model="note"
16
16
  rows="2"
@@ -18,7 +18,7 @@
18
18
  />
19
19
  </label>
20
20
  <label class="block text-xs text-gray-600">
21
- Status
21
+ {{ t("todoDialogs.fieldStatus") }}
22
22
  <select v-model="status" class="mt-1 w-full px-2 py-1.5 text-sm bg-white border border-blue-300 rounded focus:outline-none focus:border-blue-500">
23
23
  <option v-for="col in columns" :key="col.id" :value="col.id">
24
24
  {{ col.label }}
@@ -26,16 +26,16 @@
26
26
  </select>
27
27
  </label>
28
28
  <label class="block text-xs text-gray-600">
29
- Priority
29
+ {{ t("todoDialogs.fieldPriority") }}
30
30
  <select v-model="priority" class="mt-1 w-full px-2 py-1.5 text-sm bg-white border border-blue-300 rounded focus:outline-none focus:border-blue-500">
31
- <option value="">— None —</option>
31
+ <option value="">{{ t("todoDialogs.noneOption") }}</option>
32
32
  <option v-for="p in PRIORITIES" :key="p" :value="p">
33
33
  {{ PRIORITY_LABELS[p] }}
34
34
  </option>
35
35
  </select>
36
36
  </label>
37
37
  <label class="block text-xs text-gray-600">
38
- Due date
38
+ {{ t("todoDialogs.fieldDueDate") }}
39
39
  <input
40
40
  v-model="dueDate"
41
41
  type="date"
@@ -43,28 +43,33 @@
43
43
  />
44
44
  </label>
45
45
  <label class="block text-xs text-gray-600">
46
- Labels (comma-separated)
46
+ {{ t("todoDialogs.fieldLabelsCommaSeparated") }}
47
47
  <input
48
48
  v-model="labelsText"
49
49
  type="text"
50
- placeholder="work, urgent"
50
+ :placeholder="t('todoDialogs.labelsPlaceholder')"
51
51
  class="mt-1 w-full px-2 py-1.5 text-sm bg-white border border-blue-300 rounded focus:outline-none focus:border-blue-500"
52
52
  />
53
53
  </label>
54
54
  </div>
55
55
  <div class="flex items-center gap-2 pt-1">
56
- <button class="px-3 py-1.5 text-sm rounded bg-blue-500 text-white hover:bg-blue-600" @click="save">Save</button>
57
- <button class="px-3 py-1.5 text-sm rounded border border-gray-300 text-gray-600 hover:bg-gray-50" @click="emit('cancel')">Cancel</button>
56
+ <button class="px-3 py-1.5 text-sm rounded bg-blue-500 text-white hover:bg-blue-600" @click="save">{{ t("common.save") }}</button>
57
+ <button class="px-3 py-1.5 text-sm rounded border border-gray-300 text-gray-600 hover:bg-gray-50" @click="emit('cancel')">
58
+ {{ t("common.cancel") }}
59
+ </button>
58
60
  </div>
59
61
  </div>
60
62
  </template>
61
63
 
62
64
  <script setup lang="ts">
63
65
  import { ref } from "vue";
66
+ import { useI18n } from "vue-i18n";
64
67
  import type { StatusColumn, TodoItem, TodoPriority } from "../../plugins/todo/index";
65
68
  import { PRIORITIES, PRIORITY_LABELS } from "../../plugins/todo/priority";
66
69
  import type { PatchItemInput } from "../../plugins/todo/composables/useTodos";
67
70
 
71
+ const { t } = useI18n();
72
+
68
73
  const props = defineProps<{
69
74
  item: TodoItem;
70
75
  columns: StatusColumn[];
@@ -85,8 +90,8 @@ const labelsText = ref((props.item.labels ?? []).join(", "));
85
90
  function parseLabels(raw: string): string[] {
86
91
  return raw
87
92
  .split(",")
88
- .map((s) => s.trim())
89
- .filter((s) => s.length > 0);
93
+ .map((item) => item.trim())
94
+ .filter((item) => item.length > 0);
90
95
  }
91
96
 
92
97
  function save(): void {
@@ -32,7 +32,7 @@
32
32
  <span class="text-xs text-gray-500 shrink-0">{{ itemsByColumn(col.id).length }}</span>
33
33
  </div>
34
34
  <div class="relative">
35
- <button class="text-gray-400 hover:text-gray-600 px-1" title="Column actions" @click="toggleMenu(col.id)">
35
+ <button class="text-gray-400 hover:text-gray-600 px-1" :title="t('todoKanban.columnActions')" @click="toggleMenu(col.id)">
36
36
  <span class="material-icons text-base">more_horiz</span>
37
37
  </button>
38
38
  <div
@@ -40,11 +40,13 @@
40
40
  class="absolute right-0 top-6 z-20 bg-white border border-gray-200 rounded shadow-md text-xs w-40 py-1"
41
41
  @click.stop
42
42
  >
43
- <button class="w-full text-left px-3 py-1.5 hover:bg-gray-50" @click="startRename(col)">Rename</button>
43
+ <button class="w-full text-left px-3 py-1.5 hover:bg-gray-50" @click="startRename(col)">{{ t("todoKanban.rename") }}</button>
44
44
  <button class="w-full text-left px-3 py-1.5 hover:bg-gray-50" @click="markAsDone(col.id)">
45
- {{ col.isDone ? "Already done column" : "Mark as done column" }}
45
+ {{ col.isDone ? t("todoKanban.alreadyDoneColumn") : t("todoKanban.markAsDoneColumn") }}
46
+ </button>
47
+ <button class="w-full text-left px-3 py-1.5 text-red-600 hover:bg-red-50" @click="deleteColumn(col.id)">
48
+ {{ t("todoKanban.deleteColumn") }}
46
49
  </button>
47
- <button class="w-full text-left px-3 py-1.5 text-red-600 hover:bg-red-50" @click="deleteColumn(col.id)">Delete column</button>
48
50
  </div>
49
51
  </div>
50
52
  </div>
@@ -103,7 +105,7 @@
103
105
 
104
106
  <!-- Add card stub -->
105
107
  <button class="m-2 text-xs text-gray-500 hover:text-gray-800 hover:bg-gray-200 rounded py-1.5 transition-colors" @click="emit('quickAdd', col.id)">
106
- + Add card
108
+ {{ t("todoKanban.addCard") }}
107
109
  </button>
108
110
  </div>
109
111
  </template>
@@ -113,11 +115,14 @@
113
115
 
114
116
  <script setup lang="ts">
115
117
  import { computed, nextTick, ref, watch } from "vue";
118
+ import { useI18n } from "vue-i18n";
116
119
  import draggable from "vuedraggable";
117
120
  import type { StatusColumn, TodoItem } from "../../plugins/todo/index";
118
121
  import { colorForLabel } from "../../plugins/todo/labels";
119
122
  import { PRIORITY_BORDER, PRIORITY_CLASSES, PRIORITY_LABELS, dueDateClasses, formatDueLabel } from "../../plugins/todo/priority";
120
123
 
124
+ const { t } = useI18n();
125
+
121
126
  // vuedraggable @change event shape. The library emits one of these
122
127
  // three keys depending on whether the move was within the same list,
123
128
  // added from another list, or removed from the current list. We only
@@ -1,6 +1,6 @@
1
1
  <template>
2
2
  <div class="h-full overflow-y-auto p-4">
3
- <div v-if="filteredItems.length === 0" class="h-full flex items-center justify-center text-gray-400 text-sm">No items match the current filter</div>
3
+ <div v-if="filteredItems.length === 0" class="h-full flex items-center justify-center text-gray-400 text-sm">{{ t("todoTableList.noMatchingFilter") }}</div>
4
4
  <ul v-else class="space-y-2 max-w-3xl mx-auto">
5
5
  <li v-for="item in filteredItems" :key="item.id" class="rounded-lg border border-gray-200 hover:border-gray-300 transition-colors">
6
6
  <div class="flex items-center gap-3 p-3 cursor-pointer group" @click="toggleExpand(item.id)">
@@ -25,7 +25,7 @@
25
25
  </div>
26
26
  <button
27
27
  class="opacity-0 group-hover:opacity-100 text-gray-400 hover:text-red-500 text-xs px-1 shrink-0"
28
- title="Delete item"
28
+ :title="t('pluginTodo.deleteItem')"
29
29
  @click.stop="emit('delete', item.id)"
30
30
  >
31
31
 
@@ -39,12 +39,15 @@
39
39
 
40
40
  <script setup lang="ts">
41
41
  import { ref } from "vue";
42
+ import { useI18n } from "vue-i18n";
42
43
  import type { StatusColumn, TodoItem } from "../../plugins/todo/index";
43
44
  import { colorForLabel } from "../../plugins/todo/labels";
44
45
  import { PRIORITY_CLASSES, PRIORITY_LABELS, dueDateClasses, formatDueLabel } from "../../plugins/todo/priority";
45
46
  import type { PatchItemInput } from "../../plugins/todo/composables/useTodos";
46
47
  import TodoEditPanel from "./TodoEditPanel.vue";
47
48
 
49
+ const { t } = useI18n();
50
+
48
51
  const props = defineProps<{
49
52
  filteredItems: TodoItem[];
50
53
  columns: StatusColumn[];
@@ -58,22 +61,22 @@ const emit = defineEmits<{
58
61
 
59
62
  const expandedId = ref<string | null>(null);
60
63
 
61
- function toggleExpand(id: string): void {
62
- expandedId.value = expandedId.value === id ? null : id;
64
+ function toggleExpand(itemId: string): void {
65
+ expandedId.value = expandedId.value === itemId ? null : itemId;
63
66
  }
64
67
 
65
68
  function toggleComplete(item: TodoItem): void {
66
69
  emit("toggleComplete", item);
67
70
  }
68
71
 
69
- function onSave(id: string, input: PatchItemInput): void {
70
- emit("patch", id, input);
72
+ function onSave(itemId: string, input: PatchItemInput): void {
73
+ emit("patch", itemId, input);
71
74
  expandedId.value = null;
72
75
  }
73
76
 
74
77
  function statusLabel(item: TodoItem): string {
75
78
  if (!item.status) return "";
76
- const col = props.columns.find((c) => c.id === item.status);
79
+ const col = props.columns.find((column) => column.id === item.status);
77
80
  return col?.label ?? "";
78
81
  }
79
82
  </script>
@@ -1,6 +1,6 @@
1
1
  <template>
2
2
  <div class="h-full overflow-auto">
3
- <div v-if="filteredItems.length === 0" class="h-full flex items-center justify-center text-gray-400 text-sm">No items match the current filter</div>
3
+ <div v-if="filteredItems.length === 0" class="h-full flex items-center justify-center text-gray-400 text-sm">{{ t("todoTableList.noMatchingFilter") }}</div>
4
4
  <table v-else class="min-w-full text-sm">
5
5
  <thead class="bg-gray-50 sticky top-0 z-10">
6
6
  <tr class="text-left text-xs font-medium text-gray-500 uppercase">
@@ -53,7 +53,7 @@
53
53
  {{ formatShortDate(item.createdAt) }}
54
54
  </td>
55
55
  <td class="px-3 py-2 text-right">
56
- <button class="text-gray-300 hover:text-red-500 text-xs" title="Delete item" @click="emit('delete', item.id)">✕</button>
56
+ <button class="text-gray-300 hover:text-red-500 text-xs" :title="t('pluginTodo.deleteItem')" @click="emit('delete', item.id)">✕</button>
57
57
  </td>
58
58
  </tr>
59
59
  <tr v-if="expandedId === item.id">
@@ -69,6 +69,7 @@
69
69
 
70
70
  <script setup lang="ts">
71
71
  import { computed, ref } from "vue";
72
+ import { useI18n } from "vue-i18n";
72
73
  import type { StatusColumn, TodoItem } from "../../plugins/todo/index";
73
74
  import { colorForLabel } from "../../plugins/todo/labels";
74
75
  import { PRIORITY_CLASSES, PRIORITY_LABELS, PRIORITY_ORDER, dueDateClasses, formatDueLabel } from "../../plugins/todo/priority";
@@ -76,6 +77,8 @@ import type { PatchItemInput } from "../../plugins/todo/composables/useTodos";
76
77
  import TodoEditPanel from "./TodoEditPanel.vue";
77
78
  import { formatShortDate } from "../../utils/format/date";
78
79
 
80
+ const { t } = useI18n();
81
+
79
82
  type SortKey = "completed" | "text" | "status" | "priority" | "labels" | "dueDate" | "createdAt";
80
83
  type SortDir = "asc" | "desc";
81
84
 
@@ -20,6 +20,15 @@ export interface AppApi {
20
20
  refreshRoles: () => void | Promise<void>;
21
21
  /** Send a chat message through App.vue's normal sendMessage pipeline. */
22
22
  sendMessage: (message: string) => void;
23
+ /**
24
+ * Open a fresh chat session (using the currently selected role) and
25
+ * send `message` as its first turn. Used by plugin views that want
26
+ * to kick off a new conversation instead of threading into whatever
27
+ * session happens to be active.
28
+ */
29
+ startNewChat: (message: string) => void;
30
+ /** Navigate to a workspace-internal link (wiki page, file, session). */
31
+ navigateToWorkspacePath: (href: string) => void;
23
32
  }
24
33
 
25
34
  const APP_API_KEY = Symbol("appApi");
@@ -16,9 +16,9 @@ interface UseClickOutsideOptions {
16
16
  export function useClickOutside(opts: UseClickOutsideOptions): {
17
17
  handler: (e: MouseEvent) => void;
18
18
  } {
19
- function handler(e: MouseEvent): void {
19
+ function handler(event: MouseEvent): void {
20
20
  if (!opts.isOpen.value) return;
21
- if (isClickOutside(e.target as Node | null, opts.buttonRef.value, opts.popupRef.value)) {
21
+ if (isClickOutside(event.target as Node | null, opts.buttonRef.value, opts.popupRef.value)) {
22
22
  opts.isOpen.value = false;
23
23
  }
24
24
  }