kanban-lite 1.0.35 → 1.0.36

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/CHANGELOG.md CHANGED
@@ -7,6 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ### Changed
11
+ - **Card actions**: `actions` field now accepts either an array of action keys (`string[]`) or an object mapping action keys to display titles (`Record<string, string>`). The "Run Action" dropdown shows the title when the object form is used; the action key is always what's sent to the webhook. Fully backward-compatible — existing array-form cards are unchanged.
12
+
10
13
  ### Added
11
14
  - **Card logs**: Append timestamped log entries to any card, stored as a `<cardId>.log` text file auto-added as an attachment. Each entry has timestamp (auto-generated), source label (defaults to `"default"`), markdown text, and optional structured data object (stored as compact JSON). Supports markdown formatting (bold, italic, emoji) in log text.
12
15
  - **SDK**: `listLogs(cardId, boardId?)`, `addLog(cardId, text, options?, boardId?)`, `clearLogs(cardId, boardId?)` methods on `KanbanSDK`
package/README.md CHANGED
@@ -398,15 +398,17 @@ Actions let you attach named triggers to a card — things like `retry`, `deploy
398
398
  }
399
399
  ```
400
400
 
401
- 2. **Add actions to a card** — as a list of simple strings stored in the card's frontmatter:
401
+ 2. **Add actions to a card** — either as an array of action keys, or an object mapping action keys to display titles:
402
402
 
403
403
  ```yaml
404
- ---
405
- id: "42-deploy-v2"
406
- status: "in-progress"
404
+ # Array form — action key is used as the button label
407
405
  actions: ["retry", "rollback", "notify-slack"]
408
- ---
409
- # Deploy v2.0
406
+
407
+ # Object form — keys are the action names sent to the webhook, values are the UI labels
408
+ actions:
409
+ retry: "Retry deployment"
410
+ rollback: "Roll back to v1"
411
+ notify-slack: "Notify Slack"
410
412
  ```
411
413
 
412
414
  3. **Trigger an action** from any interface:
@@ -431,7 +433,7 @@ actions: ["retry", "rollback", "notify-slack"]
431
433
  "assignee": "alice",
432
434
  "labels": ["deploy"],
433
435
  "content": "# Deploy v2.0\n...",
434
- "actions": ["retry", "rollback", "notify-slack"]
436
+ "actions": ["retry", "rollback", "notify-slack"] // or {"retry": "Retry deployment", ...}
435
437
  }
436
438
  }
437
439
  ```
@@ -440,7 +442,7 @@ The webhook receives the full card object (same shape as the SDK `Card` type, mi
440
442
 
441
443
  ### Managing actions
442
444
 
443
- Actions are plain strings in the `actions` array of the card's YAML frontmatter. Edit them directly in the markdown file, or use any interface:
445
+ Actions are stored in the `actions` field of the card's YAML frontmatter — either as a plain string array or as a key→title object. Edit them directly in the markdown file, or use any interface:
444
446
 
445
447
  ```bash
446
448
  # Add actions when creating a card
package/dist/cli.js CHANGED
@@ -3292,7 +3292,18 @@ ${section}`;
3292
3292
  i += 1;
3293
3293
  }
3294
3294
  }
3295
- const actions = arr("actions");
3295
+ const rawActions = parsed["actions"];
3296
+ const actions = (() => {
3297
+ if (Array.isArray(rawActions))
3298
+ return rawActions.filter((v) => v != null).map(String);
3299
+ if (rawActions != null && typeof rawActions === "object" && !Array.isArray(rawActions)) {
3300
+ const obj = {};
3301
+ for (const [k, v] of Object.entries(rawActions))
3302
+ obj[k] = String(v ?? k);
3303
+ return obj;
3304
+ }
3305
+ return void 0;
3306
+ })();
3296
3307
  const rawMeta = parsed.metadata;
3297
3308
  const meta5 = rawMeta != null && typeof rawMeta === "object" && !Array.isArray(rawMeta) ? rawMeta : void 0;
3298
3309
  return {
@@ -3311,7 +3322,7 @@ ${section}`;
3311
3322
  order: str2("order") || "a0",
3312
3323
  content: body.trim(),
3313
3324
  ...meta5 ? { metadata: meta5 } : {},
3314
- ...actions.length > 0 ? { actions } : {},
3325
+ ...actions && (Array.isArray(actions) ? actions.length > 0 : Object.keys(actions).length > 0) ? { actions } : {},
3315
3326
  filePath
3316
3327
  };
3317
3328
  }
