kanban-lite 1.0.21 → 1.0.23
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/{CLAUDE.md → AGENTS.md} +13 -0
- package/CHANGELOG.md +68 -0
- package/README.md +10 -0
- package/dist/cli.js +168 -102
- package/dist/extension.js +178 -104
- package/dist/mcp-server.js +145 -95
- package/dist/sdk/index.cjs +126 -93
- package/dist/sdk/index.mjs +126 -93
- package/dist/sdk/sdk/KanbanSDK.d.ts +39 -7
- package/dist/sdk/shared/config.d.ts +4 -0
- package/dist/sdk/shared/types.d.ts +4 -0
- package/dist/standalone-webview/index.js +58 -58
- package/dist/standalone-webview/index.js.map +1 -1
- package/dist/standalone-webview/style.css +1 -1
- package/dist/standalone.js +606 -364
- package/dist/webview/index.js +57 -57
- package/dist/webview/index.js.map +1 -1
- package/dist/webview/style.css +1 -1
- package/docs/plans/2026-02-26-settings-tabs-design.md +40 -0
- package/docs/plans/2026-02-26-settings-tabs.md +166 -0
- package/docs/plans/2026-02-27-zoom-settings-design.md +82 -0
- package/docs/plans/2026-02-27-zoom-settings.md +395 -0
- package/docs/sdk.md +3 -6
- package/package.json +1 -1
- package/src/cli/index.ts +12 -2
- package/src/extension/KanbanPanel.ts +25 -5
- package/src/mcp-server/index.ts +20 -2
- package/src/sdk/KanbanSDK.ts +64 -7
- package/src/sdk/__tests__/KanbanSDK.test.ts +17 -1
- package/src/sdk/__tests__/metadata.test.ts +3 -1
- package/src/sdk/__tests__/multi-board.test.ts +2 -0
- package/src/sdk/parser.ts +50 -83
- package/src/shared/config.ts +14 -2
- package/src/shared/types.ts +4 -0
- package/src/standalone/__tests__/server.integration.test.ts +2 -2
- package/src/standalone/index.ts +7 -4
- package/src/standalone/server.ts +31 -6
- package/src/webview/App.tsx +42 -3
- package/src/webview/assets/main.css +31 -2
- package/src/webview/components/KanbanBoard.tsx +35 -3
- package/src/webview/components/KanbanColumn.tsx +40 -4
- package/src/webview/components/SettingsPanel.tsx +179 -77
- package/src/webview/components/Toolbar.tsx +127 -32
- package/src/webview/store/index.ts +26 -28
|
@@ -0,0 +1,395 @@
|
|
|
1
|
+
# Zoom Settings Implementation Plan
|
|
2
|
+
|
|
3
|
+
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
|
|
4
|
+
|
|
5
|
+
**Goal:** Add two zoom settings (board zoom + card detail zoom) with sliders and keyboard shortcuts so users can scale font sizes in the kanban UI.
|
|
6
|
+
|
|
7
|
+
**Architecture:** Two numeric settings (`boardZoom`, `cardZoom`) stored in `CardDisplaySettings` and `.kanban.json`. CSS custom properties `--board-zoom` and `--card-zoom` are set on `:root` and used in `calc()` multipliers on font-size rules. A `useEffect` in `App.tsx` syncs settings to CSS vars. Keyboard shortcuts adjust zoom by ±5%.
|
|
8
|
+
|
|
9
|
+
**Tech Stack:** TypeScript, React, Zustand, Tailwind CSS, CSS custom properties
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
### Task 1: Add zoom fields to CardDisplaySettings type
|
|
14
|
+
|
|
15
|
+
**Files:**
|
|
16
|
+
- Modify: `src/shared/types.ts:230-253`
|
|
17
|
+
|
|
18
|
+
**Step 1: Add boardZoom and cardZoom to CardDisplaySettings**
|
|
19
|
+
|
|
20
|
+
In `src/shared/types.ts`, add two new fields at the end of the `CardDisplaySettings` interface (before the closing `}`):
|
|
21
|
+
|
|
22
|
+
```typescript
|
|
23
|
+
/** Zoom level for the board view as a percentage (75–150). Default 100. */
|
|
24
|
+
boardZoom: number
|
|
25
|
+
/** Zoom level for the card detail panel as a percentage (75–150). Default 100. */
|
|
26
|
+
cardZoom: number
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
**Step 2: Verify types compile**
|
|
30
|
+
|
|
31
|
+
Run: `npx tsc --noEmit 2>&1 | head -20`
|
|
32
|
+
Expected: Type errors in files that construct `CardDisplaySettings` without the new fields (config.ts, store/index.ts). This is expected — we fix them in the next tasks.
|
|
33
|
+
|
|
34
|
+
**Step 3: Commit**
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
git add src/shared/types.ts
|
|
38
|
+
git commit -m "feat(types): add boardZoom and cardZoom to CardDisplaySettings"
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
### Task 2: Add zoom defaults to config and store
|
|
44
|
+
|
|
45
|
+
**Files:**
|
|
46
|
+
- Modify: `src/shared/config.ts:54-95` (KanbanConfig interface)
|
|
47
|
+
- Modify: `src/shared/config.ts:129-150` (DEFAULT_CONFIG)
|
|
48
|
+
- Modify: `src/shared/config.ts:377-391` (configToSettings)
|
|
49
|
+
- Modify: `src/shared/config.ts:406-419` (settingsToConfig)
|
|
50
|
+
- Modify: `src/webview/store/index.ts:122-134` (initial cardSettings)
|
|
51
|
+
|
|
52
|
+
**Step 1: Add zoom fields to KanbanConfig interface**
|
|
53
|
+
|
|
54
|
+
In `src/shared/config.ts`, add after the `showDeletedColumn` field (line 86) in the `KanbanConfig` interface:
|
|
55
|
+
|
|
56
|
+
```typescript
|
|
57
|
+
/** Zoom level for the board view (75–150). */
|
|
58
|
+
boardZoom: number
|
|
59
|
+
/** Zoom level for the card detail panel (75–150). */
|
|
60
|
+
cardZoom: number
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
**Step 2: Add defaults to DEFAULT_CONFIG**
|
|
64
|
+
|
|
65
|
+
In `src/shared/config.ts`, add after `showDeletedColumn: false,` (line 147):
|
|
66
|
+
|
|
67
|
+
```typescript
|
|
68
|
+
boardZoom: 100,
|
|
69
|
+
cardZoom: 100,
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
**Step 3: Add zoom to configToSettings**
|
|
73
|
+
|
|
74
|
+
In `src/shared/config.ts`, in the `configToSettings` function, add after the `defaultStatus` line (line 389):
|
|
75
|
+
|
|
76
|
+
```typescript
|
|
77
|
+
boardZoom: config.boardZoom,
|
|
78
|
+
cardZoom: config.cardZoom
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
**Step 4: Add zoom to settingsToConfig**
|
|
82
|
+
|
|
83
|
+
In `src/shared/config.ts`, in the `settingsToConfig` function, add after the `defaultStatus` line (line 417):
|
|
84
|
+
|
|
85
|
+
```typescript
|
|
86
|
+
boardZoom: settings.boardZoom,
|
|
87
|
+
cardZoom: settings.cardZoom
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
**Step 5: Add defaults to Zustand store**
|
|
91
|
+
|
|
92
|
+
In `src/webview/store/index.ts`, add after `defaultStatus: 'backlog'` (line 133) in the initial `cardSettings`:
|
|
93
|
+
|
|
94
|
+
```typescript
|
|
95
|
+
boardZoom: 100,
|
|
96
|
+
cardZoom: 100
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
**Step 6: Verify types compile**
|
|
100
|
+
|
|
101
|
+
Run: `npx tsc --noEmit`
|
|
102
|
+
Expected: Clean compile (0 errors)
|
|
103
|
+
|
|
104
|
+
**Step 7: Commit**
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
git add src/shared/config.ts src/webview/store/index.ts
|
|
108
|
+
git commit -m "feat(config): add boardZoom and cardZoom defaults"
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
### Task 3: Add CSS zoom custom properties and calc() multipliers
|
|
114
|
+
|
|
115
|
+
**Files:**
|
|
116
|
+
- Modify: `src/webview/assets/main.css`
|
|
117
|
+
- Modify: `src/webview/App.tsx`
|
|
118
|
+
|
|
119
|
+
**Step 1: Add useEffect to App.tsx to sync CSS vars**
|
|
120
|
+
|
|
121
|
+
In `src/webview/App.tsx`, add a new `useEffect` after the existing theme-change effect (after line 203):
|
|
122
|
+
|
|
123
|
+
```typescript
|
|
124
|
+
// Sync zoom CSS custom properties
|
|
125
|
+
useEffect(() => {
|
|
126
|
+
const root = document.documentElement
|
|
127
|
+
root.style.setProperty('--board-zoom', String(cardSettings.boardZoom / 100))
|
|
128
|
+
root.style.setProperty('--card-zoom', String(cardSettings.cardZoom / 100))
|
|
129
|
+
}, [cardSettings.boardZoom, cardSettings.cardZoom])
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
**Step 2: Add zoom multipliers to comment-markdown in main.css**
|
|
133
|
+
|
|
134
|
+
In `src/webview/assets/main.css`, change the `.comment-markdown` font-size (line 333) from:
|
|
135
|
+
|
|
136
|
+
```css
|
|
137
|
+
font-size: 0.75rem;
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
to:
|
|
141
|
+
|
|
142
|
+
```css
|
|
143
|
+
font-size: calc(0.75rem * var(--card-zoom, 1));
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
**Step 3: Add zoom multiplier to prose base**
|
|
147
|
+
|
|
148
|
+
In `src/webview/assets/main.css`, add a font-size to the `.prose` rule (line 78), changing from:
|
|
149
|
+
|
|
150
|
+
```css
|
|
151
|
+
.prose {
|
|
152
|
+
line-height: 1.625;
|
|
153
|
+
}
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
to:
|
|
157
|
+
|
|
158
|
+
```css
|
|
159
|
+
.prose {
|
|
160
|
+
font-size: calc(1rem * var(--card-zoom, 1));
|
|
161
|
+
line-height: 1.625;
|
|
162
|
+
}
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
**Step 4: Add zoom multiplier to markdown editor textarea**
|
|
166
|
+
|
|
167
|
+
In `src/webview/assets/main.css`, change the `.markdown-editor-textarea` font-size (line 284) from:
|
|
168
|
+
|
|
169
|
+
```css
|
|
170
|
+
font-size: var(--vscode-editor-font-size, 13px);
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
to:
|
|
174
|
+
|
|
175
|
+
```css
|
|
176
|
+
font-size: calc(var(--vscode-editor-font-size, 13px) * var(--card-zoom, 1));
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
**Step 5: Add board-zoom scoping class**
|
|
180
|
+
|
|
181
|
+
In `src/webview/assets/main.css`, add at the end of the file:
|
|
182
|
+
|
|
183
|
+
```css
|
|
184
|
+
/* Board zoom scaling */
|
|
185
|
+
.board-zoom-scope {
|
|
186
|
+
font-size: calc(1em * var(--board-zoom, 1));
|
|
187
|
+
}
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
**Step 6: Apply board-zoom-scope class to board container**
|
|
191
|
+
|
|
192
|
+
In `src/webview/App.tsx`, find the board container div (line 486):
|
|
193
|
+
|
|
194
|
+
```tsx
|
|
195
|
+
<div className={editingFeature ? 'w-1/2' : 'w-full'}>
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
Change to:
|
|
199
|
+
|
|
200
|
+
```tsx
|
|
201
|
+
<div className={`board-zoom-scope ${editingFeature ? 'w-1/2' : 'w-full'}`}>
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
**Step 7: Apply card-zoom scoping to card detail container**
|
|
205
|
+
|
|
206
|
+
In `src/webview/App.tsx`, find the card detail container div (line 498):
|
|
207
|
+
|
|
208
|
+
```tsx
|
|
209
|
+
<div className="w-1/2">
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
Change to:
|
|
213
|
+
|
|
214
|
+
```tsx
|
|
215
|
+
<div className="w-1/2" style={{ fontSize: `calc(1em * var(--card-zoom, 1))` }}>
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
**Step 8: Build and verify**
|
|
219
|
+
|
|
220
|
+
Run: `npm run build`
|
|
221
|
+
Expected: Clean build. Open the board — everything should look the same at default zoom (100%).
|
|
222
|
+
|
|
223
|
+
**Step 9: Commit**
|
|
224
|
+
|
|
225
|
+
```bash
|
|
226
|
+
git add src/webview/assets/main.css src/webview/App.tsx
|
|
227
|
+
git commit -m "feat(zoom): add CSS custom properties and calc() multipliers for zoom"
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
---
|
|
231
|
+
|
|
232
|
+
### Task 4: Add SettingsSlider component and Zoom section to Settings panel
|
|
233
|
+
|
|
234
|
+
**Files:**
|
|
235
|
+
- Modify: `src/webview/components/SettingsPanel.tsx`
|
|
236
|
+
|
|
237
|
+
**Step 1: Add SettingsSlider component**
|
|
238
|
+
|
|
239
|
+
In `src/webview/components/SettingsPanel.tsx`, add a new component after the `SettingsDropdown` component (after line 183):
|
|
240
|
+
|
|
241
|
+
```typescript
|
|
242
|
+
function SettingsSlider({ label, description, value, min, max, step, unit, onChange }: {
|
|
243
|
+
label: string
|
|
244
|
+
description?: string
|
|
245
|
+
value: number
|
|
246
|
+
min: number
|
|
247
|
+
max: number
|
|
248
|
+
step: number
|
|
249
|
+
unit?: string
|
|
250
|
+
onChange: (v: number) => void
|
|
251
|
+
}) {
|
|
252
|
+
return (
|
|
253
|
+
<div
|
|
254
|
+
className="flex items-center justify-between gap-4 px-4 py-2 transition-colors"
|
|
255
|
+
onMouseEnter={e => e.currentTarget.style.background = 'var(--vscode-list-hoverBackground)'}
|
|
256
|
+
onMouseLeave={e => e.currentTarget.style.background = 'transparent'}
|
|
257
|
+
>
|
|
258
|
+
<div className="flex-1 min-w-0">
|
|
259
|
+
<div className="text-sm" style={{ color: 'var(--vscode-foreground)' }}>{label}</div>
|
|
260
|
+
{description && (
|
|
261
|
+
<div className="text-xs mt-0.5" style={{ color: 'var(--vscode-descriptionForeground)' }}>{description}</div>
|
|
262
|
+
)}
|
|
263
|
+
</div>
|
|
264
|
+
<div className="flex items-center gap-2 shrink-0">
|
|
265
|
+
<input
|
|
266
|
+
type="range"
|
|
267
|
+
min={min}
|
|
268
|
+
max={max}
|
|
269
|
+
step={step}
|
|
270
|
+
value={value}
|
|
271
|
+
onChange={e => onChange(Number(e.target.value))}
|
|
272
|
+
className="w-20 h-1 rounded-full appearance-none cursor-pointer"
|
|
273
|
+
style={{
|
|
274
|
+
background: `linear-gradient(to right, var(--vscode-button-background) ${((value - min) / (max - min)) * 100}%, var(--vscode-badge-background, #6b7280) ${((value - min) / (max - min)) * 100}%)`,
|
|
275
|
+
}}
|
|
276
|
+
/>
|
|
277
|
+
<span
|
|
278
|
+
className="text-xs font-mono w-10 text-right"
|
|
279
|
+
style={{ color: 'var(--vscode-foreground)' }}
|
|
280
|
+
>
|
|
281
|
+
{value}{unit || '%'}
|
|
282
|
+
</span>
|
|
283
|
+
</div>
|
|
284
|
+
</div>
|
|
285
|
+
)
|
|
286
|
+
}
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
**Step 2: Add Zoom section to General tab**
|
|
290
|
+
|
|
291
|
+
In `src/webview/components/SettingsPanel.tsx`, inside the General tab content (after the Card Display `SettingsSection` closing tag around line 647), add:
|
|
292
|
+
|
|
293
|
+
```tsx
|
|
294
|
+
<div style={{ borderTop: '1px solid var(--vscode-panel-border)' }} />
|
|
295
|
+
<SettingsSection title="Zoom">
|
|
296
|
+
<SettingsSlider
|
|
297
|
+
label="Board Zoom"
|
|
298
|
+
description="Scale text size on the board view"
|
|
299
|
+
value={local.boardZoom}
|
|
300
|
+
min={75}
|
|
301
|
+
max={150}
|
|
302
|
+
step={5}
|
|
303
|
+
onChange={v => update({ boardZoom: v })}
|
|
304
|
+
/>
|
|
305
|
+
<SettingsSlider
|
|
306
|
+
label="Card Detail Zoom"
|
|
307
|
+
description="Scale text size in the card detail panel"
|
|
308
|
+
value={local.cardZoom}
|
|
309
|
+
min={75}
|
|
310
|
+
max={150}
|
|
311
|
+
step={5}
|
|
312
|
+
onChange={v => update({ cardZoom: v })}
|
|
313
|
+
/>
|
|
314
|
+
</SettingsSection>
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
**Step 3: Build and verify**
|
|
318
|
+
|
|
319
|
+
Run: `npm run build`
|
|
320
|
+
Expected: Clean build. Open Settings → General tab → see Zoom section with two sliders at 100%.
|
|
321
|
+
|
|
322
|
+
**Step 4: Commit**
|
|
323
|
+
|
|
324
|
+
```bash
|
|
325
|
+
git add src/webview/components/SettingsPanel.tsx
|
|
326
|
+
git commit -m "feat(settings): add zoom sliders to General tab"
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
---
|
|
330
|
+
|
|
331
|
+
### Task 5: Add keyboard shortcuts for zoom
|
|
332
|
+
|
|
333
|
+
**Files:**
|
|
334
|
+
- Modify: `src/webview/App.tsx`
|
|
335
|
+
|
|
336
|
+
**Step 1: Add zoom keyboard shortcuts**
|
|
337
|
+
|
|
338
|
+
In `src/webview/App.tsx`, in the existing keyboard shortcuts `useEffect` (starting at line 109), add zoom handling inside the `handleKeyDown` function. Add this block **before** the `// Ignore if user is typing in an input` check (before line 134), right after the Ctrl+Z undo block:
|
|
339
|
+
|
|
340
|
+
```typescript
|
|
341
|
+
// Ctrl/Cmd +/- for board zoom, Ctrl/Cmd+Shift +/- for card detail zoom
|
|
342
|
+
if ((e.key === '=' || e.key === '+' || e.key === '-') && (e.ctrlKey || e.metaKey)) {
|
|
343
|
+
e.preventDefault()
|
|
344
|
+
const delta = (e.key === '-') ? -5 : 5
|
|
345
|
+
const { cardSettings } = useStore.getState()
|
|
346
|
+
if (e.shiftKey) {
|
|
347
|
+
const newZoom = Math.max(75, Math.min(150, cardSettings.cardZoom + delta))
|
|
348
|
+
if (newZoom !== cardSettings.cardZoom) {
|
|
349
|
+
const next = { ...cardSettings, cardZoom: newZoom }
|
|
350
|
+
setCardSettings(next)
|
|
351
|
+
vscode.postMessage({ type: 'saveSettings', settings: next })
|
|
352
|
+
}
|
|
353
|
+
} else {
|
|
354
|
+
const newZoom = Math.max(75, Math.min(150, cardSettings.boardZoom + delta))
|
|
355
|
+
if (newZoom !== cardSettings.boardZoom) {
|
|
356
|
+
const next = { ...cardSettings, boardZoom: newZoom }
|
|
357
|
+
setCardSettings(next)
|
|
358
|
+
vscode.postMessage({ type: 'saveSettings', settings: next })
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
return
|
|
362
|
+
}
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
**Step 2: Build and verify**
|
|
366
|
+
|
|
367
|
+
Run: `npm run build`
|
|
368
|
+
Expected: Clean build. Ctrl+= increases board zoom, Ctrl+- decreases it. Ctrl+Shift+= increases card detail zoom, Ctrl+Shift+- decreases it.
|
|
369
|
+
|
|
370
|
+
**Step 3: Commit**
|
|
371
|
+
|
|
372
|
+
```bash
|
|
373
|
+
git add src/webview/App.tsx
|
|
374
|
+
git commit -m "feat(zoom): add Ctrl+/- keyboard shortcuts for zoom"
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
---
|
|
378
|
+
|
|
379
|
+
### Task 6: Type-check and final build
|
|
380
|
+
|
|
381
|
+
**Files:** None (verification only)
|
|
382
|
+
|
|
383
|
+
**Step 1: Type-check**
|
|
384
|
+
|
|
385
|
+
Run: `npx tsc --noEmit`
|
|
386
|
+
Expected: 0 errors
|
|
387
|
+
|
|
388
|
+
**Step 2: Full build**
|
|
389
|
+
|
|
390
|
+
Run: `npm run build`
|
|
391
|
+
Expected: Clean build with no warnings related to zoom
|
|
392
|
+
|
|
393
|
+
**Step 3: Final commit (if any fixups needed)**
|
|
394
|
+
|
|
395
|
+
Only commit if fixes were needed. Otherwise, the feature is complete.
|
package/docs/sdk.md
CHANGED
|
@@ -730,11 +730,8 @@ sdk.setLabel('docs', { color: '#16a34a' })
|
|
|
730
730
|
<a name="KanbanSDK+deleteLabel"></a>
|
|
731
731
|
|
|
732
732
|
#### kanbanSDK.deleteLabel(name)
|
|
733
|
-
Removes a label definition from the workspace configuration
|
|
734
|
-
|
|
735
|
-
This only removes the color/group definition — cards that use this
|
|
736
|
-
label keep their label strings. Those labels will render with default
|
|
737
|
-
gray styling in the UI.
|
|
733
|
+
Removes a label definition from the workspace configuration and cascades
|
|
734
|
+
the deletion to all cards by removing the label from their `labels` array.
|
|
738
735
|
|
|
739
736
|
**Kind**: instance method of [<code>KanbanSDK</code>](#KanbanSDK)
|
|
740
737
|
|
|
@@ -744,7 +741,7 @@ gray styling in the UI.
|
|
|
744
741
|
|
|
745
742
|
**Example**
|
|
746
743
|
```ts
|
|
747
|
-
sdk.deleteLabel('bug')
|
|
744
|
+
await sdk.deleteLabel('bug')
|
|
748
745
|
```
|
|
749
746
|
|
|
750
747
|
* * *
|
package/package.json
CHANGED
package/src/cli/index.ts
CHANGED
|
@@ -825,9 +825,19 @@ async function cmdColumns(sdk: KanbanSDK, positional: string[], flags: Flags): P
|
|
|
825
825
|
if (flags.json) console.log(JSON.stringify(columns, null, 2))
|
|
826
826
|
break
|
|
827
827
|
}
|
|
828
|
+
case 'cleanup': {
|
|
829
|
+
const columnId = positional[1]
|
|
830
|
+
if (!columnId) {
|
|
831
|
+
console.error(red('Usage: kl columns cleanup <id>'))
|
|
832
|
+
process.exit(1)
|
|
833
|
+
}
|
|
834
|
+
const moved = await sdk.cleanupColumn(columnId, boardId)
|
|
835
|
+
console.log(green(`Moved ${moved} card${moved === 1 ? '' : 's'} from "${columnId}" to deleted`))
|
|
836
|
+
break
|
|
837
|
+
}
|
|
828
838
|
default:
|
|
829
839
|
console.error(red(`Unknown columns subcommand: ${subcommand}`))
|
|
830
|
-
console.error('Available: list, add, update, remove')
|
|
840
|
+
console.error('Available: list, add, update, remove, cleanup')
|
|
831
841
|
process.exit(1)
|
|
832
842
|
}
|
|
833
843
|
}
|
|
@@ -905,7 +915,7 @@ async function cmdLabels(sdk: KanbanSDK, positional: string[], flags: Flags): Pr
|
|
|
905
915
|
console.error(red('Usage: kl labels delete <name>'))
|
|
906
916
|
process.exit(1)
|
|
907
917
|
}
|
|
908
|
-
sdk.deleteLabel(name)
|
|
918
|
+
await sdk.deleteLabel(name)
|
|
909
919
|
if (flags.json) {
|
|
910
920
|
console.log(JSON.stringify({ deleted: name }, null, 2))
|
|
911
921
|
} else {
|
|
@@ -204,6 +204,9 @@ export class KanbanPanel {
|
|
|
204
204
|
case 'removeColumn':
|
|
205
205
|
await this._removeColumn(message.columnId)
|
|
206
206
|
break
|
|
207
|
+
case 'cleanupColumn':
|
|
208
|
+
await this._cleanupColumn(message.columnId)
|
|
209
|
+
break
|
|
207
210
|
case 'transferCard': {
|
|
208
211
|
const sdk = this._getSDK()
|
|
209
212
|
if (!sdk || !this._currentBoardId) break
|
|
@@ -265,7 +268,8 @@ export class KanbanPanel {
|
|
|
265
268
|
case 'deleteLabel': {
|
|
266
269
|
const sdk = this._getSDK()
|
|
267
270
|
if (!sdk) break
|
|
268
|
-
sdk.deleteLabel(message.name)
|
|
271
|
+
await sdk.deleteLabel(message.name)
|
|
272
|
+
await this._loadFeatures()
|
|
269
273
|
this._sendFeaturesToWebview()
|
|
270
274
|
this._panel.webview.postMessage({ type: 'labelsUpdated', labels: sdk.getLabels() })
|
|
271
275
|
break
|
|
@@ -560,10 +564,7 @@ export class KanbanPanel {
|
|
|
560
564
|
if (!sdk) return
|
|
561
565
|
|
|
562
566
|
try {
|
|
563
|
-
|
|
564
|
-
for (const card of deletedCards) {
|
|
565
|
-
await sdk.permanentlyDeleteCard(card.id, this._currentBoardId)
|
|
566
|
-
}
|
|
567
|
+
await sdk.purgeDeletedCards(this._currentBoardId)
|
|
567
568
|
this._features = this._features.filter(f => f.status !== 'deleted')
|
|
568
569
|
this._sendFeaturesToWebview()
|
|
569
570
|
} catch (err) {
|
|
@@ -939,4 +940,23 @@ export class KanbanPanel {
|
|
|
939
940
|
sdk.removeColumn(columnId, this._currentBoardId)
|
|
940
941
|
this._sendFeaturesToWebview()
|
|
941
942
|
}
|
|
943
|
+
|
|
944
|
+
private async _cleanupColumn(columnId: string): Promise<void> {
|
|
945
|
+
const sdk = this._getSDK()
|
|
946
|
+
if (!sdk) return
|
|
947
|
+
|
|
948
|
+
this._migrating = true
|
|
949
|
+
try {
|
|
950
|
+
await sdk.cleanupColumn(columnId, this._currentBoardId)
|
|
951
|
+
// Update in-memory cache: mark all column cards as deleted
|
|
952
|
+
this._features = this._features.map(f =>
|
|
953
|
+
f.status === columnId ? { ...f, status: 'deleted' } : f
|
|
954
|
+
)
|
|
955
|
+
this._sendFeaturesToWebview()
|
|
956
|
+
} catch (err) {
|
|
957
|
+
vscode.window.showErrorMessage(`Failed to cleanup list: ${err}`)
|
|
958
|
+
} finally {
|
|
959
|
+
this._migrating = false
|
|
960
|
+
}
|
|
961
|
+
}
|
|
942
962
|
}
|
package/src/mcp-server/index.ts
CHANGED
|
@@ -829,6 +829,24 @@ async function main(): Promise<void> {
|
|
|
829
829
|
}
|
|
830
830
|
)
|
|
831
831
|
|
|
832
|
+
server.tool(
|
|
833
|
+
'cleanup_column',
|
|
834
|
+
'Move all cards in a column to the deleted (soft-delete) column. The column itself is kept.',
|
|
835
|
+
{
|
|
836
|
+
boardId: z.string().optional().describe('Board ID (uses default board if omitted)'),
|
|
837
|
+
columnId: z.string().describe('Column ID to clean up'),
|
|
838
|
+
},
|
|
839
|
+
async ({ boardId, columnId }) => {
|
|
840
|
+
const moved = await sdk.cleanupColumn(columnId, boardId)
|
|
841
|
+
return {
|
|
842
|
+
content: [{
|
|
843
|
+
type: 'text' as const,
|
|
844
|
+
text: `Moved ${moved} card${moved === 1 ? '' : 's'} from "${columnId}" to deleted`,
|
|
845
|
+
}],
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
)
|
|
849
|
+
|
|
832
850
|
// --- Label Tools ---
|
|
833
851
|
|
|
834
852
|
server.tool('list_labels', 'List all label definitions with colors and groups', {
|
|
@@ -855,10 +873,10 @@ async function main(): Promise<void> {
|
|
|
855
873
|
return { content: [{ type: 'text' as const, text: `Label "${oldName}" renamed to "${newName}"` }] }
|
|
856
874
|
})
|
|
857
875
|
|
|
858
|
-
server.tool('delete_label', 'Remove a label definition
|
|
876
|
+
server.tool('delete_label', 'Remove a label definition and remove it from all cards', {
|
|
859
877
|
name: z.string().describe('Label name to remove')
|
|
860
878
|
}, async ({ name }) => {
|
|
861
|
-
sdk.deleteLabel(name)
|
|
879
|
+
await sdk.deleteLabel(name)
|
|
862
880
|
return { content: [{ type: 'text' as const, text: `Label "${name}" definition removed` }] }
|
|
863
881
|
})
|
|
864
882
|
|
package/src/sdk/KanbanSDK.ts
CHANGED
|
@@ -983,25 +983,30 @@ export class KanbanSDK {
|
|
|
983
983
|
}
|
|
984
984
|
|
|
985
985
|
/**
|
|
986
|
-
* Removes a label definition from the workspace configuration
|
|
987
|
-
*
|
|
988
|
-
* This only removes the color/group definition — cards that use this
|
|
989
|
-
* label keep their label strings. Those labels will render with default
|
|
990
|
-
* gray styling in the UI.
|
|
986
|
+
* Removes a label definition from the workspace configuration and cascades
|
|
987
|
+
* the deletion to all cards by removing the label from their `labels` array.
|
|
991
988
|
*
|
|
992
989
|
* @param name - The label name to remove.
|
|
993
990
|
*
|
|
994
991
|
* @example
|
|
995
992
|
* ```ts
|
|
996
|
-
* sdk.deleteLabel('bug')
|
|
993
|
+
* await sdk.deleteLabel('bug')
|
|
997
994
|
* ```
|
|
998
995
|
*/
|
|
999
|
-
deleteLabel(name: string): void {
|
|
996
|
+
async deleteLabel(name: string): Promise<void> {
|
|
1000
997
|
const config = readConfig(this.workspaceRoot)
|
|
1001
998
|
if (config.labels) {
|
|
1002
999
|
delete config.labels[name]
|
|
1003
1000
|
writeConfig(this.workspaceRoot, config)
|
|
1004
1001
|
}
|
|
1002
|
+
|
|
1003
|
+
// Cascade to all cards
|
|
1004
|
+
const cards = await this.listCards()
|
|
1005
|
+
for (const card of cards) {
|
|
1006
|
+
if (card.labels.includes(name)) {
|
|
1007
|
+
await this.updateCard(card.id, { labels: card.labels.filter(l => l !== name) })
|
|
1008
|
+
}
|
|
1009
|
+
}
|
|
1005
1010
|
}
|
|
1006
1011
|
|
|
1007
1012
|
/**
|
|
@@ -1452,6 +1457,58 @@ export class KanbanSDK {
|
|
|
1452
1457
|
return board.columns
|
|
1453
1458
|
}
|
|
1454
1459
|
|
|
1460
|
+
/**
|
|
1461
|
+
* Moves all cards in the specified column to the `deleted` (soft-delete) column.
|
|
1462
|
+
*
|
|
1463
|
+
* This is a non-destructive operation — cards are moved to the reserved
|
|
1464
|
+
* `deleted` status and can be restored or permanently deleted later.
|
|
1465
|
+
* The column itself is not removed.
|
|
1466
|
+
*
|
|
1467
|
+
* @param columnId - The ID of the column whose cards should be moved to `deleted`.
|
|
1468
|
+
* @param boardId - Optional board ID. Defaults to the workspace's default board.
|
|
1469
|
+
* @returns A promise resolving to the number of cards that were moved.
|
|
1470
|
+
* @throws {Error} If the column is `'deleted'` (no-op protection).
|
|
1471
|
+
*
|
|
1472
|
+
* @example
|
|
1473
|
+
* ```ts
|
|
1474
|
+
* const moved = await sdk.cleanupColumn('blocked')
|
|
1475
|
+
* console.log(`Moved ${moved} cards to deleted`)
|
|
1476
|
+
* ```
|
|
1477
|
+
*/
|
|
1478
|
+
async cleanupColumn(columnId: string, boardId?: string): Promise<number> {
|
|
1479
|
+
if (columnId === DELETED_STATUS_ID) return 0
|
|
1480
|
+
const cards = await this.listCards(undefined, boardId)
|
|
1481
|
+
const cardsToMove = cards.filter(c => c.status === columnId)
|
|
1482
|
+
for (const card of cardsToMove) {
|
|
1483
|
+
await this.moveCard(card.id, DELETED_STATUS_ID, 0, boardId)
|
|
1484
|
+
}
|
|
1485
|
+
return cardsToMove.length
|
|
1486
|
+
}
|
|
1487
|
+
|
|
1488
|
+
/**
|
|
1489
|
+
* Permanently deletes all cards currently in the `deleted` column.
|
|
1490
|
+
*
|
|
1491
|
+
* This is equivalent to "empty trash". All soft-deleted cards are
|
|
1492
|
+
* removed from disk. This operation cannot be undone.
|
|
1493
|
+
*
|
|
1494
|
+
* @param boardId - Optional board ID. Defaults to the workspace's default board.
|
|
1495
|
+
* @returns A promise resolving to the number of cards that were permanently deleted.
|
|
1496
|
+
*
|
|
1497
|
+
* @example
|
|
1498
|
+
* ```ts
|
|
1499
|
+
* const count = await sdk.purgeDeletedCards()
|
|
1500
|
+
* console.log(`Permanently deleted ${count} cards`)
|
|
1501
|
+
* ```
|
|
1502
|
+
*/
|
|
1503
|
+
async purgeDeletedCards(boardId?: string): Promise<number> {
|
|
1504
|
+
const cards = await this.listCards(undefined, boardId)
|
|
1505
|
+
const deleted = cards.filter(c => c.status === DELETED_STATUS_ID)
|
|
1506
|
+
for (const card of deleted) {
|
|
1507
|
+
await this.permanentlyDeleteCard(card.id, boardId)
|
|
1508
|
+
}
|
|
1509
|
+
return deleted.length
|
|
1510
|
+
}
|
|
1511
|
+
|
|
1455
1512
|
/**
|
|
1456
1513
|
* Reorders the columns of a board.
|
|
1457
1514
|
*
|
|
@@ -379,11 +379,27 @@ describe('KanbanSDK', () => {
|
|
|
379
379
|
|
|
380
380
|
it('deleteLabel removes label definition from config', async () => {
|
|
381
381
|
sdk.setLabel('bug', { color: '#e11d48' })
|
|
382
|
-
sdk.deleteLabel('bug')
|
|
382
|
+
await sdk.deleteLabel('bug')
|
|
383
383
|
const labels = sdk.getLabels()
|
|
384
384
|
expect(labels['bug']).toBeUndefined()
|
|
385
385
|
})
|
|
386
386
|
|
|
387
|
+
it('deleteLabel cascades to all cards removing the label', async () => {
|
|
388
|
+
writeCardFile(tempDir, '1-card.md', makeCardContent({
|
|
389
|
+
id: '1-card', status: 'backlog', labels: ['bug', 'frontend']
|
|
390
|
+
}), 'backlog')
|
|
391
|
+
sdk.setLabel('bug', { color: '#e11d48', group: 'Type' })
|
|
392
|
+
|
|
393
|
+
await sdk.deleteLabel('bug')
|
|
394
|
+
|
|
395
|
+
const labels = sdk.getLabels()
|
|
396
|
+
expect(labels['bug']).toBeUndefined()
|
|
397
|
+
|
|
398
|
+
const cards = await sdk.listCards()
|
|
399
|
+
expect(cards[0].labels).not.toContain('bug')
|
|
400
|
+
expect(cards[0].labels).toContain('frontend')
|
|
401
|
+
})
|
|
402
|
+
|
|
387
403
|
it('renameLabel updates config key and cascades to all cards', async () => {
|
|
388
404
|
writeCardFile(tempDir, '1-card.md', makeCardContent({
|
|
389
405
|
id: '1-card', status: 'backlog', labels: ['bug', 'frontend']
|
|
@@ -34,6 +34,8 @@ function createV2Config(overrides?: Partial<KanbanConfig>): KanbanConfig {
|
|
|
34
34
|
compactMode: false,
|
|
35
35
|
markdownEditorMode: false,
|
|
36
36
|
showDeletedColumn: false,
|
|
37
|
+
boardZoom: 100,
|
|
38
|
+
cardZoom: 100,
|
|
37
39
|
port: 3000,
|
|
38
40
|
...overrides
|
|
39
41
|
}
|
|
@@ -271,7 +273,7 @@ describe('SDK integration - metadata', () => {
|
|
|
271
273
|
const fileContent = fs.readFileSync(card.filePath, 'utf-8')
|
|
272
274
|
expect(fileContent).toContain('metadata:')
|
|
273
275
|
expect(fileContent).toContain(' sprint: 5')
|
|
274
|
-
expect(fileContent).toContain(' team: backend')
|
|
276
|
+
expect(fileContent).toContain(' team: "backend"')
|
|
275
277
|
|
|
276
278
|
// Verify round-trip through listCards
|
|
277
279
|
const cards = await sdk.listCards()
|