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
package/{CLAUDE.md → AGENTS.md}
RENAMED
|
@@ -41,6 +41,17 @@ npm test # Run tests (vitest)
|
|
|
41
41
|
|
|
42
42
|
All three interfaces (API, CLI, MCP) support the same operations: cards CRUD, columns CRUD, settings get/update, webhooks CRUD, workspace info. When adding new functionality, add it to all three.
|
|
43
43
|
|
|
44
|
+
## Implementation Order
|
|
45
|
+
|
|
46
|
+
Always follow this order when adding or modifying any feature:
|
|
47
|
+
|
|
48
|
+
1. **SDK first** — implement the core logic in `src/sdk/KanbanSDK.ts` (or supporting SDK files) with full JSDoc comments before touching any interface layer.
|
|
49
|
+
2. **API** — add or update the corresponding REST endpoint(s) in `src/standalone/server.ts`.
|
|
50
|
+
3. **CLI** — add or update the command in `src/cli/index.ts`.
|
|
51
|
+
4. **MCP** — add or update the tool definition in `src/mcp-server/index.ts`.
|
|
52
|
+
|
|
53
|
+
Never implement a feature directly in an interface layer without the SDK method existing first. The SDK is the single source of truth for all business logic.
|
|
54
|
+
|
|
44
55
|
## Data Storage
|
|
45
56
|
|
|
46
57
|
- Cards: `.kanban/{status}/card-name-YYYY-MM-DD.md` (markdown with YAML frontmatter)
|
|
@@ -73,6 +84,8 @@ metadata:
|
|
|
73
84
|
|
|
74
85
|
- **JSDoc:** Always update JSDoc comments when changing any logic, parameters, return types, or behavior of functions, methods, interfaces, or types. JSDoc is the source of truth for `docs/sdk.md`.
|
|
75
86
|
- **Generated docs:** `docs/api.md`, `docs/sdk.md`, and `docs/webhooks.md` are autogenerated. Never edit them manually. Update the source (JSDoc for SDK, route metadata in `scripts/generate-api-docs.ts` for API, `scripts/generate-webhooks-docs.ts` for webhooks) and run `npm run docs` to regenerate.
|
|
87
|
+
- **README.md:** Always update `README.md` when adding or modifying user-facing features. Update the relevant Features section, configuration examples, CLI usage examples, REST API tables, and MCP tool table as appropriate.
|
|
88
|
+
- **CHANGELOG.md:** Always add an entry to `CHANGELOG.md` for every new feature, behaviour change, or bug fix. Place it under an `## [Unreleased]` section at the top (or the current in-progress version block). Follow the existing format: `### Added`, `### Changed`, `### Fixed` subsections with concise bullet points.
|
|
76
89
|
|
|
77
90
|
## Conventions
|
|
78
91
|
|
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,74 @@ All notable changes to the Kanban Lite extension will be documented in this file
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [2.1.0] - 2026-02-27
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- Board and card detail zoom settings with slider UI (75–150%) stored in `.kanban.json`
|
|
12
|
+
- Keyboard shortcuts for adjusting board/card zoom level (Ctrl/Cmd `+`/`-`)
|
|
13
|
+
- CSS custom properties (`--board-zoom`, `--card-zoom`) with `calc()` multipliers for smooth font scaling
|
|
14
|
+
- Smooth scrolling to the selected feature card in the kanban board
|
|
15
|
+
- Sorting options in the column context menu
|
|
16
|
+
- Default zoom level configuration for both board and card detail views
|
|
17
|
+
|
|
18
|
+
## [2.0.0] - 2026-02-26
|
|
19
|
+
|
|
20
|
+
### Added
|
|
21
|
+
- Per-card actions (named string labels) that trigger a global `actionWebhookUrl` via `POST` on demand
|
|
22
|
+
- Run Actions dropdown in the card editor and action input in CreateFeatureDialog
|
|
23
|
+
- `triggerAction` method in KanbanSDK with full support across REST API, WebSocket, MCP (`trigger_action` tool), and CLI (`--actions` flag)
|
|
24
|
+
- Comment editor component with Write / Preview tabs and a markdown formatting toolbar
|
|
25
|
+
- GitHub-style comment editing using the new CommentEditor in CommentsSection
|
|
26
|
+
- Settings panel split into three tabs: **General**, **Defaults**, and **Labels**
|
|
27
|
+
- `version` field on card frontmatter schema for format tracking
|
|
28
|
+
- Metadata filtering for card list/search operations across all interfaces
|
|
29
|
+
- Creation and modification date display with hover tooltips on FeatureCard and FeatureEditor
|
|
30
|
+
- Sort order filter for card queries
|
|
31
|
+
|
|
32
|
+
### Fixed
|
|
33
|
+
- `version` field now included in all FeatureFrontmatter constructions in the server
|
|
34
|
+
|
|
35
|
+
## [1.9.0] - 2026-02-25
|
|
36
|
+
|
|
37
|
+
### Added
|
|
38
|
+
- Card metadata support — arbitrary key-value data stored as a native YAML block in frontmatter (`metadata` field)
|
|
39
|
+
- Metadata UI: key-count chip `{N}` on card grid and collapsible tree view in the card detail panel
|
|
40
|
+
- Label definitions with color picker in the Settings panel (create, rename, delete labels)
|
|
41
|
+
- Colored labels rendered on cards, in the editor, create dialog, and toolbar
|
|
42
|
+
- Label group filtering across SDK (`filterCardsByLabelGroup`), CLI (`--label-group`), REST API, and MCP tools
|
|
43
|
+
- SDK label management methods: `getLabels`, `setLabel`, `renameLabel`, `deleteLabel`
|
|
44
|
+
- Soft-delete support: hidden **Deleted** column with per-card restore or permanent delete
|
|
45
|
+
- Purge deleted cards functionality to permanently remove all soft-deleted cards
|
|
46
|
+
- `--metadata` flag for CLI `create` and `edit` commands (accepts JSON string)
|
|
47
|
+
- Metadata support in MCP `create_card` and `update_card` tools
|
|
48
|
+
- Metadata support in REST API create/update routes
|
|
49
|
+
- Workspace info section in the Settings panel showing project path and `.kanban.json` parameters
|
|
50
|
+
- `js-yaml` dependency for robust YAML metadata parsing
|
|
51
|
+
|
|
52
|
+
### Fixed
|
|
53
|
+
- Comment parser no longer breaks on horizontal rules (`---`) inside comment blocks
|
|
54
|
+
- Blank lines in metadata YAML parsed correctly; scalar edge cases handled
|
|
55
|
+
|
|
56
|
+
## [1.8.0] - 2026-02-24
|
|
57
|
+
|
|
58
|
+
### Added
|
|
59
|
+
- Multi-board support: board selector dropdown to switch between boards and create new boards
|
|
60
|
+
- Card transfer between boards via a StatusDropdown with a nested board-and-column tree
|
|
61
|
+
- `transferCard` message type and `BoardInfo.columns` field in the extension/standalone protocol
|
|
62
|
+
- Webhooks system: CRUD operations (`create`, `get`, `update`, `delete`, `list`) stored in `.kanban-webhooks.json`
|
|
63
|
+
- Webhook event delivery on card create/update/delete/move with configurable `url`, `events`, and `secret`
|
|
64
|
+
- Webhook management commands in CLI and MCP server
|
|
65
|
+
- Comments functionality: add, edit, and delete comments on feature cards
|
|
66
|
+
- Markdown rendering for comment content
|
|
67
|
+
- Auto-generated SDK docs (`docs/sdk.md`) and REST API docs (`docs/api.md`) from JSDoc / route metadata
|
|
68
|
+
- `npm run docs` script to regenerate all documentation
|
|
69
|
+
- Theme toggle (light / dark) in the board toolbar
|
|
70
|
+
- Release scripts for versioning, changelog generation, and GitHub release creation
|
|
71
|
+
|
|
72
|
+
### Fixed
|
|
73
|
+
- SDK export paths updated to support both CommonJS and ESM module formats
|
|
74
|
+
- SDK import paths corrected; server feature loading logic improved
|
|
75
|
+
|
|
8
76
|
## [1.7.0] - 2026-02-20
|
|
9
77
|
|
|
10
78
|
### Added
|
package/README.md
CHANGED
|
@@ -44,10 +44,16 @@ kl add --title "My first task" --priority high
|
|
|
44
44
|
- **Layout toggle**: Switch between horizontal and vertical board layouts
|
|
45
45
|
- **Real-time updates**: WebSocket-powered live sync across clients
|
|
46
46
|
- **Light & dark mode** support
|
|
47
|
+
- **Tabbed settings panel**: Settings organized into **General**, **Defaults**, and **Labels** tabs
|
|
48
|
+
- **Zoom controls**: Scale the board view and card detail panel independently between 75–150% via settings sliders or keyboard shortcuts
|
|
49
|
+
- **Column sorting**: Sort cards within a column by priority, due date, or creation date from the column menu
|
|
50
|
+
- **Smooth scroll to selection**: Board automatically scrolls to the selected card when switching features
|
|
47
51
|
- **Keyboard shortcuts**:
|
|
48
52
|
- `N` - Create new feature
|
|
49
53
|
- `Esc` - Close dialogs
|
|
50
54
|
- `Cmd/Ctrl + Enter` - Submit create dialog
|
|
55
|
+
- `Ctrl/Cmd + =` / `Ctrl/Cmd + -` - Zoom board view in / out
|
|
56
|
+
- `Ctrl/Cmd + Shift + =` / `Ctrl/Cmd + Shift + -` - Zoom card detail in / out
|
|
51
57
|
|
|
52
58
|
### Feature Cards
|
|
53
59
|
|
|
@@ -628,12 +634,16 @@ Board configuration is stored in `.kanban.json` at your project root. It support
|
|
|
628
634
|
"showDueDate": true,
|
|
629
635
|
"showLabels": true,
|
|
630
636
|
"compactMode": false,
|
|
637
|
+
"boardZoom": 100,
|
|
638
|
+
"cardZoom": 100,
|
|
631
639
|
"actionWebhookUrl": "https://example.com/kanban-actions"
|
|
632
640
|
}
|
|
633
641
|
```
|
|
634
642
|
|
|
635
643
|
Columns are fully customizable per board — add, remove, rename, or recolor them from the web UI, CLI, or REST API.
|
|
636
644
|
|
|
645
|
+
`boardZoom` and `cardZoom` set the default zoom percentage (75–150) for the board view and card detail panel respectively. They can also be adjusted live in the Settings panel or with `Ctrl/Cmd + =` / `Ctrl/Cmd + -` keyboard shortcuts.
|
|
646
|
+
|
|
637
647
|
## AI Agent Integration
|
|
638
648
|
- **Claude Code**: Default, Plan, Auto-edit, and Full Auto modes
|
|
639
649
|
- **Codex**: Suggest, Auto-edit, and Full Auto modes
|
package/dist/cli.js
CHANGED
|
@@ -330,6 +330,8 @@ function migrateConfigV1ToV2(raw) {
|
|
|
330
330
|
compactMode: v1.compactMode,
|
|
331
331
|
markdownEditorMode: v1.markdownEditorMode,
|
|
332
332
|
showDeletedColumn: false,
|
|
333
|
+
boardZoom: 100,
|
|
334
|
+
cardZoom: 100,
|
|
333
335
|
port: 3e3
|
|
334
336
|
};
|
|
335
337
|
}
|
|
@@ -403,7 +405,9 @@ function configToSettings(config) {
|
|
|
403
405
|
markdownEditorMode: config.markdownEditorMode,
|
|
404
406
|
showDeletedColumn: config.showDeletedColumn,
|
|
405
407
|
defaultPriority: config.defaultPriority,
|
|
406
|
-
defaultStatus: config.defaultStatus
|
|
408
|
+
defaultStatus: config.defaultStatus,
|
|
409
|
+
boardZoom: config.boardZoom ?? 100,
|
|
410
|
+
cardZoom: config.cardZoom ?? 100
|
|
407
411
|
};
|
|
408
412
|
}
|
|
409
413
|
function settingsToConfig(config, settings) {
|
|
@@ -417,7 +421,9 @@ function settingsToConfig(config, settings) {
|
|
|
417
421
|
compactMode: settings.compactMode,
|
|
418
422
|
showDeletedColumn: settings.showDeletedColumn,
|
|
419
423
|
defaultPriority: settings.defaultPriority,
|
|
420
|
-
defaultStatus: settings.defaultStatus
|
|
424
|
+
defaultStatus: settings.defaultStatus,
|
|
425
|
+
boardZoom: settings.boardZoom,
|
|
426
|
+
cardZoom: settings.cardZoom
|
|
421
427
|
};
|
|
422
428
|
}
|
|
423
429
|
var fs, path, DEFAULT_BOARD_CONFIG, DEFAULT_CONFIG, CONFIG_FILENAME;
|
|
@@ -453,6 +459,8 @@ var init_config = __esm({
|
|
|
453
459
|
compactMode: false,
|
|
454
460
|
markdownEditorMode: false,
|
|
455
461
|
showDeletedColumn: false,
|
|
462
|
+
boardZoom: 100,
|
|
463
|
+
cardZoom: 100,
|
|
456
464
|
port: 3e3,
|
|
457
465
|
labels: {}
|
|
458
466
|
};
|
|
@@ -2714,7 +2722,7 @@ function renamed(from, to) {
|
|
|
2714
2722
|
throw new Error("Function yaml." + from + " is removed in js-yaml 4. Use yaml." + to + " instead, which is now safe by default.");
|
|
2715
2723
|
};
|
|
2716
2724
|
}
|
|
2717
|
-
var isNothing_1, isObject_1, toArray_1, repeat_1, isNegativeZero_1, extend_1, common, exception, snippet, TYPE_CONSTRUCTOR_OPTIONS, YAML_NODE_KINDS, type, schema, str, seq, map, failsafe, _null, bool, int, YAML_FLOAT_PATTERN, SCIENTIFIC_WITHOUT_DOT, float, json, core, YAML_DATE_REGEXP, YAML_TIMESTAMP_REGEXP, timestamp, merge, BASE64_MAP, binary, _hasOwnProperty$3, _toString$2, omap, _toString$1, pairs, _hasOwnProperty$2, set, _default, _hasOwnProperty$1, CONTEXT_FLOW_IN, CONTEXT_FLOW_OUT, CONTEXT_BLOCK_IN, CONTEXT_BLOCK_OUT, CHOMPING_CLIP, CHOMPING_STRIP, CHOMPING_KEEP, PATTERN_NON_PRINTABLE, PATTERN_NON_ASCII_LINE_BREAKS, PATTERN_FLOW_INDICATORS, PATTERN_TAG_HANDLE, PATTERN_TAG_URI, simpleEscapeCheck, simpleEscapeMap, i, directiveHandlers, loadAll_1, load_1, loader, _toString, _hasOwnProperty, CHAR_BOM, CHAR_TAB, CHAR_LINE_FEED, CHAR_CARRIAGE_RETURN, CHAR_SPACE, CHAR_EXCLAMATION, CHAR_DOUBLE_QUOTE, CHAR_SHARP, CHAR_PERCENT, CHAR_AMPERSAND, CHAR_SINGLE_QUOTE, CHAR_ASTERISK, CHAR_COMMA, CHAR_MINUS, CHAR_COLON, CHAR_EQUALS, CHAR_GREATER_THAN, CHAR_QUESTION, CHAR_COMMERCIAL_AT, CHAR_LEFT_SQUARE_BRACKET, CHAR_RIGHT_SQUARE_BRACKET, CHAR_GRAVE_ACCENT, CHAR_LEFT_CURLY_BRACKET, CHAR_VERTICAL_LINE, CHAR_RIGHT_CURLY_BRACKET, ESCAPE_SEQUENCES, DEPRECATED_BOOLEANS_SYNTAX, DEPRECATED_BASE60_SYNTAX, QUOTING_TYPE_SINGLE, QUOTING_TYPE_DOUBLE, STYLE_PLAIN, STYLE_SINGLE, STYLE_LITERAL, STYLE_FOLDED, STYLE_DOUBLE, dump_1, dumper, load, loadAll, dump, safeLoad, safeLoadAll, safeDump;
|
|
2725
|
+
var isNothing_1, isObject_1, toArray_1, repeat_1, isNegativeZero_1, extend_1, common, exception, snippet, TYPE_CONSTRUCTOR_OPTIONS, YAML_NODE_KINDS, type, schema, str, seq, map, failsafe, _null, bool, int, YAML_FLOAT_PATTERN, SCIENTIFIC_WITHOUT_DOT, float, json, core, YAML_DATE_REGEXP, YAML_TIMESTAMP_REGEXP, timestamp, merge, BASE64_MAP, binary, _hasOwnProperty$3, _toString$2, omap, _toString$1, pairs, _hasOwnProperty$2, set, _default, _hasOwnProperty$1, CONTEXT_FLOW_IN, CONTEXT_FLOW_OUT, CONTEXT_BLOCK_IN, CONTEXT_BLOCK_OUT, CHOMPING_CLIP, CHOMPING_STRIP, CHOMPING_KEEP, PATTERN_NON_PRINTABLE, PATTERN_NON_ASCII_LINE_BREAKS, PATTERN_FLOW_INDICATORS, PATTERN_TAG_HANDLE, PATTERN_TAG_URI, simpleEscapeCheck, simpleEscapeMap, i, directiveHandlers, loadAll_1, load_1, loader, _toString, _hasOwnProperty, CHAR_BOM, CHAR_TAB, CHAR_LINE_FEED, CHAR_CARRIAGE_RETURN, CHAR_SPACE, CHAR_EXCLAMATION, CHAR_DOUBLE_QUOTE, CHAR_SHARP, CHAR_PERCENT, CHAR_AMPERSAND, CHAR_SINGLE_QUOTE, CHAR_ASTERISK, CHAR_COMMA, CHAR_MINUS, CHAR_COLON, CHAR_EQUALS, CHAR_GREATER_THAN, CHAR_QUESTION, CHAR_COMMERCIAL_AT, CHAR_LEFT_SQUARE_BRACKET, CHAR_RIGHT_SQUARE_BRACKET, CHAR_GRAVE_ACCENT, CHAR_LEFT_CURLY_BRACKET, CHAR_VERTICAL_LINE, CHAR_RIGHT_CURLY_BRACKET, ESCAPE_SEQUENCES, DEPRECATED_BOOLEANS_SYNTAX, DEPRECATED_BASE60_SYNTAX, QUOTING_TYPE_SINGLE, QUOTING_TYPE_DOUBLE, STYLE_PLAIN, STYLE_SINGLE, STYLE_LITERAL, STYLE_FOLDED, STYLE_DOUBLE, dump_1, dumper, JSON_SCHEMA, load, loadAll, dump, safeLoad, safeLoadAll, safeDump;
|
|
2718
2726
|
var init_js_yaml = __esm({
|
|
2719
2727
|
"node_modules/js-yaml/dist/js-yaml.mjs"() {
|
|
2720
2728
|
isNothing_1 = isNothing;
|
|
@@ -3115,6 +3123,7 @@ var init_js_yaml = __esm({
|
|
|
3115
3123
|
dumper = {
|
|
3116
3124
|
dump: dump_1
|
|
3117
3125
|
};
|
|
3126
|
+
JSON_SCHEMA = json;
|
|
3118
3127
|
load = loader.load;
|
|
3119
3128
|
loadAll = loader.loadAll;
|
|
3120
3129
|
dump = dumper.dump;
|
|
@@ -3155,48 +3164,26 @@ function parseFeatureFile(content, filePath) {
|
|
|
3155
3164
|
return null;
|
|
3156
3165
|
const frontmatter = frontmatterMatch[1];
|
|
3157
3166
|
const rest = frontmatterMatch[2] || "";
|
|
3158
|
-
|
|
3159
|
-
|
|
3160
|
-
|
|
3167
|
+
let parsed;
|
|
3168
|
+
try {
|
|
3169
|
+
const loaded = load(frontmatter, { schema: JSON_SCHEMA });
|
|
3170
|
+
if (!loaded || typeof loaded !== "object" || Array.isArray(loaded))
|
|
3171
|
+
return null;
|
|
3172
|
+
parsed = loaded;
|
|
3173
|
+
} catch {
|
|
3174
|
+
return null;
|
|
3175
|
+
}
|
|
3176
|
+
const str2 = (key) => {
|
|
3177
|
+
const val = parsed[key];
|
|
3178
|
+
if (val == null)
|
|
3161
3179
|
return "";
|
|
3162
|
-
|
|
3163
|
-
return value === "null" ? "" : value;
|
|
3180
|
+
return String(val);
|
|
3164
3181
|
};
|
|
3165
|
-
const
|
|
3166
|
-
const
|
|
3167
|
-
if (!
|
|
3182
|
+
const arr = (key) => {
|
|
3183
|
+
const val = parsed[key];
|
|
3184
|
+
if (!Array.isArray(val))
|
|
3168
3185
|
return [];
|
|
3169
|
-
return
|
|
3170
|
-
};
|
|
3171
|
-
const getMetadata = () => {
|
|
3172
|
-
const lines = frontmatter.split("\n");
|
|
3173
|
-
let metaStart = -1;
|
|
3174
|
-
for (let j = 0; j < lines.length; j++) {
|
|
3175
|
-
if (/^metadata:\s*$/.test(lines[j])) {
|
|
3176
|
-
metaStart = j + 1;
|
|
3177
|
-
break;
|
|
3178
|
-
}
|
|
3179
|
-
}
|
|
3180
|
-
if (metaStart === -1)
|
|
3181
|
-
return void 0;
|
|
3182
|
-
const indentedLines = [];
|
|
3183
|
-
for (let j = metaStart; j < lines.length; j++) {
|
|
3184
|
-
if (/^\s/.test(lines[j]) || lines[j].trim() === "") {
|
|
3185
|
-
indentedLines.push(lines[j]);
|
|
3186
|
-
} else {
|
|
3187
|
-
break;
|
|
3188
|
-
}
|
|
3189
|
-
}
|
|
3190
|
-
if (indentedLines.length === 0)
|
|
3191
|
-
return void 0;
|
|
3192
|
-
try {
|
|
3193
|
-
const parsed = load(indentedLines.join("\n"));
|
|
3194
|
-
if (parsed && typeof parsed === "object")
|
|
3195
|
-
return parsed;
|
|
3196
|
-
return void 0;
|
|
3197
|
-
} catch {
|
|
3198
|
-
return void 0;
|
|
3199
|
-
}
|
|
3186
|
+
return val.filter((v) => v != null).map(String);
|
|
3200
3187
|
};
|
|
3201
3188
|
const sections = rest.split(/\n---\n/);
|
|
3202
3189
|
let body = sections[0] || "";
|
|
@@ -3217,22 +3204,23 @@ ${section}`;
|
|
|
3217
3204
|
i += 1;
|
|
3218
3205
|
}
|
|
3219
3206
|
}
|
|
3220
|
-
const
|
|
3221
|
-
const
|
|
3207
|
+
const actions = arr("actions");
|
|
3208
|
+
const rawMeta = parsed.metadata;
|
|
3209
|
+
const meta = rawMeta != null && typeof rawMeta === "object" && !Array.isArray(rawMeta) ? rawMeta : void 0;
|
|
3222
3210
|
return {
|
|
3223
|
-
version: parseInt(
|
|
3224
|
-
id:
|
|
3225
|
-
status:
|
|
3226
|
-
priority:
|
|
3227
|
-
assignee:
|
|
3228
|
-
dueDate:
|
|
3229
|
-
created:
|
|
3230
|
-
modified:
|
|
3231
|
-
completedAt:
|
|
3232
|
-
labels:
|
|
3233
|
-
attachments:
|
|
3211
|
+
version: typeof parsed.version === "number" ? parsed.version : parseInt(str2("version"), 10) || 0,
|
|
3212
|
+
id: str2("id") || extractIdFromFilename(filePath),
|
|
3213
|
+
status: str2("status") || "backlog",
|
|
3214
|
+
priority: str2("priority") || "medium",
|
|
3215
|
+
assignee: parsed.assignee != null ? String(parsed.assignee) : null,
|
|
3216
|
+
dueDate: parsed.dueDate != null ? String(parsed.dueDate) : null,
|
|
3217
|
+
created: str2("created") || (/* @__PURE__ */ new Date()).toISOString(),
|
|
3218
|
+
modified: str2("modified") || (/* @__PURE__ */ new Date()).toISOString(),
|
|
3219
|
+
completedAt: parsed.completedAt != null ? String(parsed.completedAt) : null,
|
|
3220
|
+
labels: arr("labels"),
|
|
3221
|
+
attachments: arr("attachments"),
|
|
3234
3222
|
comments,
|
|
3235
|
-
order:
|
|
3223
|
+
order: str2("order") || "a0",
|
|
3236
3224
|
content: body.trim(),
|
|
3237
3225
|
...meta ? { metadata: meta } : {},
|
|
3238
3226
|
...actions.length > 0 ? { actions } : {},
|
|
@@ -3240,37 +3228,28 @@ ${section}`;
|
|
|
3240
3228
|
};
|
|
3241
3229
|
}
|
|
3242
3230
|
function serializeFeature(feature) {
|
|
3243
|
-
const
|
|
3244
|
-
|
|
3245
|
-
|
|
3246
|
-
|
|
3247
|
-
|
|
3248
|
-
|
|
3249
|
-
|
|
3250
|
-
|
|
3251
|
-
|
|
3252
|
-
|
|
3253
|
-
|
|
3254
|
-
|
|
3255
|
-
|
|
3256
|
-
|
|
3257
|
-
|
|
3258
|
-
|
|
3259
|
-
|
|
3260
|
-
|
|
3261
|
-
|
|
3262
|
-
|
|
3263
|
-
|
|
3264
|
-
|
|
3265
|
-
lines.push(" " + line);
|
|
3266
|
-
}
|
|
3267
|
-
}
|
|
3268
|
-
lines.push("---");
|
|
3269
|
-
lines.push("");
|
|
3270
|
-
const frontmatter = lines.join("\n");
|
|
3271
|
-
let result = frontmatter + feature.content;
|
|
3272
|
-
const comments = feature.comments || [];
|
|
3273
|
-
for (const comment of comments) {
|
|
3231
|
+
const frontmatterObj = {
|
|
3232
|
+
version: feature.version ?? CARD_FORMAT_VERSION,
|
|
3233
|
+
id: feature.id,
|
|
3234
|
+
status: feature.status,
|
|
3235
|
+
priority: feature.priority,
|
|
3236
|
+
assignee: feature.assignee ?? null,
|
|
3237
|
+
dueDate: feature.dueDate ?? null,
|
|
3238
|
+
created: feature.created,
|
|
3239
|
+
modified: feature.modified,
|
|
3240
|
+
completedAt: feature.completedAt ?? null,
|
|
3241
|
+
labels: feature.labels,
|
|
3242
|
+
attachments: feature.attachments || [],
|
|
3243
|
+
order: feature.order,
|
|
3244
|
+
...feature.actions?.length ? { actions: feature.actions } : {},
|
|
3245
|
+
...feature.metadata && Object.keys(feature.metadata).length > 0 ? { metadata: feature.metadata } : {}
|
|
3246
|
+
};
|
|
3247
|
+
const yamlStr = dump(frontmatterObj, { lineWidth: -1, quotingType: '"', forceQuotes: true });
|
|
3248
|
+
let result = `---
|
|
3249
|
+
${yamlStr}---
|
|
3250
|
+
|
|
3251
|
+
${feature.content}`;
|
|
3252
|
+
for (const comment of feature.comments || []) {
|
|
3274
3253
|
result += "\n\n---\n";
|
|
3275
3254
|
result += `comment: true
|
|
3276
3255
|
`;
|
|
@@ -4308,25 +4287,28 @@ var init_KanbanSDK = __esm({
|
|
|
4308
4287
|
writeConfig(this.workspaceRoot, config);
|
|
4309
4288
|
}
|
|
4310
4289
|
/**
|
|
4311
|
-
* Removes a label definition from the workspace configuration
|
|
4312
|
-
*
|
|
4313
|
-
* This only removes the color/group definition — cards that use this
|
|
4314
|
-
* label keep their label strings. Those labels will render with default
|
|
4315
|
-
* gray styling in the UI.
|
|
4290
|
+
* Removes a label definition from the workspace configuration and cascades
|
|
4291
|
+
* the deletion to all cards by removing the label from their `labels` array.
|
|
4316
4292
|
*
|
|
4317
4293
|
* @param name - The label name to remove.
|
|
4318
4294
|
*
|
|
4319
4295
|
* @example
|
|
4320
4296
|
* ```ts
|
|
4321
|
-
* sdk.deleteLabel('bug')
|
|
4297
|
+
* await sdk.deleteLabel('bug')
|
|
4322
4298
|
* ```
|
|
4323
4299
|
*/
|
|
4324
|
-
deleteLabel(name) {
|
|
4300
|
+
async deleteLabel(name) {
|
|
4325
4301
|
const config = readConfig(this.workspaceRoot);
|
|
4326
4302
|
if (config.labels) {
|
|
4327
4303
|
delete config.labels[name];
|
|
4328
4304
|
writeConfig(this.workspaceRoot, config);
|
|
4329
4305
|
}
|
|
4306
|
+
const cards = await this.listCards();
|
|
4307
|
+
for (const card of cards) {
|
|
4308
|
+
if (card.labels.includes(name)) {
|
|
4309
|
+
await this.updateCard(card.id, { labels: card.labels.filter((l) => l !== name) });
|
|
4310
|
+
}
|
|
4311
|
+
}
|
|
4330
4312
|
}
|
|
4331
4313
|
/**
|
|
4332
4314
|
* Renames a label in the configuration and cascades the change to all cards.
|
|
@@ -4751,6 +4733,57 @@ var init_KanbanSDK = __esm({
|
|
|
4751
4733
|
this.emitEvent("column.deleted", removed);
|
|
4752
4734
|
return board.columns;
|
|
4753
4735
|
}
|
|
4736
|
+
/**
|
|
4737
|
+
* Moves all cards in the specified column to the `deleted` (soft-delete) column.
|
|
4738
|
+
*
|
|
4739
|
+
* This is a non-destructive operation — cards are moved to the reserved
|
|
4740
|
+
* `deleted` status and can be restored or permanently deleted later.
|
|
4741
|
+
* The column itself is not removed.
|
|
4742
|
+
*
|
|
4743
|
+
* @param columnId - The ID of the column whose cards should be moved to `deleted`.
|
|
4744
|
+
* @param boardId - Optional board ID. Defaults to the workspace's default board.
|
|
4745
|
+
* @returns A promise resolving to the number of cards that were moved.
|
|
4746
|
+
* @throws {Error} If the column is `'deleted'` (no-op protection).
|
|
4747
|
+
*
|
|
4748
|
+
* @example
|
|
4749
|
+
* ```ts
|
|
4750
|
+
* const moved = await sdk.cleanupColumn('blocked')
|
|
4751
|
+
* console.log(`Moved ${moved} cards to deleted`)
|
|
4752
|
+
* ```
|
|
4753
|
+
*/
|
|
4754
|
+
async cleanupColumn(columnId, boardId) {
|
|
4755
|
+
if (columnId === DELETED_STATUS_ID)
|
|
4756
|
+
return 0;
|
|
4757
|
+
const cards = await this.listCards(void 0, boardId);
|
|
4758
|
+
const cardsToMove = cards.filter((c) => c.status === columnId);
|
|
4759
|
+
for (const card of cardsToMove) {
|
|
4760
|
+
await this.moveCard(card.id, DELETED_STATUS_ID, 0, boardId);
|
|
4761
|
+
}
|
|
4762
|
+
return cardsToMove.length;
|
|
4763
|
+
}
|
|
4764
|
+
/**
|
|
4765
|
+
* Permanently deletes all cards currently in the `deleted` column.
|
|
4766
|
+
*
|
|
4767
|
+
* This is equivalent to "empty trash". All soft-deleted cards are
|
|
4768
|
+
* removed from disk. This operation cannot be undone.
|
|
4769
|
+
*
|
|
4770
|
+
* @param boardId - Optional board ID. Defaults to the workspace's default board.
|
|
4771
|
+
* @returns A promise resolving to the number of cards that were permanently deleted.
|
|
4772
|
+
*
|
|
4773
|
+
* @example
|
|
4774
|
+
* ```ts
|
|
4775
|
+
* const count = await sdk.purgeDeletedCards()
|
|
4776
|
+
* console.log(`Permanently deleted ${count} cards`)
|
|
4777
|
+
* ```
|
|
4778
|
+
*/
|
|
4779
|
+
async purgeDeletedCards(boardId) {
|
|
4780
|
+
const cards = await this.listCards(void 0, boardId);
|
|
4781
|
+
const deleted = cards.filter((c) => c.status === DELETED_STATUS_ID);
|
|
4782
|
+
for (const card of deleted) {
|
|
4783
|
+
await this.permanentlyDeleteCard(card.id, boardId);
|
|
4784
|
+
}
|
|
4785
|
+
return deleted.length;
|
|
4786
|
+
}
|
|
4754
4787
|
/**
|
|
4755
4788
|
* Reorders the columns of a board.
|
|
4756
4789
|
*
|
|
@@ -10587,10 +10620,7 @@ function startServer(featuresDir, port, webviewDir) {
|
|
|
10587
10620
|
}
|
|
10588
10621
|
async function doPurgeDeletedCards() {
|
|
10589
10622
|
try {
|
|
10590
|
-
|
|
10591
|
-
for (const card of deletedCards) {
|
|
10592
|
-
await sdk.permanentlyDeleteCard(card.id, currentBoardId);
|
|
10593
|
-
}
|
|
10623
|
+
await sdk.purgeDeletedCards(currentBoardId);
|
|
10594
10624
|
await loadFeatures();
|
|
10595
10625
|
broadcast(buildInitMessage());
|
|
10596
10626
|
return true;
|
|
@@ -10637,6 +10667,20 @@ function startServer(featuresDir, port, webviewDir) {
|
|
|
10637
10667
|
return { removed: false, error: String(err) };
|
|
10638
10668
|
}
|
|
10639
10669
|
}
|
|
10670
|
+
async function doCleanupColumn(columnId) {
|
|
10671
|
+
try {
|
|
10672
|
+
migrating = true;
|
|
10673
|
+
await sdk.cleanupColumn(columnId, currentBoardId);
|
|
10674
|
+
await loadFeatures();
|
|
10675
|
+
broadcast(buildInitMessage());
|
|
10676
|
+
return true;
|
|
10677
|
+
} catch (err) {
|
|
10678
|
+
console.error("Failed to cleanup column:", err);
|
|
10679
|
+
return false;
|
|
10680
|
+
} finally {
|
|
10681
|
+
migrating = false;
|
|
10682
|
+
}
|
|
10683
|
+
}
|
|
10640
10684
|
function doSaveSettings(newSettings) {
|
|
10641
10685
|
sdk.updateSettings(newSettings);
|
|
10642
10686
|
broadcast(buildInitMessage());
|
|
@@ -10823,6 +10867,9 @@ function startServer(featuresDir, port, webviewDir) {
|
|
|
10823
10867
|
case "removeColumn":
|
|
10824
10868
|
await doRemoveColumn(msg.columnId);
|
|
10825
10869
|
break;
|
|
10870
|
+
case "cleanupColumn":
|
|
10871
|
+
await doCleanupColumn(msg.columnId);
|
|
10872
|
+
break;
|
|
10826
10873
|
case "removeAttachment": {
|
|
10827
10874
|
const featureId = msg.featureId;
|
|
10828
10875
|
const feature = await doRemoveAttachment(featureId, msg.attachment);
|
|
@@ -10971,13 +11018,16 @@ function startServer(featuresDir, port, webviewDir) {
|
|
|
10971
11018
|
}
|
|
10972
11019
|
case "renameLabel": {
|
|
10973
11020
|
await sdk.renameLabel(msg.oldName, msg.newName);
|
|
11021
|
+
await loadFeatures();
|
|
10974
11022
|
broadcast({ type: "labelsUpdated", labels: sdk.getLabels() });
|
|
10975
11023
|
broadcast(buildInitMessage());
|
|
10976
11024
|
break;
|
|
10977
11025
|
}
|
|
10978
11026
|
case "deleteLabel": {
|
|
10979
|
-
sdk.deleteLabel(msg.name);
|
|
11027
|
+
await sdk.deleteLabel(msg.name);
|
|
11028
|
+
await loadFeatures();
|
|
10980
11029
|
broadcast({ type: "labelsUpdated", labels: sdk.getLabels() });
|
|
11030
|
+
broadcast(buildInitMessage());
|
|
10981
11031
|
break;
|
|
10982
11032
|
}
|
|
10983
11033
|
case "triggerAction": {
|
|
@@ -11589,6 +11639,9 @@ function startServer(featuresDir, port, webviewDir) {
|
|
|
11589
11639
|
if (!newName)
|
|
11590
11640
|
return jsonError(res, 400, "newName is required");
|
|
11591
11641
|
await sdk.renameLabel(name, newName);
|
|
11642
|
+
await loadFeatures();
|
|
11643
|
+
broadcast({ type: "labelsUpdated", labels: sdk.getLabels() });
|
|
11644
|
+
broadcast(buildInitMessage());
|
|
11592
11645
|
return jsonOk(res, sdk.getLabels());
|
|
11593
11646
|
} catch (err) {
|
|
11594
11647
|
return jsonError(res, 400, String(err));
|
|
@@ -11598,7 +11651,10 @@ function startServer(featuresDir, port, webviewDir) {
|
|
|
11598
11651
|
if (params) {
|
|
11599
11652
|
try {
|
|
11600
11653
|
const name = decodeURIComponent(params.name);
|
|
11601
|
-
sdk.deleteLabel(name);
|
|
11654
|
+
await sdk.deleteLabel(name);
|
|
11655
|
+
await loadFeatures();
|
|
11656
|
+
broadcast({ type: "labelsUpdated", labels: sdk.getLabels() });
|
|
11657
|
+
broadcast(buildInitMessage());
|
|
11602
11658
|
return jsonOk(res, { success: true });
|
|
11603
11659
|
} catch (err) {
|
|
11604
11660
|
return jsonError(res, 400, String(err));
|
|
@@ -13142,9 +13198,19 @@ async function cmdColumns(sdk, positional, flags) {
|
|
|
13142
13198
|
console.log(JSON.stringify(columns, null, 2));
|
|
13143
13199
|
break;
|
|
13144
13200
|
}
|
|
13201
|
+
case "cleanup": {
|
|
13202
|
+
const columnId = positional[1];
|
|
13203
|
+
if (!columnId) {
|
|
13204
|
+
console.error(red("Usage: kl columns cleanup <id>"));
|
|
13205
|
+
process.exit(1);
|
|
13206
|
+
}
|
|
13207
|
+
const moved = await sdk.cleanupColumn(columnId, boardId);
|
|
13208
|
+
console.log(green(`Moved ${moved} card${moved === 1 ? "" : "s"} from "${columnId}" to deleted`));
|
|
13209
|
+
break;
|
|
13210
|
+
}
|
|
13145
13211
|
default:
|
|
13146
13212
|
console.error(red(`Unknown columns subcommand: ${subcommand}`));
|
|
13147
|
-
console.error("Available: list, add, update, remove");
|
|
13213
|
+
console.error("Available: list, add, update, remove, cleanup");
|
|
13148
13214
|
process.exit(1);
|
|
13149
13215
|
}
|
|
13150
13216
|
}
|
|
@@ -13218,7 +13284,7 @@ async function cmdLabels(sdk, positional, flags) {
|
|
|
13218
13284
|
console.error(red("Usage: kl labels delete <name>"));
|
|
13219
13285
|
process.exit(1);
|
|
13220
13286
|
}
|
|
13221
|
-
sdk.deleteLabel(name);
|
|
13287
|
+
await sdk.deleteLabel(name);
|
|
13222
13288
|
if (flags.json) {
|
|
13223
13289
|
console.log(JSON.stringify({ deleted: name }, null, 2));
|
|
13224
13290
|
} else {
|