@@ -3329,7 +3340,7 @@ function serializeCard(card) {
3329
3340
  labels: card.labels,
3330
3341
  attachments: card.attachments || [],
3331
3342
  order: card.order,
3332
- ...card.actions?.length ? { actions: card.actions } : {},
3343
+ ...card.actions && (Array.isArray(card.actions) ? card.actions.length > 0 : Object.keys(card.actions).length > 0) ? { actions: card.actions } : {},
3333
3344
  ...card.metadata && Object.keys(card.metadata).length > 0 ? { metadata: card.metadata } : {}
3334
3345
  };
3335
3346
  const yamlStr = dump(frontmatterObj, { lineWidth: -1, quotingType: '"', forceQuotes: true });
@@ -4749,7 +4760,7 @@ CREATE INDEX IF NOT EXISTS idx_comments_card ON comments (card_id, board_i
4749
4760
  card.order || "a0",
4750
4761
  card.content || "",
4751
4762
  card.metadata && Object.keys(card.metadata).length > 0 ? JSON.stringify(card.metadata) : null,
4752
- card.actions && card.actions.length > 0 ? JSON.stringify(card.actions) : null
4763
+ card.actions && (Array.isArray(card.actions) ? card.actions.length > 0 : Object.keys(card.actions).length > 0) ? JSON.stringify(card.actions) : null
4753
4764
  );
4754
4765
  deleteComments.run(card.id, boardId);
4755
4766
  for (const comment of card.comments || []) {
@@ -5436,7 +5447,7 @@ var init_KanbanSDK = __esm({
5436
5447
  order: generateKeyBetween(lastOrder, null),
5437
5448
  content: data.content,
5438
5449
  ...data.metadata && Object.keys(data.metadata).length > 0 ? { metadata: data.metadata } : {},
5439
- ...data.actions && data.actions.length > 0 ? { actions: data.actions } : {},
5450
+ ...data.actions && (Array.isArray(data.actions) ? data.actions.length > 0 : Object.keys(data.actions).length > 0) ? { actions: data.actions } : {},
5440
5451
  filePath: this._storage.type === "markdown" ? getCardFilePath(boardDir, status, filename) : ""
5441
5452
  };
5442
5453
  await this._storage.writeCard(card);
package/dist/extension.js CHANGED
@@ -3104,7 +3104,18 @@ ${section}`;
3104
3104
  i += 1;
3105
3105
  }
3106
3106
  }
3107
- const actions = arr("actions");
3107
+ const rawActions = parsed["actions"];
3108
+ const actions = (() => {
3109
+ if (Array.isArray(rawActions))
3110
+ return rawActions.filter((v) => v != null).map(String);
3111
+ if (rawActions != null && typeof rawActions === "object" && !Array.isArray(rawActions)) {
3112
+ const obj = {};
3113
+ for (const [k, v] of Object.entries(rawActions))
3114
+ obj[k] = String(v ?? k);
3115
+ return obj;
3116
+ }
3117
+ return void 0;
3118
+ })();
3108
3119
  const rawMeta = parsed.metadata;
3109
3120
  const meta = rawMeta != null && typeof rawMeta === "object" && !Array.isArray(rawMeta) ? rawMeta : void 0;
3110
3121
  return {
@@ -3123,7 +3134,7 @@ ${section}`;
3123
3134
  order: str2("order") || "a0",
3124
3135
  content: body.trim(),
3125
3136
  ...meta ? { metadata: meta } : {},
3126
- ...actions.length > 0 ? { actions } : {},
3137
+ ...actions && (Array.isArray(actions) ? actions.length > 0 : Object.keys(actions).length > 0) ? { actions } : {},
3127
3138
  filePath
3128
3139
  };
3129
3140
  }
@@ -3141,7 +3152,7 @@ function serializeCard(card) {
3141
3152
  labels: card.labels,
3142
3153
  attachments: card.attachments || [],
3143
3154
  order: card.order,
3144
- ...card.actions?.length ? { actions: card.actions } : {},
3155
+ ...card.actions && (Array.isArray(card.actions) ? card.actions.length > 0 : Object.keys(card.actions).length > 0) ? { actions: card.actions } : {},
3145
3156
  ...card.metadata && Object.keys(card.metadata).length > 0 ? { metadata: card.metadata } : {}
3146
3157
  };
3147
3158
  const yamlStr = dump(frontmatterObj, { lineWidth: -1, quotingType: '"', forceQuotes: true });
