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
@@ -0,0 +1,93 @@
1
+ // Pure formatters for scheduler-task display. Extracted from
2
+ // TasksTab.vue so the UTC → local conversion can be unit-tested
3
+ // without spinning up Vue.
4
+ //
5
+ // Internally every task stores its daily trigger as `HH:MM` in UTC
6
+ // (that's what the scheduler engine fires on). The UI should show the
7
+ // same moment in the viewer's local timezone so a user in Tokyo sees
8
+ // "Daily 05:00 JST" instead of "Daily 20:00 UTC".
9
+
10
+ export interface DailySchedule {
11
+ type: "daily";
12
+ time: string; // "HH:MM" in UTC
13
+ }
14
+
15
+ export interface IntervalSchedule {
16
+ type: "interval";
17
+ intervalMs: number;
18
+ }
19
+
20
+ export type TaskSchedule = DailySchedule | IntervalSchedule | { type: string; [k: string]: unknown };
21
+
22
+ const DAILY_TIME_RE = /^(\d{1,2}):(\d{2})$/;
23
+
24
+ // Build a Date anchored to `now`'s local calendar day but at the
25
+ // requested UTC wall-clock hour/minute. Using today's date (rather
26
+ // than epoch 1970) makes the DST/TZ conversion accurate for the
27
+ // viewer's current moment — "20:00 UTC every day" in Europe/London
28
+ // can differ by an hour between summer and winter.
29
+ function buildUtcInstant(utcHour: number, utcMinute: number, now: Date): Date {
30
+ return new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate(), utcHour, utcMinute));
31
+ }
32
+
33
+ // Intl formatter configured to surface HH:MM + the short timezone
34
+ // name (e.g. "JST", "PDT"). When the browser can't resolve a zone
35
+ // abbreviation it falls back to the offset string ("GMT+9"), which
36
+ // is fine — the point is that the user doesn't have to mentally
37
+ // convert from UTC.
38
+ const LOCAL_TIME_FORMATTER = new Intl.DateTimeFormat(undefined, {
39
+ hour: "2-digit",
40
+ minute: "2-digit",
41
+ hour12: false,
42
+ timeZoneName: "short",
43
+ });
44
+
45
+ function extractHourMinuteTz(date: Date): { hourMinute: string; tzLabel: string } | null {
46
+ try {
47
+ const parts = LOCAL_TIME_FORMATTER.formatToParts(date);
48
+ const hour = parts.find((part) => part.type === "hour")?.value ?? "";
49
+ const minute = parts.find((part) => part.type === "minute")?.value ?? "";
50
+ const tzLabel = parts.find((part) => part.type === "timeZoneName")?.value ?? "";
51
+ if (!hour || !minute) return null;
52
+ // Intl returns "24" for midnight hour under `hour: "2-digit"` on
53
+ // some runtimes — normalize so "Daily 24:00 JST" never appears.
54
+ const normalizedHour = hour === "24" ? "00" : hour;
55
+ return { hourMinute: `${normalizedHour}:${minute}`, tzLabel };
56
+ } catch {
57
+ return null;
58
+ }
59
+ }
60
+
61
+ // Convert a UTC "HH:MM" into "Daily HH:MM <tz>" in the viewer's
62
+ // local zone. Returns the original "Daily HH:MM UTC" string if the
63
+ // input is malformed or the Intl machinery is unavailable — callers
64
+ // never see `null`/throw for a scheduler entry.
65
+ export function formatDailyLocal(utcHHMM: string, now: Date = new Date()): string {
66
+ const match = DAILY_TIME_RE.exec(utcHHMM);
67
+ if (!match) return `Daily ${utcHHMM} UTC`;
68
+ const hour = Number(match[1]);
69
+ const minute = Number(match[2]);
70
+ if (!Number.isFinite(hour) || !Number.isFinite(minute) || hour > 23 || minute > 59) {
71
+ return `Daily ${utcHHMM} UTC`;
72
+ }
73
+ const extracted = extractHourMinuteTz(buildUtcInstant(hour, minute, now));
74
+ if (!extracted) return `Daily ${utcHHMM} UTC`;
75
+ return `Daily ${extracted.hourMinute} ${extracted.tzLabel}`;
76
+ }
77
+
78
+ export function formatInterval(intervalMs: number): string {
79
+ if (!Number.isFinite(intervalMs) || intervalMs <= 0) return "Every ?";
80
+ const mins = Math.round(intervalMs / 60_000);
81
+ if (mins >= 60) return `Every ${Math.round(mins / 60)}h`;
82
+ return `Every ${mins}m`;
83
+ }
84
+
85
+ export function formatSchedule(schedule: TaskSchedule, now: Date = new Date()): string {
86
+ if (schedule.type === "interval" && typeof (schedule as IntervalSchedule).intervalMs === "number") {
87
+ return formatInterval((schedule as IntervalSchedule).intervalMs);
88
+ }
89
+ if (schedule.type === "daily" && typeof (schedule as DailySchedule).time === "string") {
90
+ return formatDailyLocal((schedule as DailySchedule).time, now);
91
+ }
92
+ return JSON.stringify(schedule);
93
+ }
@@ -1,24 +1,29 @@
1
1
  <template>
