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.
Files changed (44) hide show
  1. package/{CLAUDE.md → AGENTS.md} +13 -0
  2. package/CHANGELOG.md +68 -0
  3. package/README.md +10 -0
  4. package/dist/cli.js +168 -102
  5. package/dist/extension.js +178 -104
  6. package/dist/mcp-server.js +145 -95
  7. package/dist/sdk/index.cjs +126 -93
  8. package/dist/sdk/index.mjs +126 -93
  9. package/dist/sdk/sdk/KanbanSDK.d.ts +39 -7
  10. package/dist/sdk/shared/config.d.ts +4 -0
  11. package/dist/sdk/shared/types.d.ts +4 -0
  12. package/dist/standalone-webview/index.js +58 -58
  13. package/dist/standalone-webview/index.js.map +1 -1
  14. package/dist/standalone-webview/style.css +1 -1
  15. package/dist/standalone.js +606 -364
  16. package/dist/webview/index.js +57 -57
  17. package/dist/webview/index.js.map +1 -1
  18. package/dist/webview/style.css +1 -1
  19. package/docs/plans/2026-02-26-settings-tabs-design.md +40 -0
  20. package/docs/plans/2026-02-26-settings-tabs.md +166 -0
  21. package/docs/plans/2026-02-27-zoom-settings-design.md +82 -0
  22. package/docs/plans/2026-02-27-zoom-settings.md +395 -0
  23. package/docs/sdk.md +3 -6
  24. package/package.json +1 -1
  25. package/src/cli/index.ts +12 -2
  26. package/src/extension/KanbanPanel.ts +25 -5
  27. package/src/mcp-server/index.ts +20 -2
  28. package/src/sdk/KanbanSDK.ts +64 -7
  29. package/src/sdk/__tests__/KanbanSDK.test.ts +17 -1
  30. package/src/sdk/__tests__/metadata.test.ts +3 -1
  31. package/src/sdk/__tests__/multi-board.test.ts +2 -0
  32. package/src/sdk/parser.ts +50 -83
  33. package/src/shared/config.ts +14 -2
  34. package/src/shared/types.ts +4 -0
  35. package/src/standalone/__tests__/server.integration.test.ts +2 -2
  36. package/src/standalone/index.ts +7 -4
  37. package/src/standalone/server.ts +31 -6
  38. package/src/webview/App.tsx +42 -3
  39. package/src/webview/assets/main.css +31 -2
  40. package/src/webview/components/KanbanBoard.tsx +35 -3
  41. package/src/webview/components/KanbanColumn.tsx +40 -4
  42. package/src/webview/components/SettingsPanel.tsx +179 -77
  43. package/src/webview/components/Toolbar.tsx +127 -32
  44. 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
@@ -2,7 +2,7 @@
2
2
  "name": "kanban-lite",
3
3
  "displayName": "Kanban Lite",
4
4
  "description": "A kanban board for your codebase. Features stored as markdown.",
5
- "version": "1.0.21",
5
+ "version": "1.0.23",
6
6
  "publisher": "borgius",
7
7
  "license": "MIT",
8
8
  "repository": {
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
- const deletedCards = this._features.filter(f => f.status === 'deleted')
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
  }
@@ -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 (cards keep the label text)', {
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
 
@@ -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()