@@ -4561,7 +4572,7 @@ CREATE INDEX IF NOT EXISTS idx_comments_card ON comments (card_id, board_i
4561
4572
  card.order || "a0",
4562
4573
  card.content || "",
4563
4574
  card.metadata && Object.keys(card.metadata).length > 0 ? JSON.stringify(card.metadata) : null,
4564
- card.actions && card.actions.length > 0 ? JSON.stringify(card.actions) : null
4575
+ card.actions && (Array.isArray(card.actions) ? card.actions.length > 0 : Object.keys(card.actions).length > 0) ? JSON.stringify(card.actions) : null
4565
4576
  );
4566
4577
  deleteComments.run(card.id, boardId);
4567
4578
  for (const comment of card.comments || []) {
@@ -9160,7 +9171,7 @@ var KanbanSDK = class {
9160
9171
  order: generateKeyBetween(lastOrder, null),
9161
9172
  content: data.content,
9162
9173
  ...data.metadata && Object.keys(data.metadata).length > 0 ? { metadata: data.metadata } : {},
9163
- ...data.actions && data.actions.length > 0 ? { actions: data.actions } : {},
9174
+ ...data.actions && (Array.isArray(data.actions) ? data.actions.length > 0 : Object.keys(data.actions).length > 0) ? { actions: data.actions } : {},
9164
9175
  filePath: this._storage.type === "markdown" ? getCardFilePath(boardDir, status, filename) : ""
9165
9176
  };
9166
9177
  await this._storage.writeCard(card);
@@ -3077,7 +3077,18 @@ ${section}`;
3077
3077
  i += 1;
3078
3078
  }
3079
3079
  }
3080
- const actions = arr("actions");
3080
+ const rawActions = parsed["actions"];
3081
+ const actions = (() => {
3082
+ if (Array.isArray(rawActions))
3083
+ return rawActions.filter((v) => v != null).map(String);
3084
+ if (rawActions != null && typeof rawActions === "object" && !Array.isArray(rawActions)) {
3085
+ const obj = {};
3086
+ for (const [k, v] of Object.entries(rawActions))
3087
+ obj[k] = String(v ?? k);
3088
+ return obj;
3089
+ }
3090
+ return void 0;
3091
+ })();
3081
3092
  const rawMeta = parsed.metadata;
3082
3093
  const meta = rawMeta != null && typeof rawMeta === "object" && !Array.isArray(rawMeta) ? rawMeta : void 0;
3083
3094
  return {
@@ -3096,7 +3107,7 @@ ${section}`;
3096
3107
  order: str2("order") || "a0",
3097
3108
  content: body.trim(),
3098
3109
  ...meta ? { metadata: meta } : {},
3099
- ...actions.length > 0 ? { actions } : {},
3110
+ ...actions && (Array.isArray(actions) ? actions.length > 0 : Object.keys(actions).length > 0) ? { actions } : {},
3100
3111
  filePath
3101
3112
  };
3102
3113
  }
@@ -3114,7 +3125,7 @@ function serializeCard(card) {
3114
3125
  labels: card.labels,
3115
3126
  attachments: card.attachments || [],
3116
3127
  order: card.order,
3117
- ...card.actions?.length ? { actions: card.actions } : {},
3128
+ ...card.actions && (Array.isArray(card.actions) ? card.actions.length > 0 : Object.keys(card.actions).length > 0) ? { actions: card.actions } : {},
3118
3129
  ...card.metadata && Object.keys(card.metadata).length > 0 ? { metadata: card.metadata } : {}
3119
3130
  };
3120
3131
  const yamlStr = dump(frontmatterObj, { lineWidth: -1, quotingType: '"', forceQuotes: true });
@@ -4534,7 +4545,7 @@ CREATE INDEX IF NOT EXISTS idx_comments_card ON comments (card_id, board_i
4534
4545
  card.order || "a0",
4535
4546
  card.content || "",
4536
4547
  card.metadata && Object.keys(card.metadata).length > 0 ? JSON.stringify(card.metadata) : null,
4537
- card.actions && card.actions.length > 0 ? JSON.stringify(card.actions) : null
4548
+ card.actions && (Array.isArray(card.actions) ? card.actions.length > 0 : Object.keys(card.actions).length > 0) ? JSON.stringify(card.actions) : null
4538
4549
  );
4539
4550
  deleteComments.run(card.id, boardId);