2
2
  <div class="text-center p-4 bg-green-50 dark:bg-green-900 rounded">
3
- <div class="text-green-600 dark:text-green-300 font-medium">📊 Spreadsheet</div>
3
+ <div class="text-green-600 dark:text-green-300 font-medium">{{ t("pluginSpreadsheet.previewLabel") }}</div>
4
4
  <div class="text-sm text-gray-800 dark:text-gray-200 mt-1 font-medium truncate">
5
5
  {{ displayTitle }}
6
6
  </div>
7
- <div v-if="sheetCount > 1" class="text-xs text-gray-600 dark:text-gray-400 mt-1">{{ sheetCount }} sheets</div>
7
+ <div v-if="sheetCount > 1" class="text-xs text-gray-600 dark:text-gray-400 mt-1">
8
+ {{ t("pluginSpreadsheet.previewSheets", sheetCount, { named: { count: sheetCount } }) }}
9
+ </div>
8
10
  </div>
9
11
  </template>
10
12
 
11
13
  <script setup lang="ts">
12
14
  import { computed } from "vue";
15
+ import { useI18n } from "vue-i18n";
13
16
  import type { ToolResult } from "gui-chat-protocol";
14
17
  import type { SpreadsheetToolData } from "./definition";
15
18
 
19
+ const { t } = useI18n();
20
+
16
21
  const props = defineProps<{
17
22
  result: ToolResult<SpreadsheetToolData>;
18
23
  }>();
19
24
 
20
25
  const displayTitle = computed(() => {
21
- return props.result.title || "Spreadsheet";
26
+ return props.result.title || t("pluginSpreadsheet.previewUntitled");
22
27
  });
23
28
 
