@industry-theme/backlogmd-kanban-panel 1.0.9 → 1.0.11

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.
@@ -3,7 +3,7 @@ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { en
3
3
  var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
4
4
  import { jsxs, jsx, Fragment } from "react/jsx-runtime";
5
5
  import * as React2 from "react";
6
- import React2__default, { forwardRef, createElement, createContext, useState, useEffect, useContext, useRef, useCallback, useImperativeHandle, useMemo, useLayoutEffect } from "react";
6
+ import React2__default, { forwardRef, createElement, createContext, useState, useEffect, useContext, useRef, useCallback, useLayoutEffect, useImperativeHandle, useMemo } from "react";
7
7
  import { createPortal } from "react-dom";
8
8
  /**
9
9
  * @license lucide-react v0.552.0 - ISC
@@ -110,81 +110,89 @@ const createLucideIcon = (iconName, iconNode) => {
110
110
  * This source code is licensed under the ISC license.
111
111
  * See the LICENSE file in the root directory of this source tree.
112
112
  */
113
- const __iconNode$j = [
113
+ const __iconNode$m = [
114
114
  ["path", { d: "m12 19-7-7 7-7", key: "1l729n" }],
115
115
  ["path", { d: "M19 12H5", key: "x3x0zl" }]
116
116
  ];
117
- const ArrowLeft = createLucideIcon("arrow-left", __iconNode$j);
117
+ const ArrowLeft = createLucideIcon("arrow-left", __iconNode$m);
118
118
  /**
119
119
  * @license lucide-react v0.552.0 - ISC
120
120
  *
121
121
  * This source code is licensed under the ISC license.
122
122
  * See the LICENSE file in the root directory of this source tree.
123
123
  */
124
- const __iconNode$i = [
124
+ const __iconNode$l = [
125
125
  ["path", { d: "M8 2v4", key: "1cmpym" }],
126
126
  ["path", { d: "M16 2v4", key: "4m81vk" }],
127
127
  ["rect", { width: "18", height: "18", x: "3", y: "4", rx: "2", key: "1hopcy" }],
128
128
  ["path", { d: "M3 10h18", key: "8toen8" }]
129
129
  ];
130
- const Calendar = createLucideIcon("calendar", __iconNode$i);
130
+ const Calendar = createLucideIcon("calendar", __iconNode$l);
131
131
  /**
132
132
  * @license lucide-react v0.552.0 - ISC
133
133
  *
134
134
  * This source code is licensed under the ISC license.
135
135
  * See the LICENSE file in the root directory of this source tree.
136
136
  */
137
- const __iconNode$h = [["path", { d: "M20 6 9 17l-5-5", key: "1gmf2c" }]];
138
- const Check = createLucideIcon("check", __iconNode$h);
137
+ const __iconNode$k = [["path", { d: "M20 6 9 17l-5-5", key: "1gmf2c" }]];
138
+ const Check = createLucideIcon("check", __iconNode$k);
139
139
  /**
140
140
  * @license lucide-react v0.552.0 - ISC
141
141
  *
142
142
  * This source code is licensed under the ISC license.
143
143
  * See the LICENSE file in the root directory of this source tree.
144
144
  */
145
- const __iconNode$g = [["path", { d: "m6 9 6 6 6-6", key: "qrunsl" }]];
146
- const ChevronDown = createLucideIcon("chevron-down", __iconNode$g);
145
+ const __iconNode$j = [["path", { d: "m6 9 6 6 6-6", key: "qrunsl" }]];
146
+ const ChevronDown = createLucideIcon("chevron-down", __iconNode$j);
147
147
  /**
148
148
  * @license lucide-react v0.552.0 - ISC
149
149
  *
150
150
  * This source code is licensed under the ISC license.
151
151
  * See the LICENSE file in the root directory of this source tree.
152
152
  */
153
- const __iconNode$f = [
153
+ const __iconNode$i = [["path", { d: "m9 18 6-6-6-6", key: "mthhwq" }]];
154
+ const ChevronRight = createLucideIcon("chevron-right", __iconNode$i);
155
+ /**
156
+ * @license lucide-react v0.552.0 - ISC
157
+ *
158
+ * This source code is licensed under the ISC license.
159
+ * See the LICENSE file in the root directory of this source tree.
160
+ */
161
+ const __iconNode$h = [
154
162
  ["circle", { cx: "12", cy: "12", r: "10", key: "1mglay" }],
155
163
  ["line", { x1: "12", x2: "12", y1: "8", y2: "12", key: "1pkeuh" }],
156
164
  ["line", { x1: "12", x2: "12.01", y1: "16", y2: "16", key: "4dfq90" }]
157
165
  ];
158
- const CircleAlert = createLucideIcon("circle-alert", __iconNode$f);
166
+ const CircleAlert = createLucideIcon("circle-alert", __iconNode$h);
159
167
  /**
160
168
  * @license lucide-react v0.552.0 - ISC
161
169
  *
162
170
  * This source code is licensed under the ISC license.
163
171
  * See the LICENSE file in the root directory of this source tree.
164
172
  */
165
- const __iconNode$e = [
173
+ const __iconNode$g = [
166
174
  ["circle", { cx: "12", cy: "12", r: "10", key: "1mglay" }],
167
175
  ["path", { d: "m9 12 2 2 4-4", key: "dzmm74" }]
168
176
  ];
169
- const CircleCheck = createLucideIcon("circle-check", __iconNode$e);
177
+ const CircleCheck = createLucideIcon("circle-check", __iconNode$g);
170
178
  /**
171
179
  * @license lucide-react v0.552.0 - ISC
172
180
  *
173
181
  * This source code is licensed under the ISC license.
174
182
  * See the LICENSE file in the root directory of this source tree.
175
183
  */
176
- const __iconNode$d = [
184
+ const __iconNode$f = [
177
185
  ["rect", { width: "14", height: "14", x: "8", y: "8", rx: "2", ry: "2", key: "17jyea" }],
178
186
  ["path", { d: "M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2", key: "zix9uf" }]
179
187
  ];
180
- const Copy = createLucideIcon("copy", __iconNode$d);
188
+ const Copy = createLucideIcon("copy", __iconNode$f);
181
189
  /**
182
190
  * @license lucide-react v0.552.0 - ISC
183
191
  *
184
192
  * This source code is licensed under the ISC license.
185
193
  * See the LICENSE file in the root directory of this source tree.
186
194
  */
187
- const __iconNode$c = [
195
+ const __iconNode$e = [
188
196
  ["path", { d: "m15 15 6 6", key: "1s409w" }],
189
197
  ["path", { d: "m15 9 6-6", key: "ko1vev" }],
190
198
  ["path", { d: "M21 16v5h-5", key: "1ck2sf" }],
@@ -194,26 +202,26 @@ const __iconNode$c = [
194
202
  ["path", { d: "M3 8V3h5", key: "1ln10m" }],
195
203
  ["path", { d: "M9 9 3 3", key: "v551iv" }]
196
204
  ];
197
- const Expand = createLucideIcon("expand", __iconNode$c);
205
+ const Expand = createLucideIcon("expand", __iconNode$e);
198
206
  /**
199
207
  * @license lucide-react v0.552.0 - ISC
200
208
  *
201
209
  * This source code is licensed under the ISC license.
202
210
  * See the LICENSE file in the root directory of this source tree.
203
211
  */
204
- const __iconNode$b = [
212
+ const __iconNode$d = [
205
213
  ["path", { d: "M15 3h6v6", key: "1q9fwt" }],
206
214
  ["path", { d: "M10 14 21 3", key: "gplh6r" }],
207
215
  ["path", { d: "M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6", key: "a6xqqp" }]
208
216
  ];
209
- const ExternalLink = createLucideIcon("external-link", __iconNode$b);
217
+ const ExternalLink = createLucideIcon("external-link", __iconNode$d);
210
218
  /**
211
219
  * @license lucide-react v0.552.0 - ISC
212
220
  *
213
221
  * This source code is licensed under the ISC license.
214
222
  * See the LICENSE file in the root directory of this source tree.
215
223
  */
216
- const __iconNode$a = [
224
+ const __iconNode$c = [
217
225
  [
218
226
  "path",
219
227
  {
@@ -226,14 +234,14 @@ const __iconNode$a = [
226
234
  ["path", { d: "M16 13H8", key: "t4e002" }],
227
235
  ["path", { d: "M16 17H8", key: "z1uh3a" }]
228
236
  ];
229
- const FileText = createLucideIcon("file-text", __iconNode$a);
237
+ const FileText = createLucideIcon("file-text", __iconNode$c);
230
238
  /**
231
239
  * @license lucide-react v0.552.0 - ISC
232
240
  *
233
241
  * This source code is licensed under the ISC license.
234
242
  * See the LICENSE file in the root directory of this source tree.
235
243
  */
236
- const __iconNode$9 = [
244
+ const __iconNode$b = [
237
245
  [
238
246
  "path",
239
247
  {
@@ -242,14 +250,14 @@ const __iconNode$9 = [
242
250
  }
243
251
  ]
244
252
  ];
245
- const Flag = createLucideIcon("flag", __iconNode$9);
253
+ const Flag = createLucideIcon("flag", __iconNode$b);
246
254
  /**
247
255
  * @license lucide-react v0.552.0 - ISC
248
256
  *
249
257
  * This source code is licensed under the ISC license.
250
258
  * See the LICENSE file in the root directory of this source tree.
251
259
  */
252
- const __iconNode$8 = [
260
+ const __iconNode$a = [
253
261
  ["path", { d: "M12 10v6", key: "1bos4e" }],
254
262
  ["path", { d: "M9 13h6", key: "1uhe8q" }],
255
263
  [
@@ -260,70 +268,70 @@ const __iconNode$8 = [
260
268
  }
261
269
  ]
262
270
  ];
263
- const FolderPlus = createLucideIcon("folder-plus", __iconNode$8);
271
+ const FolderPlus = createLucideIcon("folder-plus", __iconNode$a);
264
272
  /**
265
273
  * @license lucide-react v0.552.0 - ISC
266
274
  *
267
275
  * This source code is licensed under the ISC license.
268
276
  * See the LICENSE file in the root directory of this source tree.
269
277
  */
270
- const __iconNode$7 = [
278
+ const __iconNode$9 = [
271
279
  ["line", { x1: "6", x2: "6", y1: "3", y2: "15", key: "17qcm7" }],
272
280
  ["circle", { cx: "18", cy: "6", r: "3", key: "1h7g24" }],
273
281
  ["circle", { cx: "6", cy: "18", r: "3", key: "fqmcym" }],
274
282
  ["path", { d: "M18 9a9 9 0 0 1-9 9", key: "n2h4wq" }]
275
283
  ];
276
- const GitBranch = createLucideIcon("git-branch", __iconNode$7);
284
+ const GitBranch = createLucideIcon("git-branch", __iconNode$9);
277
285
  /**
278
286
  * @license lucide-react v0.552.0 - ISC
279
287
  *
280
288
  * This source code is licensed under the ISC license.
281
289
  * See the LICENSE file in the root directory of this source tree.
282
290
  */
283
- const __iconNode$6 = [
291
+ const __iconNode$8 = [
284
292
  ["path", { d: "M5 3v14", key: "9nsxs2" }],
285
293
  ["path", { d: "M12 3v8", key: "1h2ygw" }],
286
294
  ["path", { d: "M19 3v18", key: "1sk56x" }]
287
295
  ];
288
- const Kanban = createLucideIcon("kanban", __iconNode$6);
296
+ const Kanban = createLucideIcon("kanban", __iconNode$8);
289
297
  /**
290
298
  * @license lucide-react v0.552.0 - ISC
291
299
  *
292
300
  * This source code is licensed under the ISC license.
293
301
  * See the LICENSE file in the root directory of this source tree.
294
302
  */
295
- const __iconNode$5 = [["path", { d: "M21 12a9 9 0 1 1-6.219-8.56", key: "13zald" }]];
296
- const LoaderCircle = createLucideIcon("loader-circle", __iconNode$5);
303
+ const __iconNode$7 = [["path", { d: "M21 12a9 9 0 1 1-6.219-8.56", key: "13zald" }]];
304
+ const LoaderCircle = createLucideIcon("loader-circle", __iconNode$7);
297
305
  /**
298
306
  * @license lucide-react v0.552.0 - ISC
299
307
  *
300
308
  * This source code is licensed under the ISC license.
301
309
  * See the LICENSE file in the root directory of this source tree.
302
310
  */
303
- const __iconNode$4 = [
311
+ const __iconNode$6 = [
304
312
  ["rect", { width: "20", height: "14", x: "2", y: "3", rx: "2", key: "48i651" }],
305
313
  ["line", { x1: "8", x2: "16", y1: "21", y2: "21", key: "1svkeh" }],
306
314
  ["line", { x1: "12", x2: "12", y1: "17", y2: "21", key: "vw1qmm" }]
307
315
  ];
308
- const Monitor = createLucideIcon("monitor", __iconNode$4);
316
+ const Monitor = createLucideIcon("monitor", __iconNode$6);
309
317
  /**
310
318
  * @license lucide-react v0.552.0 - ISC
311
319
  *
312
320
  * This source code is licensed under the ISC license.
313
321
  * See the LICENSE file in the root directory of this source tree.
314
322
  */
315
- const __iconNode$3 = [
323
+ const __iconNode$5 = [
316
324
  ["path", { d: "M18 8L22 12L18 16", key: "1r0oui" }],
317
325
  ["path", { d: "M2 12H22", key: "1m8cig" }]
318
326
  ];
319
- const MoveRight = createLucideIcon("move-right", __iconNode$3);
327
+ const MoveRight = createLucideIcon("move-right", __iconNode$5);
320
328
  /**
321
329
  * @license lucide-react v0.552.0 - ISC
322
330
  *
323
331
  * This source code is licensed under the ISC license.
324
332
  * See the LICENSE file in the root directory of this source tree.
325
333
  */
326
- const __iconNode$2 = [
334
+ const __iconNode$4 = [
327
335
  [
328
336
  "path",
329
337
  {
@@ -332,14 +340,27 @@ const __iconNode$2 = [
332
340
  }
333
341
  ]
334
342
  ];
335
- const Play = createLucideIcon("play", __iconNode$2);
343
+ const Play = createLucideIcon("play", __iconNode$4);
336
344
  /**
337
345
  * @license lucide-react v0.552.0 - ISC
338
346
  *
339
347
  * This source code is licensed under the ISC license.
340
348
  * See the LICENSE file in the root directory of this source tree.
341
349
  */
342
- const __iconNode$1 = [
350
+ const __iconNode$3 = [
351
+ ["path", { d: "M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8", key: "v9h5vc" }],
352
+ ["path", { d: "M21 3v5h-5", key: "1q7to0" }],
353
+ ["path", { d: "M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16", key: "3uifl3" }],
354
+ ["path", { d: "M8 16H3v5", key: "1cv678" }]
355
+ ];
356
+ const RefreshCw = createLucideIcon("refresh-cw", __iconNode$3);
357
+ /**
358
+ * @license lucide-react v0.552.0 - ISC
359
+ *
360
+ * This source code is licensed under the ISC license.
361
+ * See the LICENSE file in the root directory of this source tree.
362
+ */
363
+ const __iconNode$2 = [
343
364
  [
344
365
  "path",
345
366
  {
@@ -349,7 +370,19 @@ const __iconNode$1 = [
349
370
  ],
350
371
  ["circle", { cx: "7.5", cy: "7.5", r: ".5", fill: "currentColor", key: "kqv944" }]
351
372
  ];
352
- const Tag = createLucideIcon("tag", __iconNode$1);
373
+ const Tag = createLucideIcon("tag", __iconNode$2);
374
+ /**
375
+ * @license lucide-react v0.552.0 - ISC
376
+ *
377
+ * This source code is licensed under the ISC license.
378
+ * See the LICENSE file in the root directory of this source tree.
379
+ */
380
+ const __iconNode$1 = [
381
+ ["circle", { cx: "12", cy: "12", r: "10", key: "1mglay" }],
382
+ ["circle", { cx: "12", cy: "12", r: "6", key: "1vlfrh" }],
383
+ ["circle", { cx: "12", cy: "12", r: "2", key: "1c9p78" }]
384
+ ];
385
+ const Target = createLucideIcon("target", __iconNode$1);
353
386
  /**
354
387
  * @license lucide-react v0.552.0 - ISC
355
388
  *
@@ -601,6 +634,77 @@ function parseTaskMarkdown(content2, filePath) {
601
634
  filePath
602
635
  };
603
636
  }
637
+ function serializeTaskMarkdown(task) {
638
+ const lines = [];
639
+ lines.push("---");
640
+ lines.push(`status: ${task.status}`);
641
+ if (task.priority) {
642
+ lines.push(`priority: ${task.priority}`);
643
+ }
644
+ if (task.assignee && task.assignee.length > 0) {
645
+ lines.push(`assignee: [${task.assignee.join(", ")}]`);
646
+ }
647
+ if (task.reporter) {
648
+ lines.push(`reporter: ${task.reporter}`);
649
+ }
650
+ if (task.labels && task.labels.length > 0) {
651
+ lines.push(`labels: [${task.labels.join(", ")}]`);
652
+ }
653
+ if (task.milestone) {
654
+ lines.push(`milestone: ${task.milestone}`);
655
+ }
656
+ if (task.dependencies && task.dependencies.length > 0) {
657
+ lines.push(`dependencies: [${task.dependencies.join(", ")}]`);
658
+ }
659
+ if (task.parentTaskId) {
660
+ lines.push(`parentTaskId: ${task.parentTaskId}`);
661
+ }
662
+ if (task.subtasks && task.subtasks.length > 0) {
663
+ lines.push(`subtasks: [${task.subtasks.join(", ")}]`);
664
+ }
665
+ if (task.branch) {
666
+ lines.push(`branch: ${task.branch}`);
667
+ }
668
+ if (task.ordinal !== void 0) {
669
+ lines.push(`ordinal: ${task.ordinal}`);
670
+ }
671
+ if (task.createdDate) {
672
+ lines.push(`createdDate: ${task.createdDate}`);
673
+ }
674
+ if (task.updatedDate) {
675
+ lines.push(`updatedDate: ${task.updatedDate}`);
676
+ }
677
+ lines.push("---");
678
+ lines.push("");
679
+ lines.push(`# ${task.title}`);
680
+ lines.push("");
681
+ if (task.description) {
682
+ lines.push(task.description);
683
+ lines.push("");
684
+ }
685
+ if (task.acceptanceCriteriaItems && task.acceptanceCriteriaItems.length > 0) {
686
+ lines.push("## Acceptance Criteria");
687
+ lines.push("");
688
+ for (const criterion of task.acceptanceCriteriaItems) {
689
+ const checkbox = criterion.checked ? "[x]" : "[ ]";
690
+ lines.push(`- ${checkbox} ${criterion.text}`);
691
+ }
692
+ lines.push("");
693
+ }
694
+ if (task.implementationPlan) {
695
+ lines.push("## Implementation Plan");
696
+ lines.push("");
697
+ lines.push(task.implementationPlan);
698
+ lines.push("");
699
+ }
700
+ if (task.implementationNotes) {
701
+ lines.push("## Implementation Notes");
702
+ lines.push("");
703
+ lines.push(task.implementationNotes);
704
+ lines.push("");
705
+ }
706
+ return lines.join("\n");
707
+ }
604
708
  function parseMarkdownContent(content2) {
605
709
  let frontmatter = {};
606
710
  let remaining = content2;
@@ -717,6 +821,79 @@ function extractTaskIndexFromPath(filePath) {
717
821
  function escapeRegex(str) {
718
822
  return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
719
823
  }
824
+ function extractSection(content2, sectionTitle) {
825
+ var _a;
826
+ const src = content2.replace(/\r\n/g, "\n");
827
+ const regex2 = new RegExp(`## ${sectionTitle}\\s*\\n([\\s\\S]*?)(?=\\n## |$)`, "i");
828
+ const match = src.match(regex2);
829
+ return (_a = match == null ? void 0 : match[1]) == null ? void 0 : _a.trim();
830
+ }
831
+ function parseMilestoneFrontmatter(raw2) {
832
+ const result = {};
833
+ for (const line of raw2.split("\n")) {
834
+ const match = line.match(/^(\w+):\s*(.+)$/);
835
+ if (!match)
836
+ continue;
837
+ const [, key, value] = match;
838
+ let cleanValue = value.trim();
839
+ if (cleanValue.startsWith('"') && cleanValue.endsWith('"') || cleanValue.startsWith("'") && cleanValue.endsWith("'")) {
840
+ cleanValue = cleanValue.slice(1, -1);
841
+ }
842
+ switch (key) {
843
+ case "id":
844
+ case "title":
845
+ result[key] = cleanValue;
846
+ break;
847
+ case "tasks":
848
+ if (cleanValue.startsWith("[") && cleanValue.endsWith("]")) {
849
+ result.tasks = cleanValue.slice(1, -1).split(",").map((s2) => s2.trim()).filter(Boolean);
850
+ }
851
+ break;
852
+ }
853
+ }
854
+ return result;
855
+ }
856
+ function parseMilestoneMarkdown(content2) {
857
+ let frontmatter = {};
858
+ let remaining = content2;
859
+ const frontmatterMatch = content2.match(/^---\n([\s\S]*?)\n---\n/);
860
+ if (frontmatterMatch) {
861
+ frontmatter = parseMilestoneFrontmatter(frontmatterMatch[1]);
862
+ remaining = content2.slice(frontmatterMatch[0].length);
863
+ }
864
+ const rawContent = remaining.trim();
865
+ const description = extractSection(rawContent, "Description") || "";
866
+ return {
867
+ id: frontmatter.id || "",
868
+ title: frontmatter.title || "",
869
+ description,
870
+ rawContent,
871
+ tasks: frontmatter.tasks || []
872
+ };
873
+ }
874
+ function serializeMilestoneMarkdown(milestone) {
875
+ const lines = [];
876
+ lines.push("---");
877
+ lines.push(`id: ${milestone.id}`);
878
+ const escapedTitle = milestone.title.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
879
+ lines.push(`title: "${escapedTitle}"`);
880
+ lines.push(`tasks: [${milestone.tasks.join(", ")}]`);
881
+ lines.push("---");
882
+ lines.push("");
883
+ lines.push("## Description");
884
+ lines.push("");
885
+ lines.push(milestone.description || `Milestone: ${milestone.title}`);
886
+ lines.push("");
887
+ return lines.join("\n");
888
+ }
889
+ function getMilestoneFilename(id, title) {
890
+ const safeTitle = title.replace(/[<>:"/\\|?*]/g, "").replace(/\s+/g, "-").toLowerCase().slice(0, 50);
891
+ return `${id} - ${safeTitle}.md`;
892
+ }
893
+ function extractMilestoneIdFromFilename(filename) {
894
+ const match = filename.match(/^(m-\d+)/);
895
+ return match ? match[1] : null;
896
+ }
720
897
  const DEFAULT_STATUSES = ["To Do", "In Progress", "Done"];
721
898
  function parseBacklogConfig(content2) {
722
899
  const config = {};
@@ -928,6 +1105,124 @@ function groupTasksByStatus(tasks, statuses) {
928
1105
  }
929
1106
  return grouped;
930
1107
  }
1108
+ const NO_MILESTONE_KEY = "__none";
1109
+ function normalizeMilestoneName(name2) {
1110
+ return name2.trim();
1111
+ }
1112
+ function milestoneKey(name2) {
1113
+ return normalizeMilestoneName(name2 ?? "").toLowerCase();
1114
+ }
1115
+ function isDoneStatus(status) {
1116
+ const normalized = (status ?? "").toLowerCase();
1117
+ return normalized.includes("done") || normalized.includes("complete");
1118
+ }
1119
+ function getMilestoneLabel(milestoneId, milestoneEntities) {
1120
+ if (!milestoneId) {
1121
+ return "Tasks without milestone";
1122
+ }
1123
+ const entity = milestoneEntities.find((m) => milestoneKey(m.id) === milestoneKey(milestoneId));
1124
+ return (entity == null ? void 0 : entity.title) || milestoneId;
1125
+ }
1126
+ function collectMilestoneIds(tasks, milestoneEntities) {
1127
+ const merged = [];
1128
+ const seen = /* @__PURE__ */ new Set();
1129
+ const addMilestone = (value) => {
1130
+ const normalized = normalizeMilestoneName(value);
1131
+ if (!normalized)
1132
+ return;
1133
+ const key = milestoneKey(normalized);
1134
+ if (seen.has(key))
1135
+ return;
1136
+ seen.add(key);
1137
+ merged.push(normalized);
1138
+ };
1139
+ for (const entity of milestoneEntities) {
1140
+ addMilestone(entity.id);
1141
+ }
1142
+ for (const task of tasks) {
1143
+ addMilestone(task.milestone ?? "");
1144
+ }
1145
+ return merged;
1146
+ }
1147
+ function collectMilestones(tasks, configMilestones) {
1148
+ const merged = [];
1149
+ const seen = /* @__PURE__ */ new Set();
1150
+ const addMilestone = (value) => {
1151
+ const normalized = normalizeMilestoneName(value);
1152
+ if (!normalized)
1153
+ return;
1154
+ const key = milestoneKey(normalized);
1155
+ if (seen.has(key))
1156
+ return;
1157
+ seen.add(key);
1158
+ merged.push(normalized);
1159
+ };
1160
+ for (const m of configMilestones) {
1161
+ addMilestone(m);
1162
+ }
1163
+ for (const task of tasks) {
1164
+ addMilestone(task.milestone ?? "");
1165
+ }
1166
+ return merged;
1167
+ }
1168
+ function createBucket(milestoneId, tasks, statuses, milestoneEntities, isNoMilestone) {
1169
+ const bucketMilestoneKey = milestoneKey(milestoneId);
1170
+ const bucketTasks = tasks.filter((task) => {
1171
+ const taskMilestoneKey = milestoneKey(task.milestone);
1172
+ return bucketMilestoneKey ? taskMilestoneKey === bucketMilestoneKey : !taskMilestoneKey;
1173
+ });
1174
+ const counts = {};
1175
+ for (const status of statuses) {
1176
+ counts[status] = 0;
1177
+ }
1178
+ for (const task of bucketTasks) {
1179
+ const status = task.status ?? "";
1180
+ counts[status] = (counts[status] ?? 0) + 1;
1181
+ }
1182
+ const doneCount = bucketTasks.filter((t) => isDoneStatus(t.status)).length;
1183
+ const progress = bucketTasks.length > 0 ? Math.round(doneCount / bucketTasks.length * 100) : 0;
1184
+ const key = bucketMilestoneKey ? bucketMilestoneKey : NO_MILESTONE_KEY;
1185
+ const label = getMilestoneLabel(milestoneId, milestoneEntities);
1186
+ return {
1187
+ key,
1188
+ label,
1189
+ milestone: milestoneId,
1190
+ isNoMilestone,
1191
+ tasks: bucketTasks,
1192
+ statusCounts: counts,
1193
+ total: bucketTasks.length,
1194
+ doneCount,
1195
+ progress
1196
+ };
1197
+ }
1198
+ function buildMilestoneBuckets(tasks, milestoneEntities, statuses) {
1199
+ const allMilestoneIds = collectMilestoneIds(tasks, milestoneEntities);
1200
+ const buckets = [
1201
+ // "No milestone" bucket first
1202
+ createBucket(void 0, tasks, statuses, milestoneEntities, true),
1203
+ // Then each milestone bucket
1204
+ ...allMilestoneIds.map((m) => createBucket(m, tasks, statuses, milestoneEntities, false))
1205
+ ];
1206
+ return buckets;
1207
+ }
1208
+ function buildMilestoneBucketsFromConfig(tasks, configMilestones, statuses) {
1209
+ const milestoneEntities = configMilestones.map((id) => ({
1210
+ id,
1211
+ title: id,
1212
+ description: "",
1213
+ rawContent: "",
1214
+ tasks: []
1215
+ }));
1216
+ return buildMilestoneBuckets(tasks, milestoneEntities, statuses);
1217
+ }
1218
+ function groupTasksByMilestone(tasks, configMilestones, statuses) {
1219
+ const milestones = collectMilestones(tasks, configMilestones);
1220
+ const buckets = buildMilestoneBucketsFromConfig(tasks, configMilestones, statuses);
1221
+ return {
1222
+ milestones,
1223
+ buckets
1224
+ };
1225
+ }
931
1226
  class Core {
932
1227
  constructor(options) {
933
1228
  __publicField(this, "projectRoot");
@@ -1207,6 +1502,10 @@ class Core {
1207
1502
  if (filter == null ? void 0 : filter.priority) {
1208
1503
  tasks = tasks.filter((t) => t.priority === filter.priority);
1209
1504
  }
1505
+ if (filter == null ? void 0 : filter.milestone) {
1506
+ const filterKey = milestoneKey(filter.milestone);
1507
+ tasks = tasks.filter((t) => milestoneKey(t.milestone) === filterKey);
1508
+ }
1210
1509
  if ((filter == null ? void 0 : filter.labels) && filter.labels.length > 0) {
1211
1510
  tasks = tasks.filter((t) => filter.labels.some((label) => t.labels.includes(label)));
1212
1511
  }
@@ -1227,6 +1526,205 @@ class Core {
1227
1526
  const tasks = Array.from(this.tasks.values());
1228
1527
  return groupTasksByStatus(tasks, this.config.statuses);
1229
1528
  }
1529
+ /**
1530
+ * Get tasks grouped by milestone
1531
+ *
1532
+ * Returns a MilestoneSummary with:
1533
+ * - milestones: List of milestone IDs in display order
1534
+ * - buckets: Array of MilestoneBucket with tasks, progress, status counts
1535
+ *
1536
+ * The first bucket is always "Tasks without milestone".
1537
+ * Each bucket includes progress percentage based on done status.
1538
+ *
1539
+ * @example
1540
+ * ```typescript
1541
+ * const summary = core.getTasksByMilestone();
1542
+ * for (const bucket of summary.buckets) {
1543
+ * console.log(`${bucket.label}: ${bucket.progress}% complete`);
1544
+ * console.log(` ${bucket.doneCount}/${bucket.total} tasks done`);
1545
+ * }
1546
+ * ```
1547
+ */
1548
+ getTasksByMilestone() {
1549
+ this.ensureInitialized();
1550
+ const tasks = Array.from(this.tasks.values());
1551
+ return groupTasksByMilestone(tasks, this.config.milestones, this.config.statuses);
1552
+ }
1553
+ // =========================================================================
1554
+ // Milestone CRUD Operations
1555
+ // =========================================================================
1556
+ /**
1557
+ * Get the milestones directory path
1558
+ */
1559
+ getMilestonesDir() {
1560
+ return this.fs.join(this.projectRoot, "backlog", "milestones");
1561
+ }
1562
+ /**
1563
+ * List all milestones from the milestones directory
1564
+ *
1565
+ * @returns Array of Milestone objects sorted by ID
1566
+ */
1567
+ async listMilestones() {
1568
+ const milestonesDir = this.getMilestonesDir();
1569
+ if (!await this.fs.exists(milestonesDir)) {
1570
+ return [];
1571
+ }
1572
+ const entries = await this.fs.readDir(milestonesDir);
1573
+ const milestones = [];
1574
+ for (const entry of entries) {
1575
+ if (!entry.endsWith(".md"))
1576
+ continue;
1577
+ if (entry.toLowerCase() === "readme.md")
1578
+ continue;
1579
+ const milestoneId = extractMilestoneIdFromFilename(entry);
1580
+ if (!milestoneId)
1581
+ continue;
1582
+ const filepath = this.fs.join(milestonesDir, entry);
1583
+ if (await this.fs.isDirectory(filepath))
1584
+ continue;
1585
+ try {
1586
+ const content2 = await this.fs.readFile(filepath);
1587
+ milestones.push(parseMilestoneMarkdown(content2));
1588
+ } catch (error) {
1589
+ console.warn(`Failed to parse milestone file ${filepath}:`, error);
1590
+ }
1591
+ }
1592
+ return milestones.sort((a, b) => a.id.localeCompare(b.id, void 0, { numeric: true }));
1593
+ }
1594
+ /**
1595
+ * Load a single milestone by ID
1596
+ *
1597
+ * @param id - Milestone ID (e.g., "m-0")
1598
+ * @returns Milestone or null if not found
1599
+ */
1600
+ async loadMilestone(id) {
1601
+ const milestonesDir = this.getMilestonesDir();
1602
+ if (!await this.fs.exists(milestonesDir)) {
1603
+ return null;
1604
+ }
1605
+ const entries = await this.fs.readDir(milestonesDir);
1606
+ const milestoneFile = entries.find((entry) => {
1607
+ const fileId = extractMilestoneIdFromFilename(entry);
1608
+ return fileId === id;
1609
+ });
1610
+ if (!milestoneFile) {
1611
+ return null;
1612
+ }
1613
+ const filepath = this.fs.join(milestonesDir, milestoneFile);
1614
+ try {
1615
+ const content2 = await this.fs.readFile(filepath);
1616
+ return parseMilestoneMarkdown(content2);
1617
+ } catch {
1618
+ return null;
1619
+ }
1620
+ }
1621
+ /**
1622
+ * Create a new milestone
1623
+ *
1624
+ * @param input - Milestone creation input
1625
+ * @returns Created milestone
1626
+ */
1627
+ async createMilestone(input) {
1628
+ const milestonesDir = this.getMilestonesDir();
1629
+ await this.fs.createDir(milestonesDir, { recursive: true });
1630
+ const entries = await this.fs.readDir(milestonesDir).catch(() => []);
1631
+ const existingIds = entries.map((f) => {
1632
+ const match = f.match(/^m-(\d+)/);
1633
+ return (match == null ? void 0 : match[1]) ? parseInt(match[1], 10) : -1;
1634
+ }).filter((id2) => id2 >= 0);
1635
+ const nextId = existingIds.length > 0 ? Math.max(...existingIds) + 1 : 0;
1636
+ const id = `m-${nextId}`;
1637
+ const description = input.description || `Milestone: ${input.title}`;
1638
+ const tempMilestone = {
1639
+ id,
1640
+ title: input.title,
1641
+ description,
1642
+ tasks: []
1643
+ };
1644
+ const content2 = serializeMilestoneMarkdown(tempMilestone);
1645
+ const milestone = {
1646
+ id,
1647
+ title: input.title,
1648
+ description,
1649
+ rawContent: content2,
1650
+ tasks: []
1651
+ };
1652
+ const filename = getMilestoneFilename(id, input.title);
1653
+ const filepath = this.fs.join(milestonesDir, filename);
1654
+ await this.fs.writeFile(filepath, content2);
1655
+ return milestone;
1656
+ }
1657
+ /**
1658
+ * Update an existing milestone
1659
+ *
1660
+ * @param id - Milestone ID to update
1661
+ * @param input - Fields to update
1662
+ * @returns Updated milestone or null if not found
1663
+ */
1664
+ async updateMilestone(id, input) {
1665
+ const existing = await this.loadMilestone(id);
1666
+ if (!existing) {
1667
+ return null;
1668
+ }
1669
+ const milestonesDir = this.getMilestonesDir();
1670
+ const entries = await this.fs.readDir(milestonesDir);
1671
+ const currentFile = entries.find((entry) => {
1672
+ const fileId = extractMilestoneIdFromFilename(entry);
1673
+ return fileId === id;
1674
+ });
1675
+ if (!currentFile) {
1676
+ return null;
1677
+ }
1678
+ const newTitle = input.title ?? existing.title;
1679
+ const newDescription = input.description ?? existing.description;
1680
+ const tempMilestone = {
1681
+ id: existing.id,
1682
+ title: newTitle,
1683
+ description: newDescription,
1684
+ tasks: existing.tasks
1685
+ };
1686
+ const content2 = serializeMilestoneMarkdown(tempMilestone);
1687
+ const updated = {
1688
+ id: existing.id,
1689
+ title: newTitle,
1690
+ description: newDescription,
1691
+ rawContent: content2,
1692
+ tasks: existing.tasks
1693
+ };
1694
+ const oldPath = this.fs.join(milestonesDir, currentFile);
1695
+ await this.fs.deleteFile(oldPath);
1696
+ const newFilename = getMilestoneFilename(id, updated.title);
1697
+ const newPath = this.fs.join(milestonesDir, newFilename);
1698
+ await this.fs.writeFile(newPath, content2);
1699
+ return updated;
1700
+ }
1701
+ /**
1702
+ * Delete a milestone
1703
+ *
1704
+ * @param id - Milestone ID to delete
1705
+ * @returns true if deleted, false if not found
1706
+ */
1707
+ async deleteMilestone(id) {
1708
+ const milestonesDir = this.getMilestonesDir();
1709
+ if (!await this.fs.exists(milestonesDir)) {
1710
+ return false;
1711
+ }
1712
+ const entries = await this.fs.readDir(milestonesDir);
1713
+ const milestoneFile = entries.find((entry) => {
1714
+ const fileId = extractMilestoneIdFromFilename(entry);
1715
+ return fileId === id;
1716
+ });
1717
+ if (!milestoneFile) {
1718
+ return false;
1719
+ }
1720
+ const filepath = this.fs.join(milestonesDir, milestoneFile);
1721
+ try {
1722
+ await this.fs.deleteFile(filepath);
1723
+ return true;
1724
+ } catch {
1725
+ return false;
1726
+ }
1727
+ }
1230
1728
  /**
1231
1729
  * Get a single task by ID
1232
1730
  *
@@ -1359,6 +1857,10 @@ class Core {
1359
1857
  if (filter.priority) {
1360
1858
  result = result.filter((t) => t.priority === filter.priority);
1361
1859
  }
1860
+ if (filter.milestone) {
1861
+ const filterKey = milestoneKey(filter.milestone);
1862
+ result = result.filter((t) => milestoneKey(t.milestone) === filterKey);
1863
+ }
1362
1864
  if (filter.labels && filter.labels.length > 0) {
1363
1865
  result = result.filter((t) => filter.labels.some((label) => t.labels.includes(label)));
1364
1866
  }
@@ -1387,6 +1889,247 @@ class Core {
1387
1889
  }
1388
1890
  }
1389
1891
  }
1892
+ // =========================================================================
1893
+ // Milestone-Task Sync Helpers
1894
+ // =========================================================================
1895
+ /**
1896
+ * Add a task ID to a milestone's tasks array
1897
+ *
1898
+ * @param taskId - Task ID to add
1899
+ * @param milestoneId - Milestone ID to update
1900
+ */
1901
+ async addTaskToMilestone(taskId, milestoneId) {
1902
+ const milestone = await this.loadMilestone(milestoneId);
1903
+ if (!milestone) {
1904
+ console.warn(`Milestone ${milestoneId} not found when adding task ${taskId}`);
1905
+ return;
1906
+ }
1907
+ if (milestone.tasks.includes(taskId)) {
1908
+ return;
1909
+ }
1910
+ const updatedMilestone = {
1911
+ ...milestone,
1912
+ tasks: [...milestone.tasks, taskId]
1913
+ };
1914
+ await this.writeMilestoneFile(updatedMilestone);
1915
+ }
1916
+ /**
1917
+ * Remove a task ID from a milestone's tasks array
1918
+ *
1919
+ * @param taskId - Task ID to remove
1920
+ * @param milestoneId - Milestone ID to update
1921
+ */
1922
+ async removeTaskFromMilestone(taskId, milestoneId) {
1923
+ const milestone = await this.loadMilestone(milestoneId);
1924
+ if (!milestone) {
1925
+ return;
1926
+ }
1927
+ if (!milestone.tasks.includes(taskId)) {
1928
+ return;
1929
+ }
1930
+ const updatedMilestone = {
1931
+ ...milestone,
1932
+ tasks: milestone.tasks.filter((id) => id !== taskId)
1933
+ };
1934
+ await this.writeMilestoneFile(updatedMilestone);
1935
+ }
1936
+ /**
1937
+ * Write a milestone to disk
1938
+ */
1939
+ async writeMilestoneFile(milestone) {
1940
+ const milestonesDir = this.getMilestonesDir();
1941
+ const entries = await this.fs.readDir(milestonesDir).catch(() => []);
1942
+ const currentFile = entries.find((entry) => {
1943
+ const fileId = extractMilestoneIdFromFilename(entry);
1944
+ return fileId === milestone.id;
1945
+ });
1946
+ if (currentFile) {
1947
+ const oldPath = this.fs.join(milestonesDir, currentFile);
1948
+ await this.fs.deleteFile(oldPath).catch(() => {
1949
+ });
1950
+ }
1951
+ const content2 = serializeMilestoneMarkdown(milestone);
1952
+ const filename = getMilestoneFilename(milestone.id, milestone.title);
1953
+ const filepath = this.fs.join(milestonesDir, filename);
1954
+ await this.fs.writeFile(filepath, content2);
1955
+ }
1956
+ /**
1957
+ * Get the tasks directory path
1958
+ */
1959
+ getTasksDir() {
1960
+ return this.fs.join(this.projectRoot, "backlog", "tasks");
1961
+ }
1962
+ // =========================================================================
1963
+ // Task CRUD Operations
1964
+ // =========================================================================
1965
+ /**
1966
+ * Create a new task
1967
+ *
1968
+ * @param input - Task creation input
1969
+ * @returns Created task
1970
+ */
1971
+ async createTask(input) {
1972
+ var _a;
1973
+ this.ensureInitialized();
1974
+ const tasksDir = this.getTasksDir();
1975
+ await this.fs.createDir(tasksDir, { recursive: true });
1976
+ const existingIds = Array.from(this.tasks.keys()).map((id) => parseInt(id.replace(/\D/g, ""), 10)).filter((n) => !isNaN(n));
1977
+ const nextId = existingIds.length > 0 ? Math.max(...existingIds) + 1 : 1;
1978
+ const taskId = String(nextId);
1979
+ const now = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
1980
+ const task = {
1981
+ id: taskId,
1982
+ title: input.title,
1983
+ status: input.status || this.config.defaultStatus || "To Do",
1984
+ priority: input.priority,
1985
+ assignee: input.assignee || [],
1986
+ createdDate: now,
1987
+ labels: input.labels || [],
1988
+ milestone: input.milestone,
1989
+ dependencies: input.dependencies || [],
1990
+ parentTaskId: input.parentTaskId,
1991
+ description: input.description,
1992
+ implementationPlan: input.implementationPlan,
1993
+ implementationNotes: input.implementationNotes,
1994
+ acceptanceCriteriaItems: (_a = input.acceptanceCriteria) == null ? void 0 : _a.map((ac, i) => ({
1995
+ index: i + 1,
1996
+ text: ac.text,
1997
+ checked: ac.checked || false
1998
+ })),
1999
+ rawContent: input.rawContent,
2000
+ source: "local"
2001
+ };
2002
+ const content2 = serializeTaskMarkdown(task);
2003
+ const safeTitle = input.title.replace(/[<>:"/\\|?*]/g, "").replace(/\s+/g, " ").slice(0, 50);
2004
+ const filename = `${taskId} - ${safeTitle}.md`;
2005
+ const filepath = this.fs.join(tasksDir, filename);
2006
+ await this.fs.writeFile(filepath, content2);
2007
+ task.filePath = filepath;
2008
+ this.tasks.set(taskId, task);
2009
+ if (input.milestone) {
2010
+ await this.addTaskToMilestone(taskId, input.milestone);
2011
+ }
2012
+ return task;
2013
+ }
2014
+ /**
2015
+ * Update an existing task
2016
+ *
2017
+ * @param id - Task ID to update
2018
+ * @param input - Fields to update
2019
+ * @returns Updated task or null if not found
2020
+ */
2021
+ async updateTask(id, input) {
2022
+ this.ensureInitialized();
2023
+ const existing = this.tasks.get(id);
2024
+ if (!existing) {
2025
+ return null;
2026
+ }
2027
+ const oldMilestone = existing.milestone;
2028
+ const newMilestone = input.milestone === null ? void 0 : input.milestone !== void 0 ? input.milestone : oldMilestone;
2029
+ const now = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
2030
+ const updated = {
2031
+ ...existing,
2032
+ title: input.title ?? existing.title,
2033
+ status: input.status ?? existing.status,
2034
+ priority: input.priority ?? existing.priority,
2035
+ milestone: newMilestone,
2036
+ updatedDate: now,
2037
+ description: input.description ?? existing.description,
2038
+ implementationPlan: input.clearImplementationPlan ? void 0 : input.implementationPlan ?? existing.implementationPlan,
2039
+ implementationNotes: input.clearImplementationNotes ? void 0 : input.implementationNotes ?? existing.implementationNotes,
2040
+ ordinal: input.ordinal ?? existing.ordinal,
2041
+ dependencies: input.dependencies ?? existing.dependencies
2042
+ };
2043
+ if (input.labels) {
2044
+ updated.labels = input.labels;
2045
+ } else {
2046
+ if (input.addLabels) {
2047
+ updated.labels = [.../* @__PURE__ */ new Set([...updated.labels, ...input.addLabels])];
2048
+ }
2049
+ if (input.removeLabels) {
2050
+ updated.labels = updated.labels.filter((l) => !input.removeLabels.includes(l));
2051
+ }
2052
+ }
2053
+ if (input.assignee) {
2054
+ updated.assignee = input.assignee;
2055
+ }
2056
+ if (input.addDependencies) {
2057
+ updated.dependencies = [
2058
+ .../* @__PURE__ */ new Set([...updated.dependencies, ...input.addDependencies])
2059
+ ];
2060
+ }
2061
+ if (input.removeDependencies) {
2062
+ updated.dependencies = updated.dependencies.filter((d) => !input.removeDependencies.includes(d));
2063
+ }
2064
+ if (input.acceptanceCriteria) {
2065
+ updated.acceptanceCriteriaItems = input.acceptanceCriteria.map((ac, i) => ({
2066
+ index: i + 1,
2067
+ text: ac.text,
2068
+ checked: ac.checked || false
2069
+ }));
2070
+ }
2071
+ const content2 = serializeTaskMarkdown(updated);
2072
+ if (existing.filePath) {
2073
+ await this.fs.deleteFile(existing.filePath).catch(() => {
2074
+ });
2075
+ }
2076
+ const tasksDir = this.getTasksDir();
2077
+ const safeTitle = updated.title.replace(/[<>:"/\\|?*]/g, "").replace(/\s+/g, " ").slice(0, 50);
2078
+ const filename = `${id} - ${safeTitle}.md`;
2079
+ const filepath = this.fs.join(tasksDir, filename);
2080
+ await this.fs.writeFile(filepath, content2);
2081
+ updated.filePath = filepath;
2082
+ this.tasks.set(id, updated);
2083
+ const milestoneChanged = milestoneKey(oldMilestone) !== milestoneKey(newMilestone);
2084
+ if (milestoneChanged) {
2085
+ if (oldMilestone) {
2086
+ await this.removeTaskFromMilestone(id, oldMilestone);
2087
+ }
2088
+ if (newMilestone) {
2089
+ await this.addTaskToMilestone(id, newMilestone);
2090
+ }
2091
+ }
2092
+ return updated;
2093
+ }
2094
+ /**
2095
+ * Delete a task
2096
+ *
2097
+ * @param id - Task ID to delete
2098
+ * @returns true if deleted, false if not found
2099
+ */
2100
+ async deleteTask(id) {
2101
+ this.ensureInitialized();
2102
+ const task = this.tasks.get(id);
2103
+ if (!task) {
2104
+ return false;
2105
+ }
2106
+ if (task.milestone) {
2107
+ await this.removeTaskFromMilestone(id, task.milestone);
2108
+ }
2109
+ if (task.filePath) {
2110
+ try {
2111
+ await this.fs.deleteFile(task.filePath);
2112
+ } catch {
2113
+ }
2114
+ }
2115
+ this.tasks.delete(id);
2116
+ return true;
2117
+ }
2118
+ /**
2119
+ * Load specific tasks by their IDs (for lazy loading milestone tasks)
2120
+ *
2121
+ * @param ids - Array of task IDs to load
2122
+ * @returns Array of loaded tasks (missing tasks excluded)
2123
+ */
2124
+ async loadTasksByIds(ids) {
2125
+ if (this.initialized) {
2126
+ return ids.map((id) => this.tasks.get(id)).filter((t) => t !== void 0);
2127
+ }
2128
+ if (this.lazyInitialized) {
2129
+ return this.loadTasks(ids);
2130
+ }
2131
+ throw new Error("Core not initialized. Call initialize() or initializeLazy() first.");
2132
+ }
1390
2133
  }
1391
2134
  class PanelFileSystemAdapter {
1392
2135
  constructor(access) {
@@ -1818,7 +2561,8 @@ const KanbanColumn = ({
1818
2561
  hasMore = false,
1819
2562
  isLoadingMore = false,
1820
2563
  onLoadMore,
1821
- onTaskClick
2564
+ onTaskClick,
2565
+ fullWidth = false
1822
2566
  }) => {
1823
2567
  const { theme: theme2 } = useTheme();
1824
2568
  const getPriorityColor = (priority) => {
@@ -1840,8 +2584,8 @@ const KanbanColumn = ({
1840
2584
  style: {
1841
2585
  flex: "1 1 0",
1842
2586
  // Grow to fill available width equally
1843
- minWidth: "280px",
1844
- maxWidth: "500px",
2587
+ minWidth: fullWidth ? void 0 : "280px",
2588
+ maxWidth: fullWidth ? void 0 : "500px",
1845
2589
  // Cap max width for readability
1846
2590
  height: "100%",
1847
2591
  // Fill parent height
@@ -1905,7 +2649,11 @@ const KanbanColumn = ({
1905
2649
  flexDirection: "column",
1906
2650
  gap: "8px",
1907
2651
  overflowY: "auto",
1908
- WebkitOverflowScrolling: "touch"
2652
+ overflowX: "hidden",
2653
+ WebkitOverflowScrolling: "touch",
2654
+ // Add padding for scroll content, use margin trick to prevent clipping
2655
+ paddingRight: "4px",
2656
+ marginRight: "-4px"
1909
2657
  },
1910
2658
  children: [
1911
2659
  tasks.map((task) => /* @__PURE__ */ jsxs(
@@ -2084,7 +2832,8 @@ const KanbanColumn = ({
2084
2832
  "Loading..."
2085
2833
  ] }) : `Load more (${remaining} remaining)`
2086
2834
  }
2087
- )
2835
+ ),
2836
+ /* @__PURE__ */ jsx("div", { style: { flexShrink: 0, height: "4px" } })
2088
2837
  ]
2089
2838
  }
2090
2839
  ),
@@ -2354,6 +3103,20 @@ const KanbanPanelContent = ({
2354
3103
  var _a, _b;
2355
3104
  const { theme: theme2 } = useTheme();
2356
3105
  const [_selectedTask, setSelectedTask] = useState(null);
3106
+ const [selectedTab, setSelectedTab] = useState("todo");
3107
+ const [isNarrowView, setIsNarrowView] = useState(false);
3108
+ const containerRef = useRef(null);
3109
+ useLayoutEffect(() => {
3110
+ const container = containerRef.current;
3111
+ if (!container) return;
3112
+ const observer = new ResizeObserver((entries) => {
3113
+ for (const entry of entries) {
3114
+ setIsNarrowView(entry.contentRect.width < 768);
3115
+ }
3116
+ });
3117
+ observer.observe(container);
3118
+ return () => observer.disconnect();
3119
+ }, []);
2357
3120
  const {
2358
3121
  statusColumns,
2359
3122
  tasksByStatus,
@@ -2444,6 +3207,7 @@ const KanbanPanelContent = ({
2444
3207
  return /* @__PURE__ */ jsxs(
2445
3208
  "div",
2446
3209
  {
3210
+ ref: containerRef,
2447
3211
  style: {
2448
3212
  padding: "clamp(12px, 3vw, 20px)",
2449
3213
  // Responsive padding for mobile
@@ -2488,7 +3252,7 @@ const KanbanPanelContent = ({
2488
3252
  )
2489
3253
  ] }),
2490
3254
  isBacklogProject && /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: "16px", flexWrap: "wrap" }, children: [
2491
- /* @__PURE__ */ jsx("div", { style: { display: "flex", alignItems: "center", gap: "12px" }, children: statusColumns.map((status) => {
3255
+ !isNarrowView && /* @__PURE__ */ jsx("div", { style: { display: "flex", alignItems: "center", gap: "12px" }, children: statusColumns.map((status) => {
2492
3256
  const statusState = tasksByStatus.get(status);
2493
3257
  const count = (statusState == null ? void 0 : statusState.count) || 0;
2494
3258
  const completedState = columnStates.get("completed");
@@ -2565,44 +3329,113 @@ const KanbanPanelContent = ({
2565
3329
  canInitialize,
2566
3330
  onInitialize: handleInitialize
2567
3331
  }
2568
- ) : /* @__PURE__ */ jsx(
2569
- "div",
2570
- {
2571
- style: {
2572
- flex: 1,
2573
- display: "flex",
2574
- gap: "16px",
2575
- justifyContent: "center",
2576
- // Center columns when they hit max-width
2577
- overflowX: "auto",
2578
- overflowY: "hidden",
2579
- paddingBottom: "8px",
2580
- minHeight: 0,
2581
- // Allow flex child to shrink below content size
2582
- WebkitOverflowScrolling: "touch"
2583
- // Smooth scrolling on iOS
2584
- },
2585
- children: statusColumns.map((status) => {
2586
- const statusState = tasksByStatus.get(status);
2587
- const columnTasks = (statusState == null ? void 0 : statusState.tasks) || [];
2588
- const isCompleted = status === "completed";
2589
- const completedState = columnStates.get("completed");
2590
- return /* @__PURE__ */ jsx(
2591
- KanbanColumn,
3332
+ ) : /* @__PURE__ */ jsxs(Fragment, { children: [
3333
+ isNarrowView && /* @__PURE__ */ jsx(
3334
+ "div",
3335
+ {
3336
+ style: {
3337
+ flexShrink: 0,
3338
+ display: "flex",
3339
+ gap: "4px",
3340
+ background: theme2.colors.backgroundSecondary,
3341
+ borderRadius: theme2.radii[2],
3342
+ padding: "4px"
3343
+ },
3344
+ children: statusColumns.map((status) => {
3345
+ const isSelected = status === selectedTab;
3346
+ const statusState = tasksByStatus.get(status);
3347
+ const count = (statusState == null ? void 0 : statusState.count) || 0;
3348
+ return /* @__PURE__ */ jsxs(
3349
+ "button",
3350
+ {
3351
+ onClick: () => setSelectedTab(status),
3352
+ style: {
3353
+ flex: 1,
3354
+ padding: "10px 12px",
3355
+ border: "none",
3356
+ borderRadius: theme2.radii[1],
3357
+ background: isSelected ? theme2.colors.primary : "transparent",
3358
+ color: isSelected ? theme2.colors.background : theme2.colors.textSecondary,
3359
+ fontSize: theme2.fontSizes[1],
3360
+ fontWeight: theme2.fontWeights.medium,
3361
+ cursor: "pointer",
3362
+ transition: "all 0.2s ease",
3363
+ display: "flex",
3364
+ alignItems: "center",
3365
+ justifyContent: "center",
3366
+ gap: "6px"
3367
+ },
3368
+ children: [
3369
+ STATUS_DISPLAY_LABELS[status],
3370
+ /* @__PURE__ */ jsx(
3371
+ "span",
3372
+ {
3373
+ style: {
3374
+ background: isSelected ? "rgba(255,255,255,0.2)" : theme2.colors.background,
3375
+ padding: "2px 6px",
3376
+ borderRadius: theme2.radii[1],
3377
+ fontSize: theme2.fontSizes[0]
3378
+ },
3379
+ children: count
3380
+ }
3381
+ )
3382
+ ]
3383
+ },
3384
+ status
3385
+ );
3386
+ })
3387
+ }
3388
+ ),
3389
+ /* @__PURE__ */ jsx(
3390
+ "div",
3391
+ {
3392
+ style: {
3393
+ flex: 1,
3394
+ overflowX: isNarrowView ? "hidden" : "auto",
3395
+ overflowY: "hidden",
3396
+ minHeight: 0,
3397
+ // Allow flex child to shrink below content size
3398
+ WebkitOverflowScrolling: "touch"
3399
+ // Smooth scrolling on iOS
3400
+ },
3401
+ children: /* @__PURE__ */ jsx(
3402
+ "div",
2592
3403
  {
2593
- status: STATUS_DISPLAY_LABELS[status],
2594
- tasks: columnTasks,
2595
- total: isCompleted ? completedState == null ? void 0 : completedState.total : void 0,
2596
- hasMore: isCompleted ? completedState == null ? void 0 : completedState.hasMore : false,
2597
- isLoadingMore: isCompleted ? completedState == null ? void 0 : completedState.isLoadingMore : false,
2598
- onLoadMore: isCompleted ? () => loadMore("completed") : void 0,
2599
- onTaskClick: handleTaskClick
2600
- },
2601
- status
2602
- );
2603
- })
2604
- }
2605
- )
3404
+ style: {
3405
+ display: "flex",
3406
+ gap: "16px",
3407
+ height: "100%",
3408
+ paddingBottom: "8px",
3409
+ width: "fit-content",
3410
+ minWidth: "100%",
3411
+ margin: "0 auto"
3412
+ // Center when columns don't fill width
3413
+ },
3414
+ children: statusColumns.filter((status) => !isNarrowView || status === selectedTab).map((status) => {
3415
+ const statusState = tasksByStatus.get(status);
3416
+ const columnTasks = (statusState == null ? void 0 : statusState.tasks) || [];
3417
+ const isCompleted = status === "completed";
3418
+ const completedState = columnStates.get("completed");
3419
+ return /* @__PURE__ */ jsx(
3420
+ KanbanColumn,
3421
+ {
3422
+ status: STATUS_DISPLAY_LABELS[status],
3423
+ tasks: columnTasks,
3424
+ total: isCompleted ? completedState == null ? void 0 : completedState.total : void 0,
3425
+ hasMore: isCompleted ? completedState == null ? void 0 : completedState.hasMore : false,
3426
+ isLoadingMore: isCompleted ? completedState == null ? void 0 : completedState.isLoadingMore : false,
3427
+ onLoadMore: isCompleted ? () => loadMore("completed") : void 0,
3428
+ onTaskClick: handleTaskClick,
3429
+ fullWidth: isNarrowView
3430
+ },
3431
+ status
3432
+ );
3433
+ })
3434
+ }
3435
+ )
3436
+ }
3437
+ )
3438
+ ] })
2606
3439
  ]
2607
3440
  }
2608
3441
  );
@@ -46939,6 +47772,803 @@ const TaskDetailPanelContent = ({ context, actions, events }) => {
46939
47772
  const TaskDetailPanel = (props) => {
46940
47773
  return /* @__PURE__ */ jsx(ThemeProvider, { children: /* @__PURE__ */ jsx(TaskDetailPanelContent, { ...props }) });
46941
47774
  };
47775
+ function useMilestoneData(options) {
47776
+ const { context, actions } = options || {};
47777
+ const [milestones, setMilestones] = useState([]);
47778
+ const [isLoading, setIsLoading] = useState(true);
47779
+ const [error, setError] = useState(null);
47780
+ const [isBacklogProject, setIsBacklogProject] = useState(false);
47781
+ const coreRef = useRef(null);
47782
+ const activeFilePathRef = useRef(null);
47783
+ const contextRef = useRef(context);
47784
+ const actionsRef = useRef(actions);
47785
+ useEffect(() => {
47786
+ contextRef.current = context;
47787
+ actionsRef.current = actions;
47788
+ }, [context, actions]);
47789
+ const fetchFileContent = useCallback(async (path2) => {
47790
+ const currentContext = contextRef.current;
47791
+ const currentActions = actionsRef.current;
47792
+ if (!currentActions || !currentContext) {
47793
+ throw new Error("PanelContext not available");
47794
+ }
47795
+ if (activeFilePathRef.current === path2) {
47796
+ await new Promise((resolve) => setTimeout(resolve, 100));
47797
+ }
47798
+ activeFilePathRef.current = path2;
47799
+ try {
47800
+ if (currentActions.openFile) {
47801
+ const result = await currentActions.openFile(path2);
47802
+ if (typeof result === "string") {
47803
+ return result;
47804
+ }
47805
+ } else {
47806
+ throw new Error("openFile action not available");
47807
+ }
47808
+ const activeFileSlice = currentContext.getRepositorySlice("active-file");
47809
+ const fileData = activeFileSlice == null ? void 0 : activeFileSlice.data;
47810
+ if (!(fileData == null ? void 0 : fileData.content)) {
47811
+ throw new Error(`Failed to fetch content for ${path2}`);
47812
+ }
47813
+ return fileData.content;
47814
+ } finally {
47815
+ activeFilePathRef.current = null;
47816
+ }
47817
+ }, []);
47818
+ const fileTreeVersionRef = useRef(null);
47819
+ const loadMilestoneData = useCallback(async () => {
47820
+ var _a, _b;
47821
+ if (!context || !actions) {
47822
+ console.log("[useMilestoneData] No context provided");
47823
+ setIsBacklogProject(false);
47824
+ setMilestones([]);
47825
+ setIsLoading(false);
47826
+ coreRef.current = null;
47827
+ fileTreeVersionRef.current = null;
47828
+ return;
47829
+ }
47830
+ const fileTreeSlice = context.getRepositorySlice("fileTree");
47831
+ if (!((_a = fileTreeSlice == null ? void 0 : fileTreeSlice.data) == null ? void 0 : _a.allFiles)) {
47832
+ console.log("[useMilestoneData] FileTree not available");
47833
+ setIsBacklogProject(false);
47834
+ setMilestones([]);
47835
+ coreRef.current = null;
47836
+ fileTreeVersionRef.current = null;
47837
+ return;
47838
+ }
47839
+ const currentVersion = fileTreeSlice.data.sha || ((_b = fileTreeSlice.data.metadata) == null ? void 0 : _b.sourceSha) || "unknown";
47840
+ if (coreRef.current && fileTreeVersionRef.current === currentVersion) {
47841
+ console.log("[useMilestoneData] Data already loaded for this version");
47842
+ setIsLoading(false);
47843
+ return;
47844
+ }
47845
+ setIsLoading(true);
47846
+ setError(null);
47847
+ try {
47848
+ const files = fileTreeSlice.data.allFiles;
47849
+ const filePaths = files.map((f) => f.path);
47850
+ const fs = new PanelFileSystemAdapter({
47851
+ fetchFile: fetchFileContent,
47852
+ filePaths
47853
+ });
47854
+ const core2 = new Core({
47855
+ projectRoot: "",
47856
+ adapters: { fs }
47857
+ });
47858
+ const isProject = await core2.isBacklogProject();
47859
+ if (!isProject) {
47860
+ console.log("[useMilestoneData] Not a Backlog.md project");
47861
+ setIsBacklogProject(false);
47862
+ setMilestones([]);
47863
+ coreRef.current = null;
47864
+ return;
47865
+ }
47866
+ console.log("[useMilestoneData] Loading milestone data...");
47867
+ setIsBacklogProject(true);
47868
+ await core2.initializeLazy(filePaths);
47869
+ coreRef.current = core2;
47870
+ console.log("[useMilestoneData] Core instance:", core2);
47871
+ console.log("[useMilestoneData] Core.listMilestones:", typeof core2.listMilestones);
47872
+ console.log("[useMilestoneData] Core prototype:", Object.getPrototypeOf(core2));
47873
+ if (typeof core2.listMilestones !== "function") {
47874
+ console.error("[useMilestoneData] core.listMilestones is not a function!");
47875
+ console.error("[useMilestoneData] Core methods:", Object.keys(core2));
47876
+ console.error("[useMilestoneData] Core prototype methods:", Object.getOwnPropertyNames(Object.getPrototypeOf(core2)));
47877
+ throw new Error("core.listMilestones is not available - check @backlog-md/core version");
47878
+ }
47879
+ const milestoneList = await core2.listMilestones();
47880
+ console.log(`[useMilestoneData] Loaded ${milestoneList.length} milestones`);
47881
+ const milestoneStates = milestoneList.map((m) => ({
47882
+ milestone: m,
47883
+ tasks: [],
47884
+ isLoading: false,
47885
+ isExpanded: false
47886
+ }));
47887
+ fileTreeVersionRef.current = currentVersion;
47888
+ setMilestones(milestoneStates);
47889
+ } catch (err) {
47890
+ console.error("[useMilestoneData] Failed to load:", err);
47891
+ setError(err instanceof Error ? err.message : "Failed to load milestone data");
47892
+ setIsBacklogProject(false);
47893
+ setMilestones([]);
47894
+ coreRef.current = null;
47895
+ fileTreeVersionRef.current = null;
47896
+ } finally {
47897
+ setIsLoading(false);
47898
+ }
47899
+ }, [context, actions, fetchFileContent]);
47900
+ useEffect(() => {
47901
+ loadMilestoneData();
47902
+ }, [loadMilestoneData]);
47903
+ const expandMilestone = useCallback(async (milestoneId) => {
47904
+ const core2 = coreRef.current;
47905
+ if (!core2) {
47906
+ console.warn("[useMilestoneData] Core not available");
47907
+ return;
47908
+ }
47909
+ const milestoneState = milestones.find((m) => m.milestone.id === milestoneId);
47910
+ if (!milestoneState) {
47911
+ console.warn(`[useMilestoneData] Milestone ${milestoneId} not found`);
47912
+ return;
47913
+ }
47914
+ if (milestoneState.tasks.length > 0) {
47915
+ setMilestones(
47916
+ (prev) => prev.map(
47917
+ (m) => m.milestone.id === milestoneId ? { ...m, isExpanded: true } : m
47918
+ )
47919
+ );
47920
+ return;
47921
+ }
47922
+ setMilestones(
47923
+ (prev) => prev.map(
47924
+ (m) => m.milestone.id === milestoneId ? { ...m, isLoading: true, isExpanded: true } : m
47925
+ )
47926
+ );
47927
+ try {
47928
+ const taskIds = milestoneState.milestone.tasks;
47929
+ console.log(`[useMilestoneData] Loading ${taskIds.length} tasks for milestone ${milestoneId}`);
47930
+ const tasks = await core2.loadTasksByIds(taskIds);
47931
+ console.log(`[useMilestoneData] Loaded ${tasks.length} tasks`);
47932
+ setMilestones(
47933
+ (prev) => prev.map(
47934
+ (m) => m.milestone.id === milestoneId ? { ...m, tasks, isLoading: false } : m
47935
+ )
47936
+ );
47937
+ } catch (err) {
47938
+ console.error(`[useMilestoneData] Failed to load tasks for ${milestoneId}:`, err);
47939
+ setError(err instanceof Error ? err.message : "Failed to load tasks");
47940
+ setMilestones(
47941
+ (prev) => prev.map(
47942
+ (m) => m.milestone.id === milestoneId ? { ...m, isLoading: false } : m
47943
+ )
47944
+ );
47945
+ }
47946
+ }, [milestones]);
47947
+ const collapseMilestone = useCallback((milestoneId) => {
47948
+ setMilestones(
47949
+ (prev) => prev.map(
47950
+ (m) => m.milestone.id === milestoneId ? { ...m, isExpanded: false } : m
47951
+ )
47952
+ );
47953
+ }, []);
47954
+ const toggleMilestone = useCallback(async (milestoneId) => {
47955
+ const milestoneState = milestones.find((m) => m.milestone.id === milestoneId);
47956
+ if (!milestoneState) return;
47957
+ if (milestoneState.isExpanded) {
47958
+ collapseMilestone(milestoneId);
47959
+ } else {
47960
+ await expandMilestone(milestoneId);
47961
+ }
47962
+ }, [milestones, expandMilestone, collapseMilestone]);
47963
+ const refreshData = useCallback(async () => {
47964
+ fileTreeVersionRef.current = null;
47965
+ await loadMilestoneData();
47966
+ }, [loadMilestoneData]);
47967
+ return {
47968
+ milestones,
47969
+ isLoading,
47970
+ error,
47971
+ isBacklogProject,
47972
+ expandMilestone,
47973
+ collapseMilestone,
47974
+ toggleMilestone,
47975
+ refreshData
47976
+ };
47977
+ }
47978
+ const MilestoneCard = ({
47979
+ milestoneState,
47980
+ onToggle,
47981
+ onTaskClick
47982
+ }) => {
47983
+ const { theme: theme2 } = useTheme();
47984
+ const { milestone, tasks, isLoading, isExpanded } = milestoneState;
47985
+ const totalTasks = milestone.tasks.length;
47986
+ const doneTasks = tasks.filter(
47987
+ (t) => {
47988
+ var _a, _b;
47989
+ return ((_a = t.status) == null ? void 0 : _a.toLowerCase().includes("done")) || ((_b = t.status) == null ? void 0 : _b.toLowerCase().includes("complete"));
47990
+ }
47991
+ ).length;
47992
+ const progress = totalTasks > 0 ? Math.round(doneTasks / totalTasks * 100) : 0;
47993
+ const showProgress = tasks.length > 0 || totalTasks === 0;
47994
+ const getPriorityColor = (priority) => {
47995
+ switch (priority) {
47996
+ case "high":
47997
+ return theme2.colors.error;
47998
+ case "medium":
47999
+ return theme2.colors.warning;
48000
+ case "low":
48001
+ return theme2.colors.info;
48002
+ default:
48003
+ return theme2.colors.border;
48004
+ }
48005
+ };
48006
+ return /* @__PURE__ */ jsxs(
48007
+ "div",
48008
+ {
48009
+ style: {
48010
+ flexShrink: 0,
48011
+ background: theme2.colors.surface,
48012
+ borderRadius: theme2.radii[2],
48013
+ border: `1px solid ${theme2.colors.border}`,
48014
+ overflow: "hidden"
48015
+ },
48016
+ children: [
48017
+ /* @__PURE__ */ jsxs(
48018
+ "div",
48019
+ {
48020
+ onClick: onToggle,
48021
+ style: {
48022
+ padding: "16px",
48023
+ cursor: "pointer",
48024
+ display: "flex",
48025
+ flexDirection: "column",
48026
+ gap: "12px",
48027
+ transition: "background 0.2s ease"
48028
+ },
48029
+ onMouseEnter: (e) => {
48030
+ e.currentTarget.style.background = theme2.colors.backgroundSecondary;
48031
+ },
48032
+ onMouseLeave: (e) => {
48033
+ e.currentTarget.style.background = "transparent";
48034
+ },
48035
+ children: [
48036
+ /* @__PURE__ */ jsxs(
48037
+ "div",
48038
+ {
48039
+ style: {
48040
+ display: "flex",
48041
+ alignItems: "center",
48042
+ justifyContent: "space-between",
48043
+ gap: "12px"
48044
+ },
48045
+ children: [
48046
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: "8px" }, children: [
48047
+ isLoading ? /* @__PURE__ */ jsx(
48048
+ LoaderCircle,
48049
+ {
48050
+ size: 16,
48051
+ color: theme2.colors.primary,
48052
+ style: { animation: "spin 1s linear infinite" }
48053
+ }
48054
+ ) : isExpanded ? /* @__PURE__ */ jsx(ChevronDown, { size: 16, color: theme2.colors.textSecondary }) : /* @__PURE__ */ jsx(ChevronRight, { size: 16, color: theme2.colors.textSecondary }),
48055
+ /* @__PURE__ */ jsx(
48056
+ "h3",
48057
+ {
48058
+ style: {
48059
+ margin: 0,
48060
+ fontSize: theme2.fontSizes[3],
48061
+ fontWeight: theme2.fontWeights.semibold,
48062
+ color: theme2.colors.text
48063
+ },
48064
+ children: milestone.title
48065
+ }
48066
+ )
48067
+ ] }),
48068
+ /* @__PURE__ */ jsxs(
48069
+ "span",
48070
+ {
48071
+ style: {
48072
+ fontSize: theme2.fontSizes[1],
48073
+ color: theme2.colors.textSecondary,
48074
+ background: theme2.colors.backgroundSecondary,
48075
+ padding: "4px 10px",
48076
+ borderRadius: theme2.radii[1],
48077
+ fontWeight: theme2.fontWeights.medium
48078
+ },
48079
+ children: [
48080
+ totalTasks,
48081
+ " task",
48082
+ totalTasks !== 1 ? "s" : ""
48083
+ ]
48084
+ }
48085
+ )
48086
+ ]
48087
+ }
48088
+ ),
48089
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: "12px" }, children: [
48090
+ /* @__PURE__ */ jsx(
48091
+ "div",
48092
+ {
48093
+ style: {
48094
+ flex: 1,
48095
+ height: "6px",
48096
+ background: theme2.colors.backgroundSecondary,
48097
+ borderRadius: theme2.radii[1],
48098
+ overflow: "hidden"
48099
+ },
48100
+ children: /* @__PURE__ */ jsx(
48101
+ "div",
48102
+ {
48103
+ style: {
48104
+ height: "100%",
48105
+ width: showProgress ? `${progress}%` : "0%",
48106
+ background: progress === 100 ? theme2.colors.success : theme2.colors.primary,
48107
+ transition: "width 0.3s ease"
48108
+ }
48109
+ }
48110
+ )
48111
+ }
48112
+ ),
48113
+ /* @__PURE__ */ jsx(
48114
+ "span",
48115
+ {
48116
+ style: {
48117
+ fontSize: theme2.fontSizes[1],
48118
+ color: theme2.colors.textSecondary,
48119
+ minWidth: "45px",
48120
+ textAlign: "right"
48121
+ },
48122
+ children: showProgress ? `${progress}%` : "..."
48123
+ }
48124
+ )
48125
+ ] }),
48126
+ !isExpanded && milestone.description && /* @__PURE__ */ jsx(
48127
+ "p",
48128
+ {
48129
+ style: {
48130
+ margin: 0,
48131
+ fontSize: theme2.fontSizes[1],
48132
+ color: theme2.colors.textSecondary,
48133
+ overflow: "hidden",
48134
+ textOverflow: "ellipsis",
48135
+ display: "-webkit-box",
48136
+ WebkitLineClamp: 2,
48137
+ WebkitBoxOrient: "vertical",
48138
+ lineHeight: "1.4"
48139
+ },
48140
+ children: milestone.description
48141
+ }
48142
+ )
48143
+ ]
48144
+ }
48145
+ ),
48146
+ isExpanded && /* @__PURE__ */ jsxs(
48147
+ "div",
48148
+ {
48149
+ style: {
48150
+ borderTop: `1px solid ${theme2.colors.border}`,
48151
+ padding: "16px",
48152
+ display: "flex",
48153
+ flexDirection: "column",
48154
+ gap: "12px"
48155
+ },
48156
+ children: [
48157
+ milestone.description && /* @__PURE__ */ jsx(
48158
+ "p",
48159
+ {
48160
+ style: {
48161
+ margin: 0,
48162
+ fontSize: theme2.fontSizes[1],
48163
+ color: theme2.colors.textSecondary,
48164
+ lineHeight: "1.5"
48165
+ },
48166
+ children: milestone.description
48167
+ }
48168
+ ),
48169
+ tasks.length > 0 && /* @__PURE__ */ jsxs(
48170
+ "div",
48171
+ {
48172
+ style: {
48173
+ display: "flex",
48174
+ gap: "12px",
48175
+ flexWrap: "wrap",
48176
+ fontSize: theme2.fontSizes[1]
48177
+ },
48178
+ children: [
48179
+ /* @__PURE__ */ jsxs("span", { style: { color: theme2.colors.success }, children: [
48180
+ doneTasks,
48181
+ " done"
48182
+ ] }),
48183
+ /* @__PURE__ */ jsxs("span", { style: { color: theme2.colors.textSecondary }, children: [
48184
+ totalTasks - doneTasks,
48185
+ " remaining"
48186
+ ] })
48187
+ ]
48188
+ }
48189
+ ),
48190
+ isLoading ? /* @__PURE__ */ jsxs(
48191
+ "div",
48192
+ {
48193
+ style: {
48194
+ display: "flex",
48195
+ alignItems: "center",
48196
+ justifyContent: "center",
48197
+ padding: "24px",
48198
+ color: theme2.colors.textSecondary,
48199
+ gap: "8px"
48200
+ },
48201
+ children: [
48202
+ /* @__PURE__ */ jsx(
48203
+ LoaderCircle,
48204
+ {
48205
+ size: 16,
48206
+ style: { animation: "spin 1s linear infinite" }
48207
+ }
48208
+ ),
48209
+ "Loading tasks..."
48210
+ ]
48211
+ }
48212
+ ) : tasks.length > 0 ? /* @__PURE__ */ jsx(
48213
+ "div",
48214
+ {
48215
+ style: {
48216
+ display: "flex",
48217
+ flexDirection: "column",
48218
+ gap: "8px",
48219
+ maxHeight: "300px",
48220
+ overflowY: "auto",
48221
+ paddingRight: "4px"
48222
+ },
48223
+ children: tasks.map((task) => {
48224
+ var _a;
48225
+ return /* @__PURE__ */ jsxs(
48226
+ "div",
48227
+ {
48228
+ onClick: (e) => {
48229
+ e.stopPropagation();
48230
+ onTaskClick == null ? void 0 : onTaskClick(task);
48231
+ },
48232
+ style: {
48233
+ background: theme2.colors.background,
48234
+ borderRadius: theme2.radii[1],
48235
+ padding: "10px 12px",
48236
+ border: `1px solid ${theme2.colors.border}`,
48237
+ borderLeft: `3px solid ${getPriorityColor(task.priority)}`,
48238
+ cursor: onTaskClick ? "pointer" : "default",
48239
+ transition: "all 0.2s ease",
48240
+ display: "flex",
48241
+ alignItems: "center",
48242
+ justifyContent: "space-between",
48243
+ gap: "12px"
48244
+ },
48245
+ onMouseEnter: (e) => {
48246
+ if (onTaskClick) {
48247
+ e.currentTarget.style.background = theme2.colors.backgroundSecondary;
48248
+ }
48249
+ },
48250
+ onMouseLeave: (e) => {
48251
+ e.currentTarget.style.background = theme2.colors.background;
48252
+ },
48253
+ children: [
48254
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: "8px", flex: 1, minWidth: 0 }, children: [
48255
+ /* @__PURE__ */ jsxs(
48256
+ "span",
48257
+ {
48258
+ style: {
48259
+ fontFamily: theme2.fonts.monospace,
48260
+ fontSize: theme2.fontSizes[0],
48261
+ color: theme2.colors.textMuted,
48262
+ flexShrink: 0
48263
+ },
48264
+ children: [
48265
+ "#",
48266
+ task.id
48267
+ ]
48268
+ }
48269
+ ),
48270
+ /* @__PURE__ */ jsx(
48271
+ "span",
48272
+ {
48273
+ style: {
48274
+ fontSize: theme2.fontSizes[1],
48275
+ color: theme2.colors.text,
48276
+ overflow: "hidden",
48277
+ textOverflow: "ellipsis",
48278
+ whiteSpace: "nowrap"
48279
+ },
48280
+ children: task.title
48281
+ }
48282
+ )
48283
+ ] }),
48284
+ /* @__PURE__ */ jsx(
48285
+ "span",
48286
+ {
48287
+ style: {
48288
+ fontSize: theme2.fontSizes[0],
48289
+ color: ((_a = task.status) == null ? void 0 : _a.toLowerCase().includes("done")) ? theme2.colors.success : theme2.colors.textSecondary,
48290
+ background: theme2.colors.backgroundSecondary,
48291
+ padding: "2px 8px",
48292
+ borderRadius: theme2.radii[1],
48293
+ flexShrink: 0
48294
+ },
48295
+ children: task.status
48296
+ }
48297
+ )
48298
+ ]
48299
+ },
48300
+ task.id
48301
+ );
48302
+ })
48303
+ }
48304
+ ) : totalTasks === 0 ? /* @__PURE__ */ jsx(
48305
+ "div",
48306
+ {
48307
+ style: {
48308
+ padding: "16px",
48309
+ textAlign: "center",
48310
+ color: theme2.colors.textMuted,
48311
+ fontSize: theme2.fontSizes[1]
48312
+ },
48313
+ children: "No tasks in this milestone"
48314
+ }
48315
+ ) : null
48316
+ ]
48317
+ }
48318
+ ),
48319
+ /* @__PURE__ */ jsx("style", { children: `
48320
+ @keyframes spin {
48321
+ to { transform: rotate(360deg); }
48322
+ }
48323
+ ` })
48324
+ ]
48325
+ }
48326
+ );
48327
+ };
48328
+ const MilestonePanelContent = ({
48329
+ context,
48330
+ actions,
48331
+ events
48332
+ }) => {
48333
+ const { theme: theme2 } = useTheme();
48334
+ const [isRefreshing, setIsRefreshing] = useState(false);
48335
+ const {
48336
+ milestones,
48337
+ isLoading,
48338
+ error,
48339
+ isBacklogProject,
48340
+ toggleMilestone,
48341
+ refreshData
48342
+ } = useMilestoneData({
48343
+ context,
48344
+ actions
48345
+ });
48346
+ const handleTaskClick = (task) => {
48347
+ if (events) {
48348
+ events.emit({
48349
+ type: "task:selected",
48350
+ source: "milestone-panel",
48351
+ timestamp: Date.now(),
48352
+ payload: { taskId: task.id, task }
48353
+ });
48354
+ }
48355
+ };
48356
+ const handleRefresh = async () => {
48357
+ setIsRefreshing(true);
48358
+ try {
48359
+ await refreshData();
48360
+ } finally {
48361
+ setIsRefreshing(false);
48362
+ }
48363
+ };
48364
+ const totalMilestones = milestones.length;
48365
+ const totalTasks = milestones.reduce((sum, m) => sum + m.milestone.tasks.length, 0);
48366
+ return /* @__PURE__ */ jsxs(
48367
+ "div",
48368
+ {
48369
+ style: {
48370
+ padding: "clamp(12px, 3vw, 20px)",
48371
+ fontFamily: theme2.fonts.body,
48372
+ height: "100%",
48373
+ boxSizing: "border-box",
48374
+ display: "flex",
48375
+ flexDirection: "column",
48376
+ gap: "16px",
48377
+ overflow: "hidden",
48378
+ backgroundColor: theme2.colors.background,
48379
+ color: theme2.colors.text
48380
+ },
48381
+ children: [
48382
+ /* @__PURE__ */ jsxs(
48383
+ "div",
48384
+ {
48385
+ style: {
48386
+ flexShrink: 0,
48387
+ display: "flex",
48388
+ alignItems: "center",
48389
+ justifyContent: "space-between",
48390
+ gap: "12px",
48391
+ flexWrap: "wrap"
48392
+ },
48393
+ children: [
48394
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: "12px" }, children: [
48395
+ /* @__PURE__ */ jsx(Target, { size: 24, color: theme2.colors.primary }),
48396
+ /* @__PURE__ */ jsx(
48397
+ "h2",
48398
+ {
48399
+ style: {
48400
+ margin: 0,
48401
+ fontSize: theme2.fontSizes[4],
48402
+ color: theme2.colors.text
48403
+ },
48404
+ children: "Milestones"
48405
+ }
48406
+ )
48407
+ ] }),
48408
+ isBacklogProject && /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: "12px" }, children: [
48409
+ /* @__PURE__ */ jsxs(
48410
+ "span",
48411
+ {
48412
+ style: {
48413
+ fontSize: theme2.fontSizes[1],
48414
+ color: theme2.colors.textSecondary,
48415
+ background: theme2.colors.backgroundSecondary,
48416
+ padding: "4px 10px",
48417
+ borderRadius: theme2.radii[1]
48418
+ },
48419
+ children: [
48420
+ totalMilestones,
48421
+ " milestone",
48422
+ totalMilestones !== 1 ? "s" : "",
48423
+ " · ",
48424
+ totalTasks,
48425
+ " task",
48426
+ totalTasks !== 1 ? "s" : ""
48427
+ ]
48428
+ }
48429
+ ),
48430
+ /* @__PURE__ */ jsx(
48431
+ "button",
48432
+ {
48433
+ onClick: handleRefresh,
48434
+ disabled: isRefreshing || isLoading,
48435
+ style: {
48436
+ background: theme2.colors.backgroundSecondary,
48437
+ border: `1px solid ${theme2.colors.border}`,
48438
+ borderRadius: theme2.radii[1],
48439
+ padding: "6px",
48440
+ cursor: isRefreshing ? "wait" : "pointer",
48441
+ display: "flex",
48442
+ alignItems: "center",
48443
+ justifyContent: "center",
48444
+ transition: "all 0.2s ease"
48445
+ },
48446
+ title: "Refresh milestones",
48447
+ children: /* @__PURE__ */ jsx(
48448
+ RefreshCw,
48449
+ {
48450
+ size: 16,
48451
+ color: theme2.colors.textSecondary,
48452
+ style: {
48453
+ animation: isRefreshing ? "spin 1s linear infinite" : "none"
48454
+ }
48455
+ }
48456
+ )
48457
+ }
48458
+ )
48459
+ ] })
48460
+ ]
48461
+ }
48462
+ ),
48463
+ error && /* @__PURE__ */ jsxs(
48464
+ "div",
48465
+ {
48466
+ style: {
48467
+ flexShrink: 0,
48468
+ padding: "12px",
48469
+ background: `${theme2.colors.error}20`,
48470
+ border: `1px solid ${theme2.colors.error}`,
48471
+ borderRadius: theme2.radii[2],
48472
+ display: "flex",
48473
+ alignItems: "center",
48474
+ gap: "8px",
48475
+ color: theme2.colors.error,
48476
+ fontSize: theme2.fontSizes[1]
48477
+ },
48478
+ children: [
48479
+ /* @__PURE__ */ jsx(CircleAlert, { size: 16 }),
48480
+ /* @__PURE__ */ jsx("span", { children: error })
48481
+ ]
48482
+ }
48483
+ ),
48484
+ isLoading ? /* @__PURE__ */ jsx(
48485
+ "div",
48486
+ {
48487
+ style: {
48488
+ flex: 1,
48489
+ display: "flex",
48490
+ alignItems: "center",
48491
+ justifyContent: "center",
48492
+ color: theme2.colors.textSecondary
48493
+ },
48494
+ children: "Loading milestones..."
48495
+ }
48496
+ ) : !isBacklogProject ? /* @__PURE__ */ jsxs(
48497
+ "div",
48498
+ {
48499
+ style: {
48500
+ flex: 1,
48501
+ display: "flex",
48502
+ flexDirection: "column",
48503
+ alignItems: "center",
48504
+ justifyContent: "center",
48505
+ gap: "16px",
48506
+ color: theme2.colors.textSecondary
48507
+ },
48508
+ children: [
48509
+ /* @__PURE__ */ jsx(Target, { size: 48, color: theme2.colors.border }),
48510
+ /* @__PURE__ */ jsxs("div", { style: { textAlign: "center" }, children: [
48511
+ /* @__PURE__ */ jsx("p", { style: { margin: 0, fontSize: theme2.fontSizes[2] }, children: "No Backlog.md project found" }),
48512
+ /* @__PURE__ */ jsx("p", { style: { margin: "8px 0 0 0", fontSize: theme2.fontSizes[1] }, children: "Initialize a project from the Kanban Board panel" })
48513
+ ] })
48514
+ ]
48515
+ }
48516
+ ) : milestones.length === 0 ? /* @__PURE__ */ jsxs(
48517
+ "div",
48518
+ {
48519
+ style: {
48520
+ flex: 1,
48521
+ display: "flex",
48522
+ flexDirection: "column",
48523
+ alignItems: "center",
48524
+ justifyContent: "center",
48525
+ gap: "16px",
48526
+ color: theme2.colors.textSecondary
48527
+ },
48528
+ children: [
48529
+ /* @__PURE__ */ jsx(Target, { size: 48, color: theme2.colors.border }),
48530
+ /* @__PURE__ */ jsxs("div", { style: { textAlign: "center" }, children: [
48531
+ /* @__PURE__ */ jsx("p", { style: { margin: 0, fontSize: theme2.fontSizes[2] }, children: "No milestones yet" }),
48532
+ /* @__PURE__ */ jsx("p", { style: { margin: "8px 0 0 0", fontSize: theme2.fontSizes[1] }, children: "Create milestones to organize your tasks into releases or sprints" })
48533
+ ] })
48534
+ ]
48535
+ }
48536
+ ) : /* @__PURE__ */ jsx(
48537
+ "div",
48538
+ {
48539
+ style: {
48540
+ flex: 1,
48541
+ overflowY: "auto",
48542
+ overflowX: "hidden",
48543
+ display: "flex",
48544
+ flexDirection: "column",
48545
+ gap: "12px",
48546
+ paddingRight: "4px",
48547
+ marginRight: "-4px"
48548
+ },
48549
+ children: milestones.map((milestoneState) => /* @__PURE__ */ jsx(
48550
+ MilestoneCard,
48551
+ {
48552
+ milestoneState,
48553
+ onToggle: () => toggleMilestone(milestoneState.milestone.id),
48554
+ onTaskClick: handleTaskClick
48555
+ },
48556
+ milestoneState.milestone.id
48557
+ ))
48558
+ }
48559
+ ),
48560
+ /* @__PURE__ */ jsx("style", { children: `
48561
+ @keyframes spin {
48562
+ to { transform: rotate(360deg); }
48563
+ }
48564
+ ` })
48565
+ ]
48566
+ }
48567
+ );
48568
+ };
48569
+ const MilestonePanel = (props) => {
48570
+ return /* @__PURE__ */ jsx(ThemeProvider, { children: /* @__PURE__ */ jsx(MilestonePanelContent, { ...props }) });
48571
+ };
46942
48572
  const moveTaskTool = {
46943
48573
  name: "move_task",
46944
48574
  description: "Moves a task to a different status column on the kanban board",
@@ -47111,6 +48741,28 @@ const panels = [
47111
48741
  onUnmount: async (_context) => {
47112
48742
  console.log("Task Detail Panel unmounting");
47113
48743
  }
48744
+ },
48745
+ {
48746
+ metadata: {
48747
+ id: "principal-ade.milestone-panel",
48748
+ name: "Milestones",
48749
+ icon: "🎯",
48750
+ version: "0.1.0",
48751
+ author: "Principal ADE",
48752
+ description: "View and manage Backlog.md milestones with progress tracking",
48753
+ slices: ["fileTree"]
48754
+ },
48755
+ component: MilestonePanel,
48756
+ onMount: async (context) => {
48757
+ var _a;
48758
+ console.log(
48759
+ "Milestone Panel mounted",
48760
+ (_a = context.currentScope.repository) == null ? void 0 : _a.path
48761
+ );
48762
+ },
48763
+ onUnmount: async (_context) => {
48764
+ console.log("Milestone Panel unmounting");
48765
+ }
47114
48766
  }
47115
48767
  ];
47116
48768
  const onPackageLoad = async () => {