4540
4551
  for (const comment of card.comments || []) {
@@ -5419,7 +5430,7 @@ var KanbanSDK = class {
5419
5430
  order: generateKeyBetween(lastOrder, null),
5420
5431
  content: data.content,
5421
5432
  ...data.metadata && Object.keys(data.metadata).length > 0 ? { metadata: data.metadata } : {},
5422
- ...data.actions && data.actions.length > 0 ? { actions: data.actions } : {},
5433
+ ...data.actions && (Array.isArray(data.actions) ? data.actions.length > 0 : Object.keys(data.actions).length > 0) ? { actions: data.actions } : {},
5423
5434
  filePath: this._storage.type === "markdown" ? getCardFilePath(boardDir, status, filename) : ""
5424
5435
  };
5425
5436
  await this._storage.writeCard(card);
@@ -3078,7 +3078,18 @@ ${section}`;
3078
3078
  i += 1;
3079
3079
  }
3080
3080
  }
3081
- const actions = arr("actions");
3081
+ const rawActions = parsed["actions"];
3082
+ const actions = (() => {
3083
+ if (Array.isArray(rawActions))
3084
+ return rawActions.filter((v) => v != null).map(String);
3085
+ if (rawActions != null && typeof rawActions === "object" && !Array.isArray(rawActions)) {
3086
+ const obj = {};
3087
+ for (const [k, v] of Object.entries(rawActions))
3088
+ obj[k] = String(v ?? k);
3089
+ return obj;
3090
+ }
3091
+ return void 0;
3092
+ })();
3082
3093
  const rawMeta = parsed.metadata;
3083
3094
  const meta = rawMeta != null && typeof rawMeta === "object" && !Array.isArray(rawMeta) ? rawMeta : void 0;
3084
3095
  return {
@@ -3097,7 +3108,7 @@ ${section}`;
3097
3108
  order: str2("order") || "a0",
3098
3109
  content: body.trim(),
3099
3110
  ...meta ? { metadata: meta } : {},
3100
- ...actions.length > 0 ? { actions } : {},
3111
+ ...actions && (Array.isArray(actions) ? actions.length > 0 : Object.keys(actions).length > 0) ? { actions } : {},
3101
3112
  filePath
3102
3113
  };
3103
3114
  }
@@ -3115,7 +3126,7 @@ function serializeCard(card) {
3115
3126
  labels: card.labels,
3116
3127
  attachments: card.attachments || [],
3117
3128
  order: card.order,
3118
- ...card.actions?.length ? { actions: card.actions } : {},
3129
+ ...card.actions && (Array.isArray(card.actions) ? card.actions.length > 0 : Object.keys(card.actions).length > 0) ? { actions: card.actions } : {},
3119
3130
  ...card.metadata && Object.keys(card.metadata).length > 0 ? { metadata: card.metadata } : {}
3120
3131
  };
3121
3132
  const yamlStr = dump(frontmatterObj, { lineWidth: -1, quotingType: '"', forceQuotes: true });
@@ -3712,7 +3723,7 @@ CREATE INDEX IF NOT EXISTS idx_comments_card ON comments (card_id, board_i
3712
3723
  card.order || "a0",
3713
3724
  card.content || "",
3714
3725
  card.metadata && Object.keys(card.metadata).length > 0 ? JSON.stringify(card.metadata) : null,
3715
- card.actions && card.actions.length > 0 ? JSON.stringify(card.actions) : null
3726
+ card.actions && (Array.isArray(card.actions) ? card.actions.length > 0 : Object.keys(card.actions).length > 0) ? JSON.stringify(card.actions) : null
3716
3727
  );
3717
3728
  deleteComments.run(card.id, boardId);
3718
3729
  for (const comment of card.comments || []) {
@@ -4616,7 +4627,7 @@ var KanbanSDK = class {
4616
4627
  order: generateKeyBetween(lastOrder, null),
4617
4628
  content: data.content,
4618
4629
  ...data.metadata && Object.keys(data.metadata).length > 0 ? { metadata: data.metadata } : {},
4619
- ...data.actions && data.actions.length > 0 ? { actions: data.actions } : {},
4630
+ ...data.actions && (Array.isArray(data.actions) ? data.actions.length > 0 : Object.keys(data.actions).length > 0) ? { actions: data.actions } : {},
4620
4631
  filePath: this._storage.type === "markdown" ? getCardFilePath(boardDir, status, filename) : ""
4621
4632
  };
4622
4633
  await this._storage.writeCard(card);
@@ -3063,7 +3063,18 @@ ${section}`;
3063
3063
  i += 1;
3064
3064
  }
3065
3065
  }
3066
- const actions = arr("actions");
3066
+ const rawActions = parsed["actions"];
3067
+ const actions = (() => {
3068
+ if (Array.isArray(rawActions))
3069
+ return rawActions.filter((v) => v != null).map(String);
3070
+ if (rawActions != null && typeof rawActions === "object" && !Array.isArray(rawActions)) {
3071
+ const obj = {};
3072
+ for (const [k, v] of Object.entries(rawActions))
3073
+ obj[k] = String(v ?? k);
3074
+ return obj;
3075
+ }
3076
+ return void 0;
3077
+ })();
3067
3078
  const rawMeta = parsed.metadata;
3068
3079
  const meta = rawMeta != null && typeof rawMeta === "object" && !Array.isArray(rawMeta) ? rawMeta : void 0;
3069
3080
  return {
@@ -3082,7 +3093,7 @@ ${section}`;
3082
3093
  order: str2("order") || "a0",
3083
3094
  content: body.trim(),
3084
3095
  ...meta ? { metadata: meta } : {},
3085
- ...actions.length > 0 ? { actions } : {},
3096
+ ...actions && (Array.isArray(actions) ? actions.length > 0 : Object.keys(actions).length > 0) ? { actions } : {},
3086
3097
  filePath
3087
3098
  };