24
29
  const sheetCount = computed(() => {
@@ -1,25 +1,25 @@
1
1
  <template>
2
2
  <div class="spreadsheet-container">
3
3
  <div v-if="loading" class="min-h-full p-8 flex items-center justify-center">
4
- <div class="text-gray-500">Loading spreadsheet...</div>
4
+ <div class="text-gray-500">{{ t("pluginSpreadsheet.loading") }}</div>
5
5
  </div>
6
6
  <div v-else-if="errorMessage" class="min-h-full p-8 flex items-center justify-center">
7
7
  <div class="error">{{ errorMessage }}</div>
8
8
  </div>
9
9
  <div v-else-if="!resolvedSheets || resolvedSheets.length === 0" class="min-h-full p-8 flex items-center justify-center">
10
- <div class="text-gray-500">No spreadsheet data available</div>
10
+ <div class="text-gray-500">{{ t("pluginSpreadsheet.noData") }}</div>
11
11
  </div>
12
12
  <template v-else>
13
13
  <div class="spreadsheet-content-wrapper">
14
14
  <div class="p-4">
15
15
  <div class="header">
16
16
  <h1 class="title">
17
- {{ selectedResult.title || "Spreadsheet" }}
17
+ {{ selectedResult.title || t("pluginSpreadsheet.untitled") }}
18
18
  </h1>
19
19
  <div class="button-group">
20
20
  <button class="download-btn excel-btn" @click="downloadExcel">
21
21
  <span class="material-icons">download</span>
22
- Excel
22
+ {{ t("pluginSpreadsheet.excel") }}
23
23
  </button>
24
24
  </div>
25
25
  </div>
@@ -43,9 +43,9 @@
43
43
 
44
44
  <!-- Collapsible Editor -->
45
45
  <details v-if="!miniEditorOpen" ref="editorDetails" class="spreadsheet-source">
46
- <summary>Edit Spreadsheet Data</summary>
46
+ <summary>{{ t("pluginSpreadsheet.editData") }}</summary>
47
47
  <textarea ref="editorTextarea" v-model="editableData" class="spreadsheet-editor" spellcheck="false" @input="handleDataEdit"></textarea>
48
- <button class="apply-btn" :disabled="!hasChanges" @click="applyChanges">Apply Changes</button>
48
+ <button class="apply-btn" :disabled="!hasChanges" @click="applyChanges">{{ t("pluginSpreadsheet.applyChanges") }}</button>
49
49
  </details>
50
50
 
51
51
  <!-- Mini Editor at Bottom -->
@@ -57,11 +57,11 @@
57
57
  <div class="radio-group">
58
58
  <label class="radio-option">
59
59
  <input v-model="miniEditorType" type="radio" value="string" />
60
- String
60
+ {{ t("pluginSpreadsheet.stringType") }}
61
61
  </label>
62
62
  <label class="radio-option">
63
63
  <input v-model="miniEditorType" type="radio" value="object" />
64
- Formula
64
+ {{ t("pluginSpreadsheet.formulaType") }}
65
65
  </label>
66
66
  </div>
67
67
 
@@ -71,7 +71,7 @@
71
71
  v-model="miniEditorValue"
72
72
  type="text"
73
73
  class="form-input"
74
- placeholder="Value"
74
+ :placeholder="t('pluginSpreadsheet.valuePlaceholder')"
75
75
  @keyup.enter="saveMiniEditor"
76
76
  />
77
77
 
@@ -81,13 +81,19 @@
81
81
  v-model="miniEditorFormula"
82
82
  type="text"
83
83
  class="form-input"
84
- placeholder="Value or Formula (e.g., 100 or SUM(B2:B11))"
84
+ :placeholder="t('pluginSpreadsheet.valueOrFormulaPlaceholder')"
85
+ @keyup.enter="saveMiniEditor"
86
+ />
87
+ <input
88
+ v-model="miniEditorFormat"
89
+ type="text"
90
+ class="form-input"
91
+ :placeholder="t('pluginSpreadsheet.formatPlaceholder')"
85
92
  @keyup.enter="saveMiniEditor"
86
93
  />
87
- <input v-model="miniEditorFormat" type="text" class="form-input" placeholder="Format (e.g., $#,##0.00)" @keyup.enter="saveMiniEditor" />
88
94
  </template>
89
95
 
90
- <button class="save-btn" @click="saveMiniEditor">Update</button>
96
+ <button class="save-btn" @click="saveMiniEditor">{{ t("pluginSpreadsheet.update") }}</button>
91
97
  <button class="cancel-btn" @click="closeMiniEditor">✕</button>
92
98
  </div>
93
99
  </div>
@@ -97,6 +103,7 @@
97
103
 
98
104
  <script setup lang="ts">
99
105
  import { computed, ref, watch, onMounted, onUnmounted } from "vue";
106
+ import { useI18n } from "vue-i18n";
100
107
  import * as XLSX from "xlsx";
101
108
  import type { ToolResult } from "gui-chat-protocol";
102
109
  import type { SpreadsheetToolData, SpreadsheetSheet } from "./definition";
@@ -119,6 +126,8 @@ import { API_ROUTES } from "../../config/apiRoutes";
119
126
  import type { FilesContentResponseLike } from "./engine/responseDecoder";
120
127
  import { isObj, isRecord } from "../../utils/types";
121
128
 
129
+ const { t } = useI18n();
130
+
122
131
  /**
123
132
  * Normalize malformed data structures
124
133
  * Some models generate flat arrays instead of 2D arrays - fix them
@@ -1,7 +1,5 @@
1
1
  <template>
2
- <div class="text-sm leading-snug" :class="textColorClass">
3
- <div class="preview-markdown" v-html="renderedMarkdown" />
4
- </div>
2
+ <div class="preview-text p-2 text-sm leading-snug" :class="textColorClass">{{ previewText }}</div>
5
3
  </template>
6
4
 
7
5
  <script setup lang="ts">
@@ -14,7 +12,6 @@ const props = defineProps<{
14
12
  result: ToolResultComplete<TextResponseData>;
15
13
  }>();
16
14
 
17
- const previewText = computed(() => props.result.data?.text ?? "");
18
15
  const messageRole = computed(() => props.result.data?.role ?? "assistant");
19
16
 
20
17
  const textColorClass = computed(() => {
@@ -28,67 +25,27 @@ const textColorClass = computed(() => {
28
25
  }
29
26
  });
30
27
 
31
- const renderedMarkdown = computed(() => marked(previewText.value, { breaks: true, gfm: true }));
28
+ const previewText = computed(() => markdownToPlainText(props.result.data?.text ?? ""));
29
+
30
+ function markdownToPlainText(markdown: string): string {
31
+ const html = marked(markdown, { breaks: true, gfm: true }) as string;
32
+ const spaced = html
33
+ .replace(/<\/(td|th)>/gi, " ")
34
+ .replace(/<\/(p|h[1-6]|li|tr|blockquote|pre|div)>/gi, "$&\n")
35
+ .replace(/<br\s*\/?>/gi, "\n");
36
+ const doc = new DOMParser().parseFromString(spaced, "text/html");
37
+ const text = doc.body.textContent ?? "";
38
+ return text.replace(/\n{3,}/g, "\n\n").trim();
39
+ }
32
40
  </script>
33
41
 
34
42
  <style scoped>
35
- .preview-markdown {
43
+ .preview-text {
36
44
  overflow: hidden;
37
45
  display: -webkit-box;
38
46
  -webkit-line-clamp: 5;
39
47
  -webkit-box-orient: vertical;
40
- /* Links inside v-html would otherwise hijack the row-level
41
- select click and navigate to the article URL. */
42
- pointer-events: none;
43
- }
44
-
45
- .preview-markdown :deep(p) {
46
- margin: 0;
47
- display: inline;
48
- }
49
-
50
- .preview-markdown :deep(h1),
51
- .preview-markdown :deep(h2),
52
- .preview-markdown :deep(h3),
53
- .preview-markdown :deep(h4),
54
- .preview-markdown :deep(h5),
55
- .preview-markdown :deep(h6) {
56
- font-size: inherit;
57
- font-weight: bold;
58
- display: inline;
59
- }
60
-
61
- .preview-markdown :deep(ul),
62
- .preview-markdown :deep(ol) {
63
- display: inline;
64
- margin: 0;
65
- padding: 0;
66
- list-style: none;
67
- }
68
-
69
- .preview-markdown :deep(li) {
70
- display: inline;
71
- }
72
-
73
- .preview-markdown :deep(li)::before {
74
- content: "• ";
75
- }
76
-
77
- .preview-markdown :deep(strong) {
78
- font-weight: bold;
79
- }
80
-
81
- .preview-markdown :deep(em) {
82
- font-style: italic;
83
- }
84
-
85
- .preview-markdown :deep(code) {
86
- font-family: monospace;
87
- font-size: 0.9em;
88
- }
89
-
90
- .preview-markdown :deep(pre) {
91
- display: inline;
92
48
  white-space: pre-wrap;
49
+ word-break: break-word;
93
50
  }
94
51
  </style>
@@ -4,10 +4,10 @@
4
4
  <div class="button-group">
5
5
  <button class="download-btn download-btn-green" :disabled="pdfDownloading" @click="downloadPdf">
6
6
  <span class="material-icons">{{ pdfDownloading ? "hourglass_empty" : "download" }}</span>
7
- PDF
7
+ {{ t("pluginTextResponse.pdf") }}
8
8
  </button>
9
9
  </div>
10
- <span v-if="pdfError" class="text-xs text-red-500 self-center ml-2" :title="pdfError">⚠ PDF failed</span>
10
+ <span v-if="pdfError" class="text-xs text-red-500 self-center ml-2" :title="pdfError">{{ t("pluginTextResponse.pdfFailed") }}</span>
11
11
  </div>
12
12
  <div class="flex-1 overflow-hidden relative" @click.capture="openLinksInNewTab">
13
13
  <div class="text-response-container">
@@ -27,28 +27,36 @@
27
27
 
28
28
  <!-- Collapsible Editor -->
29
29
  <details v-if="editable" ref="detailsEl" class="text-response-source" data-testid="text-response-edit">
30
- <summary data-testid="text-response-edit-summary">Edit Text Content</summary>
30
+ <summary data-testid="text-response-edit-summary">{{ t("pluginTextResponse.editContent") }}</summary>
31
31
  <textarea v-model="editedText" class="text-response-editor" spellcheck="false" data-testid="text-response-edit-textarea"></textarea>
32
- <button class="apply-btn" :disabled="!hasChanges" data-testid="text-response-apply-btn" @click="applyChanges">Apply Changes</button>
32
+ <button class="apply-btn" :disabled="!hasChanges" data-testid="text-response-apply-btn" @click="applyChanges">
33
+ {{ t("pluginTextResponse.applyChanges") }}
34
+ </button>
33
35
  </details>
34
36
  </div>
35
- <button v-show="!editing" class="copy-btn" :title="copied ? 'Copied!' : 'Copy'" @click="copyText">
37
+ <button v-show="!editing" class="copy-btn" :title="copied ? t('pluginTextResponse.copiedLabel') : t('pluginTextResponse.copyLabel')" @click="copyText">
36
38
  <span class="material-icons">{{ copied ? "check" : "content_copy" }}</span>
37
39
  </button>
38
- <button v-show="editing" class="cancel-btn" @click="cancelEdit">Cancel</button>
40
+ <button v-show="editing" class="cancel-btn" @click="cancelEdit">{{ t("pluginTextResponse.cancel") }}</button>
39
41
  </div>
40
42
  </div>
41
43
  </template>
42
44
 
43
45
  <script setup lang="ts">
44
46
  import { ref, computed, watch, onMounted, onBeforeUnmount } from "vue";
47
+ import { useI18n } from "vue-i18n";
45
48
  import { marked } from "marked";
46
49
  import type { ToolResult, ToolResultComplete } from "gui-chat-protocol/vue";
47
50
  import type { TextResponseData } from "./types";
48
51
  import { handleExternalLinkClick } from "../../utils/dom/externalLink";
52
+ import { classifyWorkspacePath } from "../../utils/path/workspaceLinkRouter";
53
+ import { useAppApi } from "../../composables/useAppApi";
49
54
  import { usePdfDownload } from "../../composables/usePdfDownload";
50
55
  import { useClipboardCopy } from "../../composables/useClipboardCopy";
51
56
 
57
+ const { t } = useI18n();
58
+ const appApi = useAppApi();
59
+
52
60
  const props = withDefaults(
53
61
  defineProps<{
54
62
  selectedResult: ToolResultComplete<TextResponseData>;
@@ -160,7 +168,19 @@ function applyChanges() {
160
168
  const isAssistant = computed(() => (props.selectedResult.data?.role ?? "assistant") === "assistant");
161
169
 
162
170
  function openLinksInNewTab(event: MouseEvent): void {
163
- handleExternalLinkClick(event);
171
+ if (handleExternalLinkClick(event)) return;
172
+ // Internal workspace-path links (rendered by marked from agent
173
+ // Markdown): route to the appropriate view instead of letting them
174
+ // navigate the SPA to a non-existent session route.
175
+ const target = event.target as HTMLElement;
176
+ const anchor = target.closest("a");
177
+ if (!anchor) return;
178
+ const href = anchor.getAttribute("href");
179
+ if (!href || href.startsWith("#")) return;
180
+ if (classifyWorkspacePath(href)) {
181
+ event.preventDefault();
182
+ appApi.navigateToWorkspacePath(href);
183
+ }
164
184
  }
165
185
 
166
186
  const { pdfDownloading, pdfError, downloadPdf: rawDownloadPdf } = usePdfDownload();
@@ -168,8 +188,8 @@ const { pdfDownloading, pdfError, downloadPdf: rawDownloadPdf } = usePdfDownload
168
188
  const detailsEl = ref<HTMLDetailsElement>();
169
189
  const editing = ref(false);
170
190
 
171
- function onDetailsToggle(e: Event) {
172
- editing.value = (e.target as HTMLDetailsElement).open;
191
+ function onDetailsToggle(event: Event) {
192
+ editing.value = (event.target as HTMLDetailsElement).open;
173
193
  }
174
194
 
175
195
  onMounted(() => {
@@ -1,8 +1,8 @@
1
1
  <template>
2
- <div class="text-sm">
2
+ <div class="p-2 text-sm">
3
3
  <div class="flex items-center gap-1 font-medium text-gray-700 mb-1">
4
- <span>☑</span>
5
- <span>{{ completedCount }}/{{ items.length }} completed</span>
4
+ <span aria-hidden="true">{{ t("todoPreview.headerIcon") }}</span>
5
+ <span>{{ t("todoPreview.completedRatio", { done: completedCount, total: items.length }) }}</span>
6
6
  </div>
7
7
  <div
8
8
  v-for="item in preview"
@@ -10,7 +10,7 @@
10
10
  class="text-xs truncate flex items-center gap-1"
11
11
  :class="item.completed ? 'line-through text-gray-400' : 'text-gray-600'"
12
12
  >
13
- <span class="shrink-0">{{ item.completed ? "" : "" }}</span>
13
+ <span class="shrink-0">{{ item.completed ? t("todoPreview.doneIcon") : t("todoPreview.pendingIcon") }}</span>
14
14
  <span class="truncate">{{ item.text }}</span>
15
15
  <template v-if="(item.labels?.length ?? 0) > 0">
16
16
  <span
@@ -20,16 +20,21 @@
20
20
  :class="colorForLabel(label)"
21
21
  >{{ label }}</span
22
22
  >
23
- <span v-if="(item.labels?.length ?? 0) > 2" class="text-[9px] text-gray-400 shrink-0">+{{ (item.labels?.length ?? 0) - 2 }}</span>
23
+ <span v-if="(item.labels?.length ?? 0) > 2" class="text-[9px] text-gray-400 shrink-0">{{
24
+ t("todoPreview.moreLabels", { count: (item.labels?.length ?? 0) - 2 })
25
+ }}</span>
24
26
  </template>
25
27
  </div>
26
- <div v-if="more > 0" class="text-xs text-gray-400">+ {{ more }} more…</div>
28
+ <div v-if="more > 0" class="text-xs text-gray-400">{{ t("todoPreview.moreItems", { count: more }) }}</div>
27
29
  </div>
28
30
  </template>
29
31
 
30
32
  <script setup lang="ts">
31
33
  import { computed, ref, watch } from "vue";
34
+ import { useI18n } from "vue-i18n";
32
35
  import type { ToolResultComplete } from "gui-chat-protocol/vue";
36
+
37
+ const { t } = useI18n();
33
38
  import type { TodoData, TodoItem } from "./index";
34
39
  import { useFreshPluginData } from "../../composables/useFreshPluginData";
35
40
  import { API_ROUTES } from "../../config/apiRoutes";
@@ -42,8 +47,8 @@ const items = ref<TodoItem[]>(props.result.data?.items ?? []);
42
47
  const { refresh } = useFreshPluginData<TodoItem[]>({
43
48
  endpoint: () => API_ROUTES.todos.list,
44
49
  extract: (json) => {
45
- const v = (json as { data?: { items?: TodoItem[] } }).data?.items;
46
- return Array.isArray(v) ? v : null;
50
+ const extracted = (json as { data?: { items?: TodoItem[] } }).data?.items;
51
+ return Array.isArray(extracted) ? extracted : null;
47
52
  },
48
53
  apply: (data) => {
49
54
  items.value = data;
@@ -3,16 +3,16 @@
3
3
  <!-- API error banner — surfaces POST /api/todos failures so a
4
4
  silent add/remove/toggle becomes diagnosable. -->
5
5
  <div v-if="todoApiError" class="px-4 py-2 bg-red-50 border-b border-red-200 text-sm text-red-700" role="alert" data-testid="todo-api-error">
6
- Failed to update todos: {{ todoApiError }}
6
+ {{ t("pluginTodo.apiError", { error: todoApiError }) }}
7
7
  </div>
8
8
  <div class="flex items-center justify-between px-6 py-4 border-b border-gray-100">
9
- <h2 class="text-lg font-semibold text-gray-800">Todo List</h2>
10
- <span class="text-sm text-gray-500">{{ completedCount }}/{{ items.length }} completed</span>
9
+ <h2 class="text-lg font-semibold text-gray-800">{{ t("pluginTodo.heading") }}</h2>
10
+ <span class="text-sm text-gray-500">{{ t("pluginTodo.completedRatio", { done: completedCount, total: items.length }) }}</span>
11
11
  </div>
12
12
 
13
13
  <!-- Filter bar: only shown when at least one label is in use. -->
14
14
  <div v-if="labelInventory.length > 0" class="flex flex-wrap items-center gap-1.5 px-6 py-2 border-b border-gray-100 bg-gray-50">
15
- <span class="text-xs text-gray-500 mr-1">Filter:</span>
15
+ <span class="text-xs text-gray-500 mr-1">{{ t("pluginTodo.filter") }}</span>
16
16
  <button
17
17
  v-for="entry in labelInventory"
18
18
  :key="entry.label"
@@ -27,14 +27,21 @@
27
27
  {{ entry.label }}
28
28
  <span class="opacity-60">{{ entry.count }}</span>
29
29
  </button>
30
- <button v-if="activeFilters.size > 0" class="ml-auto text-xs text-gray-500 hover:text-gray-700" title="Clear all filters" @click="clearFilters">
31
- Clear
30
+ <button
31
+ v-if="activeFilters.size > 0"
32
+ class="ml-auto text-xs text-gray-500 hover:text-gray-700"
33
+ :title="t('pluginTodo.clearFilters')"
34
+ @click="clearFilters"
35
+ >
36
+ {{ t("pluginTodo.clearButton") }}
32
37
  </button>
33
38
  </div>
34
39
 
35
- <div v-if="items.length === 0" class="flex-1 flex items-center justify-center text-gray-400">No todo items yet</div>
40
+ <div v-if="items.length === 0" class="flex-1 flex items-center justify-center text-gray-400">{{ t("pluginTodo.noItems") }}</div>
36
41
 
37
- <div v-else-if="filteredItems.length === 0" class="flex-1 flex items-center justify-center text-gray-400 text-sm">No items match the active filter</div>
42
+ <div v-else-if="filteredItems.length === 0" class="flex-1 flex items-center justify-center text-gray-400 text-sm">
43
+ {{ t("pluginTodo.noMatchingFilter") }}
44
+ </div>
38
45
 
39
46
  <ul v-else class="flex-1 overflow-y-auto p-4 space-y-2">
40
47
  <li v-for="item in filteredItems" :key="item.id" class="rounded-lg border" :class="selectedId === item.id ? 'border-blue-400' : 'border-gray-200'">
@@ -62,10 +69,10 @@
62
69
  </div>
63
70
  <button
64
71
  class="opacity-0 group-hover:opacity-100 text-gray-400 hover:text-red-500 text-xs px-1 shrink-0"
65
- title="Delete item"
72
+ :title="t('pluginTodo.deleteItem')"
66
73
  @click.stop="remove(item)"
67
74
  >
68
-
75
+ {{ t("pluginTodo.deleteSymbol") }}
69
76
  </button>
70
77
  <span class="material-icons text-gray-400 text-sm" :title="selectedId === item.id ? 'Collapse' : 'Expand'">
71
78
  {{ selectedId === item.id ? "expand_less" : "expand_more" }}
@@ -80,20 +87,25 @@
80
87
  spellcheck="false"
81
88
  />
82
89
  <div class="flex items-center gap-2">
83
- <button class="px-3 py-1.5 text-sm rounded bg-blue-500 text-white hover:bg-blue-600" @click="applyItemEdit">Update</button>
84
- <button class="px-3 py-1.5 text-sm rounded border border-gray-300 text-gray-600 hover:bg-gray-50" @click="selectedId = null">Cancel</button>
90
+ <button class="px-3 py-1.5 text-sm rounded bg-blue-500 text-white hover:bg-blue-600" @click="applyItemEdit">{{ t("pluginTodo.update") }}</button>
91
+ <button class="px-3 py-1.5 text-sm rounded border border-gray-300 text-gray-600 hover:bg-gray-50" @click="selectedId = null">
92
+ {{ t("common.cancel") }}
93
+ </button>
85
94
  <span v-if="yamlError" class="text-xs text-red-500">{{ yamlError }}</span>
86
95
  </div>
87
96
  </div>
88
97
  </li>
89
98
  </ul>
90
99
 
91
- <button v-if="hasCompleted" class="mx-6 mb-2 text-sm text-gray-500 hover:text-gray-700 self-start" @click="clearCompleted">Clear completed</button>
100
+ <button v-if="hasCompleted" class="mx-6 mb-2 text-sm text-gray-500 hover:text-gray-700 self-start" @click="clearCompleted">
101
+ {{ t("pluginTodo.clearCompleted") }}
102
+ </button>
92
103
  </div>
93
104
  </template>
94
105
 
95
106
  <script setup lang="ts">
96
107
  import { computed, ref, watch } from "vue";
108
+ import { useI18n } from "vue-i18n";
97
109
  import type { ToolResultComplete } from "gui-chat-protocol/vue";
98
110
  import type { TodoData, TodoItem } from "./index";
99
111
  import { useFreshPluginData } from "../../composables/useFreshPluginData";
@@ -101,6 +113,8 @@ import { apiPost } from "../../utils/api";
101
113
  import { API_ROUTES } from "../../config/apiRoutes";
102
114
  import { colorForLabel, filterByLabels, listLabelsWithCount, subtractLabels } from "./labels";
103
115
 
116
+ const { t } = useI18n();
117
+
104
118
  const props = defineProps<{
105
119
  selectedResult: ToolResultComplete<TodoData>;
106
120
  }>();
@@ -111,8 +125,8 @@ const items = ref<TodoItem[]>(props.selectedResult.data?.items ?? []);
111
125
  const { refresh } = useFreshPluginData<TodoItem[]>({
112
126
  endpoint: () => API_ROUTES.todos.list,
113
127
  extract: (json) => {
114
- const v = (json as { data?: { items?: TodoItem[] } }).data?.items;
115
- return Array.isArray(v) ? v : null;
128
+ const extracted = (json as { data?: { items?: TodoItem[] } }).data?.items;
129
+ return Array.isArray(extracted) ? extracted : null;
116
130
  },
117
131
  apply: (data) => {
118
132
  items.value = data;
@@ -163,12 +177,12 @@ function clearFilters(): void {
163
177
 
164
178
  // ── YAML helpers ─────────────────────────────────────────────────────────────
165
179
 
166
- function yamlStringValue(v: string): string {
167
- const needsQuotes = v === "" || /[:#[\]{},&*?|<>=!%@`]/.test(v) || /^\s|\s$/.test(v) || /^(true|false|null|~)$/i.test(v) || /^\d/.test(v);
180
+ function yamlStringValue(str: string): string {
181
+ const needsQuotes = str === "" || /[:#[\]{},&*?|<>=!%@`]/.test(str) || /^\s|\s$/.test(str) || /^(true|false|null|~)$/i.test(str) || /^\d/.test(str);
168
182
  if (needsQuotes) {
169
- return `"${v.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
183
+ return `"${str.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
170
184
  }
171
- return v;
185
+ return str;
172
186
  }
173
187
 
174
188
  function serializeYaml(item: TodoItem): string {
@@ -191,19 +205,19 @@ function parseFlowSequence(raw: string): string[] {
191
205
  let buffer = "";
192
206
  let inQuotes = false;
193
207
  for (let i = 0; i < inner.length; i++) {
194
- const ch = inner[i];
195
- if (ch === '"' && inner[i - 1] !== "\\") {
208
+ const char = inner[i];
209
+ if (char === '"' && inner[i - 1] !== "\\") {
196
210
  inQuotes = !inQuotes;
197
- buffer += ch;
211
+ buffer += char;
198
212
  continue;
199
213
  }
200
- if (ch === "," && !inQuotes) {
214
+ if (char === "," && !inQuotes) {
201
215
  const piece = parseYamlValue(buffer.trim());
202
216
  if (piece) result.push(piece);
203
217
  buffer = "";
204
218
  continue;
205
219
  }
206
- buffer += ch;
220
+ buffer += char;
207
221
  }
208
222
  const last = parseYamlValue(buffer.trim());
209
223
  if (last) result.push(last);
@@ -166,12 +166,12 @@ export function useTodos(initialItems: TodoItem[] = [], initialColumns: StatusCo
166
166
  error,
167
167
  refresh,
168
168
  createItem: (input) => call(API_ROUTES.todos.items, "POST", input),
169
- patchItem: (id, input) => call(API_ROUTES.todos.item.replace(":id", encodeURIComponent(id)), "PATCH", input),
170
- moveItem: (id, input) => call(API_ROUTES.todos.itemMove.replace(":id", encodeURIComponent(id)), "POST", input),
171
- deleteItem: (id) => call(API_ROUTES.todos.item.replace(":id", encodeURIComponent(id)), "DELETE"),
169
+ patchItem: (itemId, input) => call(API_ROUTES.todos.item.replace(":id", encodeURIComponent(itemId)), "PATCH", input),
170
+ moveItem: (itemId, input) => call(API_ROUTES.todos.itemMove.replace(":id", encodeURIComponent(itemId)), "POST", input),
171
+ deleteItem: (itemId) => call(API_ROUTES.todos.item.replace(":id", encodeURIComponent(itemId)), "DELETE"),
172
172
  addColumn: (input) => call(API_ROUTES.todos.columns, "POST", input),
173
- patchColumn: (id, input) => call(API_ROUTES.todos.column.replace(":id", encodeURIComponent(id)), "PATCH", input),
174
- deleteColumn: (id) => call(API_ROUTES.todos.column.replace(":id", encodeURIComponent(id)), "DELETE"),
173
+ patchColumn: (colId, input) => call(API_ROUTES.todos.column.replace(":id", encodeURIComponent(colId)), "PATCH", input),
174
+ deleteColumn: (colId) => call(API_ROUTES.todos.column.replace(":id", encodeURIComponent(colId)), "DELETE"),
175
175
  reorderColumns: (ids) => call(API_ROUTES.todos.columnsOrder, "PUT", { ids }),
176
176
  };
177
177
  }