3088
3099
  }
@@ -3100,7 +3111,7 @@ function serializeCard(card) {
3100
3111
  labels: card.labels,
3101
3112
  attachments: card.attachments || [],
3102
3113
  order: card.order,
3103
- ...card.actions?.length ? { actions: card.actions } : {},
3114
+ ...card.actions && (Array.isArray(card.actions) ? card.actions.length > 0 : Object.keys(card.actions).length > 0) ? { actions: card.actions } : {},
3104
3115
  ...card.metadata && Object.keys(card.metadata).length > 0 ? { metadata: card.metadata } : {}
3105
3116
  };
3106
3117
  const yamlStr = dump(frontmatterObj, { lineWidth: -1, quotingType: '"', forceQuotes: true });
@@ -3694,7 +3705,7 @@ CREATE INDEX IF NOT EXISTS idx_comments_card ON comments (card_id, board_i
3694
3705
  card.order || "a0",
3695
3706
  card.content || "",
3696
3707
  card.metadata && Object.keys(card.metadata).length > 0 ? JSON.stringify(card.metadata) : null,
3697
- card.actions && card.actions.length > 0 ? JSON.stringify(card.actions) : null
3708
+ card.actions && (Array.isArray(card.actions) ? card.actions.length > 0 : Object.keys(card.actions).length > 0) ? JSON.stringify(card.actions) : null
3698
3709
  );
3699
3710
  deleteComments.run(card.id, boardId);
3700
3711
  for (const comment of card.comments || []) {
@@ -4572,7 +4583,7 @@ var KanbanSDK = class {
4572
4583
  order: generateKeyBetween(lastOrder, null),
4573
4584
  content: data.content,
4574
4585
  ...data.metadata && Object.keys(data.metadata).length > 0 ? { metadata: data.metadata } : {},
4575
- ...data.actions && data.actions.length > 0 ? { actions: data.actions } : {},
4586
+ ...data.actions && (Array.isArray(data.actions) ? data.actions.length > 0 : Object.keys(data.actions).length > 0) ? { actions: data.actions } : {},
4576
4587
  filePath: this._storage.type === "markdown" ? getCardFilePath(boardDir, status, filename) : ""
4577
4588
  };
4578
4589
  await this._storage.writeCard(card);
@@ -91,8 +91,8 @@ export interface Card {
91
91
  content: string;
92
92
  /** Arbitrary user-defined metadata stored as YAML in the frontmatter. */
93
93
  metadata?: Record<string, any>;
94
- /** Named action strings that can be triggered via the action webhook. */
95
- actions?: string[];
94
+ /** Named actions that can be triggered via the action webhook. Either an array of action keys or a map of action key → display title. */
95
+ actions?: string[] | Record<string, string>;
96
96
  /** Absolute path to the card's markdown file on disk. */
97
97
  filePath: string;
98
98
  }
@@ -283,8 +283,8 @@ export interface CardFrontmatter {
283
283
  order: string;
284
284
  /** Arbitrary user-defined metadata stored as YAML in the frontmatter. */
285
285
  metadata?: Record<string, any>;
286
- /** Named action strings that can be triggered via the action webhook. */
287
- actions?: string[];
286
+ /** Named actions that can be triggered via the action webhook. Either an array of action keys or a map of action key → display title. */
287
+ actions?: string[] | Record<string, string>;
288
288
  }
289
289
  /**
290
290
  * Read-only workspace information displayed in the settings panel.
@@ -343,7 +343,7 @@ export type WebviewMessage = {
343
343
  dueDate: string | null;
344
344
  labels: string[];
345
345
  metadata?: Record<string, any>;
346
- actions?: string[];
346
+ actions?: string[] | Record<string, string>;
347
347
  };
348
348
  } | {
349
349
  type: 'moveCard';