@orchestra-mcp/tasks 0.2.0

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/dist/index.js ADDED
@@ -0,0 +1,4946 @@
1
+ import {
2
+ mcpCall
3
+ } from "./chunk-ESY4NTC7.js";
4
+
5
+ // src/BacklogView/BacklogView.tsx
6
+ import { jsx, jsxs } from "react/jsx-runtime";
7
+ var TYPE_ICONS = {
8
+ task: "\u2611",
9
+ bug: "\u{1F41B}",
10
+ story: "\u{1F4D6}",
11
+ epic: "\u26A1"
12
+ };
13
+ var getInitials = (name) => name.split(" ").map((w) => w[0]).join("").toUpperCase().slice(0, 2);
14
+ var BacklogView = ({
15
+ columns,
16
+ onCardClick,
17
+ compact = false
18
+ }) => {
19
+ return /* @__PURE__ */ jsx(
20
+ "div",
21
+ {
22
+ className: `backlog ${compact ? "backlog--compact" : ""}`,
23
+ "data-testid": "backlog-view",
24
+ children: columns.map((col) => /* @__PURE__ */ jsxs("div", { className: "backlog__column", "data-testid": `column-${col.id}`, children: [
25
+ /* @__PURE__ */ jsxs("div", { className: "backlog__column-header", children: [
26
+ /* @__PURE__ */ jsx(
27
+ "span",
28
+ {
29
+ className: "backlog__column-dot",
30
+ style: { backgroundColor: col.color || "var(--color-accent)" }
31
+ }
32
+ ),
33
+ /* @__PURE__ */ jsx("span", { className: "backlog__column-title", children: col.title }),
34
+ /* @__PURE__ */ jsx("span", { className: "backlog__column-count", "data-testid": `count-${col.id}`, children: col.cards.length })
35
+ ] }),
36
+ /* @__PURE__ */ jsx("div", { className: "backlog__cards", children: col.cards.map((card) => /* @__PURE__ */ jsxs(
37
+ "button",
38
+ {
39
+ type: "button",
40
+ className: "backlog__card",
41
+ "data-testid": `card-${card.id}`,
42
+ onClick: () => onCardClick?.(card),
43
+ children: [
44
+ /* @__PURE__ */ jsxs("div", { className: "backlog__card-top", children: [
45
+ card.type && /* @__PURE__ */ jsx("span", { className: "backlog__card-type", "data-testid": `type-${card.id}`, children: TYPE_ICONS[card.type] || card.type }),
46
+ card.priority && /* @__PURE__ */ jsx(
47
+ "span",
48
+ {
49
+ className: `backlog__card-priority backlog__card-priority--${card.priority}`,
50
+ "data-testid": `priority-${card.id}`
51
+ }
52
+ )
53
+ ] }),
54
+ /* @__PURE__ */ jsx("span", { className: "backlog__card-title", children: card.title }),
55
+ card.description && /* @__PURE__ */ jsx("span", { className: "backlog__card-desc", children: card.description }),
56
+ /* @__PURE__ */ jsxs("div", { className: "backlog__card-bottom", children: [
57
+ card.tags && card.tags.length > 0 && /* @__PURE__ */ jsx("div", { className: "backlog__card-tags", "data-testid": `tags-${card.id}`, children: card.tags.map((tag) => /* @__PURE__ */ jsx("span", { className: "backlog__card-tag", children: tag }, tag)) }),
58
+ card.assignee && /* @__PURE__ */ jsx("span", { className: "backlog__card-assignee", "data-testid": `assignee-${card.id}`, children: getInitials(card.assignee) })
59
+ ] })
60
+ ]
61
+ },
62
+ card.id
63
+ )) })
64
+ ] }, col.id))
65
+ }
66
+ );
67
+ };
68
+
69
+ // src/StatusGrid/StatusGrid.tsx
70
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
71
+ var StatusGrid = ({ items, onStatusClick, activeStatus }) => {
72
+ if (items.length === 0) return null;
73
+ return /* @__PURE__ */ jsx2("div", { className: "status-grid", "data-testid": "status-grid", children: items.map((item) => /* @__PURE__ */ jsxs2(
74
+ "button",
75
+ {
76
+ type: "button",
77
+ className: [
78
+ "status-grid__cell",
79
+ activeStatus === item.status ? "status-grid__cell--active" : ""
80
+ ].filter(Boolean).join(" "),
81
+ "data-testid": `status-${item.status}`,
82
+ onClick: () => onStatusClick?.(item.status),
83
+ children: [
84
+ /* @__PURE__ */ jsx2(
85
+ "span",
86
+ {
87
+ className: "status-grid__dot",
88
+ style: { backgroundColor: item.color || "var(--color-accent)" }
89
+ }
90
+ ),
91
+ /* @__PURE__ */ jsx2("span", { className: "status-grid__label", children: item.label }),
92
+ /* @__PURE__ */ jsx2("span", { className: "status-grid__count", children: item.count })
93
+ ]
94
+ },
95
+ item.status
96
+ )) });
97
+ };
98
+
99
+ // src/TaskList/TaskList.tsx
100
+ import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
101
+ var PRIORITY_COLORS = {
102
+ low: "#22c55e",
103
+ medium: "#eab308",
104
+ high: "#f97316",
105
+ critical: "#ef4444"
106
+ };
107
+ var TYPE_ICONS2 = {
108
+ task: "\u2611",
109
+ bug: "\u{1F41B}",
110
+ hotfix: "\u{1F525}",
111
+ story: "\u{1F4D6}",
112
+ epic: "\u26A1"
113
+ };
114
+ var TaskList = ({
115
+ items,
116
+ onTaskClick,
117
+ statusConfig,
118
+ emptyMessage = "No tasks"
119
+ }) => {
120
+ if (items.length === 0) {
121
+ return /* @__PURE__ */ jsx3("div", { className: "task-list__empty", "data-testid": "task-list-empty", children: emptyMessage });
122
+ }
123
+ return /* @__PURE__ */ jsx3("div", { className: "task-list", "data-testid": "task-list", children: items.map((item) => {
124
+ const cfg = statusConfig?.[item.status];
125
+ return /* @__PURE__ */ jsxs3(
126
+ "button",
127
+ {
128
+ type: "button",
129
+ className: "task-list__row",
130
+ "data-testid": `task-${item.id}`,
131
+ onClick: () => onTaskClick?.(item),
132
+ children: [
133
+ item.type && /* @__PURE__ */ jsx3("span", { className: "task-list__type", children: TYPE_ICONS2[item.type] || item.type }),
134
+ item.priority && /* @__PURE__ */ jsx3(
135
+ "span",
136
+ {
137
+ className: "task-list__priority",
138
+ style: { backgroundColor: PRIORITY_COLORS[item.priority] },
139
+ title: item.priority
140
+ }
141
+ ),
142
+ /* @__PURE__ */ jsx3("span", { className: "task-list__id", children: item.id }),
143
+ /* @__PURE__ */ jsx3("span", { className: "task-list__title", children: item.title }),
144
+ cfg && /* @__PURE__ */ jsx3(
145
+ "span",
146
+ {
147
+ className: "task-list__badge",
148
+ style: { backgroundColor: cfg.color + "22", color: cfg.color, borderColor: cfg.color + "44" },
149
+ children: cfg.label
150
+ }
151
+ )
152
+ ]
153
+ },
154
+ item.id
155
+ );
156
+ }) });
157
+ };
158
+
159
+ // src/DetailPanel/DetailPanel.tsx
160
+ import { useEffect, useRef } from "react";
161
+ import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
162
+ var DetailPanel = ({
163
+ isOpen,
164
+ onClose,
165
+ title,
166
+ subtitle,
167
+ fields,
168
+ children
169
+ }) => {
170
+ const panelRef = useRef(null);
171
+ useEffect(() => {
172
+ if (!isOpen) return;
173
+ const handleEsc = (e) => {
174
+ if (e.key === "Escape") onClose();
175
+ };
176
+ document.addEventListener("keydown", handleEsc);
177
+ return () => document.removeEventListener("keydown", handleEsc);
178
+ }, [isOpen, onClose]);
179
+ if (!isOpen) return null;
180
+ return /* @__PURE__ */ jsx4("div", { className: "detail-overlay", onClick: onClose, role: "presentation", children: /* @__PURE__ */ jsxs4(
181
+ "div",
182
+ {
183
+ ref: panelRef,
184
+ className: "detail-panel",
185
+ role: "dialog",
186
+ "aria-modal": "true",
187
+ "aria-label": title,
188
+ onClick: (e) => e.stopPropagation(),
189
+ children: [
190
+ /* @__PURE__ */ jsxs4("div", { className: "detail-panel__header", children: [
191
+ /* @__PURE__ */ jsxs4("div", { className: "detail-panel__titles", children: [
192
+ subtitle && /* @__PURE__ */ jsx4("span", { className: "detail-panel__subtitle", children: subtitle }),
193
+ /* @__PURE__ */ jsx4("h2", { className: "detail-panel__title", children: title })
194
+ ] }),
195
+ /* @__PURE__ */ jsx4(
196
+ "button",
197
+ {
198
+ type: "button",
199
+ className: "detail-panel__close",
200
+ onClick: onClose,
201
+ "aria-label": "Close",
202
+ children: /* @__PURE__ */ jsx4("svg", { width: "18", height: "18", viewBox: "0 0 20 20", fill: "none", children: /* @__PURE__ */ jsx4("path", { d: "M15 5L5 15M5 5L15 15", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round" }) })
203
+ }
204
+ )
205
+ ] }),
206
+ fields && fields.length > 0 && /* @__PURE__ */ jsx4("div", { className: "detail-panel__fields", children: fields.map((f, i) => /* @__PURE__ */ jsxs4("div", { className: "detail-panel__field", children: [
207
+ /* @__PURE__ */ jsx4("span", { className: "detail-panel__field-label", children: f.label }),
208
+ /* @__PURE__ */ jsx4(
209
+ "span",
210
+ {
211
+ className: "detail-panel__field-value",
212
+ style: f.color ? { color: f.color } : void 0,
213
+ children: f.value
214
+ }
215
+ )
216
+ ] }, i)) }),
217
+ /* @__PURE__ */ jsx4("div", { className: "detail-panel__body", children })
218
+ ]
219
+ }
220
+ ) });
221
+ };
222
+
223
+ // src/Dashboard/DashboardGrid.tsx
224
+ import { useRef as useRef2, useCallback, useState, useMemo, useEffect as useEffect2 } from "react";
225
+ import { Fragment, jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
226
+ var HOLD_DELAY = 200;
227
+ function calculateHoverIndex(widgets, draggedId, mouseX, mouseY, gridElement) {
228
+ const items = Array.from(gridElement.querySelectorAll(".dashboard-grid__item:not(.dashboard-grid__item--dragging)"));
229
+ if (items.length === 0) return 0;
230
+ let closestIndex = 0;
231
+ let closestDistance = Infinity;
232
+ items.forEach((item, index) => {
233
+ const rect = item.getBoundingClientRect();
234
+ const centerX2 = rect.left + rect.width / 2;
235
+ const centerY2 = rect.top + rect.height / 2;
236
+ const distance = Math.sqrt(
237
+ Math.pow(mouseX - centerX2, 2) + Math.pow(mouseY - centerY2, 2)
238
+ );
239
+ if (distance < closestDistance) {
240
+ closestDistance = distance;
241
+ closestIndex = index;
242
+ }
243
+ });
244
+ const closestRect = items[closestIndex].getBoundingClientRect();
245
+ const centerX = closestRect.left + closestRect.width / 2;
246
+ const centerY = closestRect.top + closestRect.height / 2;
247
+ if (mouseX > centerX || mouseY > centerY) {
248
+ return Math.min(closestIndex + 1, items.length);
249
+ }
250
+ return closestIndex;
251
+ }
252
+ var DashboardGrid = ({
253
+ widgets,
254
+ columns,
255
+ onMove,
256
+ onMoveToIndex,
257
+ onWidgetContextMenu,
258
+ renderWidget,
259
+ className
260
+ }) => {
261
+ const gridRef = useRef2(null);
262
+ const holdTimerRef = useRef2(null);
263
+ const dragHandlersRef = useRef2(null);
264
+ const [dragState, setDragState] = useState(null);
265
+ useEffect2(() => {
266
+ return () => {
267
+ if (holdTimerRef.current) clearTimeout(holdTimerRef.current);
268
+ if (dragHandlersRef.current) dragHandlersRef.current.cleanup();
269
+ };
270
+ }, []);
271
+ const displayWidgets = useMemo(() => {
272
+ if (!dragState) return widgets;
273
+ const draggedWidget = widgets.find((w) => w.id === dragState.widgetId);
274
+ if (!draggedWidget) return widgets;
275
+ const otherWidgets = widgets.filter((w) => w.id !== dragState.widgetId);
276
+ const reordered = [
277
+ ...otherWidgets.slice(0, dragState.hoverIndex),
278
+ draggedWidget,
279
+ ...otherWidgets.slice(dragState.hoverIndex)
280
+ ];
281
+ return reordered;
282
+ }, [widgets, dragState]);
283
+ const handleDragStart = useCallback(
284
+ (widgetId, colSpan, locked) => (e) => {
285
+ if (locked || !onMoveToIndex) return;
286
+ e.preventDefault();
287
+ e.stopPropagation();
288
+ const item = e.currentTarget.closest(".dashboard-grid__item");
289
+ if (!item) return;
290
+ const rect = item.getBoundingClientRect();
291
+ const offsetX = e.clientX - rect.left;
292
+ const offsetY = e.clientY - rect.top;
293
+ const startX = e.clientX;
294
+ const startY = e.clientY;
295
+ if (holdTimerRef.current) {
296
+ clearTimeout(holdTimerRef.current);
297
+ }
298
+ if (dragHandlersRef.current) {
299
+ dragHandlersRef.current.cleanup();
300
+ }
301
+ let isHolding = true;
302
+ let isDragging = false;
303
+ const handleMove = (moveEvent) => {
304
+ if (!isHolding && !isDragging) return;
305
+ if (isHolding) {
306
+ const dx = Math.abs(moveEvent.clientX - startX);
307
+ const dy = Math.abs(moveEvent.clientY - startY);
308
+ if (dx > 5 || dy > 5) {
309
+ cleanup();
310
+ return;
311
+ }
312
+ }
313
+ if (!isDragging) return;
314
+ moveEvent.preventDefault();
315
+ const grid = gridRef.current;
316
+ if (!grid) return;
317
+ const hoverIndex = calculateHoverIndex(
318
+ widgets,
319
+ widgetId,
320
+ moveEvent.clientX,
321
+ moveEvent.clientY,
322
+ grid
323
+ );
324
+ setDragState((prev) => {
325
+ if (!prev) return null;
326
+ return {
327
+ ...prev,
328
+ currentX: moveEvent.clientX,
329
+ currentY: moveEvent.clientY,
330
+ hoverIndex
331
+ };
332
+ });
333
+ };
334
+ const handleUp = (upEvent) => {
335
+ upEvent.preventDefault();
336
+ upEvent.stopPropagation();
337
+ if (isDragging) {
338
+ const grid = gridRef.current;
339
+ if (grid) {
340
+ const finalIndex = calculateHoverIndex(
341
+ widgets,
342
+ widgetId,
343
+ upEvent.clientX,
344
+ upEvent.clientY,
345
+ grid
346
+ );
347
+ if (onMoveToIndex) {
348
+ onMoveToIndex(widgetId, finalIndex);
349
+ }
350
+ }
351
+ }
352
+ cleanup();
353
+ };
354
+ const cleanup = () => {
355
+ isHolding = false;
356
+ if (holdTimerRef.current) {
357
+ clearTimeout(holdTimerRef.current);
358
+ holdTimerRef.current = null;
359
+ }
360
+ document.removeEventListener("mousemove", handleMove);
361
+ document.removeEventListener("mouseup", handleUp);
362
+ if (isDragging) {
363
+ item.classList.remove("dashboard-grid__item--dragging");
364
+ isDragging = false;
365
+ setTimeout(() => {
366
+ setDragState(null);
367
+ }, 0);
368
+ }
369
+ dragHandlersRef.current = null;
370
+ };
371
+ dragHandlersRef.current = { cleanup };
372
+ holdTimerRef.current = setTimeout(() => {
373
+ if (!isHolding) return;
374
+ isHolding = false;
375
+ isDragging = true;
376
+ holdTimerRef.current = null;
377
+ item.classList.add("dashboard-grid__item--dragging");
378
+ const initialIndex = widgets.findIndex((w) => w.id === widgetId);
379
+ setDragState({
380
+ widgetId,
381
+ startX,
382
+ startY,
383
+ currentX: startX,
384
+ currentY: startY,
385
+ offsetX,
386
+ offsetY,
387
+ hoverIndex: initialIndex
388
+ });
389
+ }, HOLD_DELAY);
390
+ document.addEventListener("mousemove", handleMove);
391
+ document.addEventListener("mouseup", handleUp);
392
+ },
393
+ [onMoveToIndex, columns, widgets]
394
+ );
395
+ const draggingWidget = dragState ? widgets.find((w) => w.id === dragState.widgetId) : null;
396
+ return /* @__PURE__ */ jsxs5(Fragment, { children: [
397
+ /* @__PURE__ */ jsx5(
398
+ "div",
399
+ {
400
+ ref: gridRef,
401
+ className: `dashboard-grid ${className || ""}`,
402
+ "data-testid": "dashboard-grid",
403
+ children: displayWidgets.map((widget) => {
404
+ const clampedSpan = Math.min(widget.colSpan, columns);
405
+ const isDragging = dragState?.widgetId === widget.id;
406
+ return /* @__PURE__ */ jsxs5(
407
+ "div",
408
+ {
409
+ className: `dashboard-grid__item${widget.collapsed ? " dashboard-grid__item--collapsed" : ""}${isDragging ? " dashboard-grid__item--dragging" : ""}`,
410
+ "data-testid": `widget-${widget.id}`,
411
+ onContextMenu: onWidgetContextMenu ? (e) => onWidgetContextMenu(widget.id, e) : void 0,
412
+ style: {
413
+ gridColumn: `span ${clampedSpan}`
414
+ },
415
+ children: [
416
+ /* @__PURE__ */ jsx5(
417
+ "div",
418
+ {
419
+ className: "dashboard-grid__drag-handle",
420
+ onMouseDown: handleDragStart(widget.id, clampedSpan, widget.locked),
421
+ style: { cursor: widget.locked ? "default" : "grab" }
422
+ }
423
+ ),
424
+ renderWidget(widget)
425
+ ]
426
+ },
427
+ widget.id
428
+ );
429
+ })
430
+ }
431
+ ),
432
+ dragState && draggingWidget && /* @__PURE__ */ jsx5(
433
+ "div",
434
+ {
435
+ className: "dashboard-grid__ghost",
436
+ style: {
437
+ position: "fixed",
438
+ left: dragState.currentX - dragState.offsetX,
439
+ top: dragState.currentY - dragState.offsetY,
440
+ width: `${100 / columns * Math.min(draggingWidget.colSpan, columns)}%`,
441
+ maxWidth: "600px",
442
+ pointerEvents: "none",
443
+ zIndex: 9999
444
+ },
445
+ children: renderWidget(draggingWidget)
446
+ }
447
+ )
448
+ ] });
449
+ };
450
+
451
+ // src/Dashboard/ResizeHandle.tsx
452
+ import { useCallback as useCallback2, useRef as useRef3 } from "react";
453
+ import { jsx as jsx6 } from "react/jsx-runtime";
454
+ var ResizeHandle = ({
455
+ onResize,
456
+ minColSpan = 2,
457
+ minRowSpan = 1
458
+ }) => {
459
+ const startRef = useRef3({ x: 0, y: 0 });
460
+ const handlePointerDown = useCallback2(
461
+ (e) => {
462
+ e.preventDefault();
463
+ e.stopPropagation();
464
+ startRef.current = { x: e.clientX, y: e.clientY };
465
+ const target = e.currentTarget;
466
+ target.setPointerCapture(e.pointerId);
467
+ const handleUp = (upEvent) => {
468
+ target.releasePointerCapture(upEvent.pointerId);
469
+ target.removeEventListener("pointerup", handleUp);
470
+ const dx = upEvent.clientX - startRef.current.x;
471
+ const dy = upEvent.clientY - startRef.current.y;
472
+ const colDelta = Math.round(dx / 100);
473
+ const rowDelta = Math.round(dy / 80);
474
+ const newColSpan = Math.max(minColSpan, minColSpan + colDelta);
475
+ const newRowSpan = Math.max(minRowSpan, minRowSpan + rowDelta);
476
+ onResize(newColSpan, newRowSpan);
477
+ };
478
+ target.addEventListener("pointerup", handleUp);
479
+ },
480
+ [onResize, minColSpan, minRowSpan]
481
+ );
482
+ return /* @__PURE__ */ jsx6(
483
+ "div",
484
+ {
485
+ className: "resize-handle",
486
+ "data-testid": "resize-handle",
487
+ onPointerDown: handlePointerDown,
488
+ "aria-label": "Resize widget",
489
+ role: "separator"
490
+ }
491
+ );
492
+ };
493
+
494
+ // src/Dashboard/useGridDrag.ts
495
+ import { useCallback as useCallback3, useRef as useRef4, useState as useState2 } from "react";
496
+ var useGridDrag = ({
497
+ gridRef,
498
+ columns = 12,
499
+ onDrop
500
+ }) => {
501
+ const [isDragging, setIsDragging] = useState2(false);
502
+ const startPos = useRef4({ x: 0, y: 0 });
503
+ const snapToGrid = useCallback3(
504
+ (clientX, clientY) => {
505
+ const grid = gridRef.current;
506
+ if (!grid) return { col: 1, row: 1 };
507
+ const rect = grid.getBoundingClientRect();
508
+ const colWidth = rect.width / columns;
509
+ const rowHeight = 80;
510
+ const relX = clientX - rect.left;
511
+ const relY = clientY - rect.top;
512
+ const col = Math.max(1, Math.min(columns, Math.ceil(relX / colWidth)));
513
+ const row = Math.max(1, Math.ceil(relY / rowHeight));
514
+ return { col, row };
515
+ },
516
+ [gridRef, columns]
517
+ );
518
+ const handlePointerDown = useCallback3(
519
+ (e) => {
520
+ e.preventDefault();
521
+ setIsDragging(true);
522
+ startPos.current = { x: e.clientX, y: e.clientY };
523
+ const target = e.currentTarget;
524
+ target.setPointerCapture(e.pointerId);
525
+ const handleMove = (_moveEvent) => {
526
+ };
527
+ const handleUp = (upEvent) => {
528
+ setIsDragging(false);
529
+ target.releasePointerCapture(upEvent.pointerId);
530
+ target.removeEventListener("pointermove", handleMove);
531
+ target.removeEventListener("pointerup", handleUp);
532
+ const { col, row } = snapToGrid(upEvent.clientX, upEvent.clientY);
533
+ onDrop(col, row);
534
+ };
535
+ target.addEventListener("pointermove", handleMove);
536
+ target.addEventListener("pointerup", handleUp);
537
+ },
538
+ [snapToGrid, onDrop]
539
+ );
540
+ return { isDragging, handlePointerDown };
541
+ };
542
+
543
+ // src/Dashboard/WidgetContextMenu.tsx
544
+ import { useEffect as useEffect3, useRef as useRef5 } from "react";
545
+ import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
546
+ function getColSpanOptions(columns) {
547
+ if (columns <= 1) return [1];
548
+ const opts = [];
549
+ for (let i = 1; i <= columns; i++) {
550
+ if (columns % i === 0 || i === columns) opts.push(i);
551
+ }
552
+ if (!opts.includes(columns)) opts.push(columns);
553
+ return opts;
554
+ }
555
+ var WidgetContextMenu = ({
556
+ x,
557
+ y,
558
+ widgetId,
559
+ columns,
560
+ currentColSpan,
561
+ isLocked = false,
562
+ onColSpanChange,
563
+ onHide,
564
+ onLock,
565
+ onClose
566
+ }) => {
567
+ const menuRef = useRef5(null);
568
+ useEffect3(() => {
569
+ const handleClick = (e) => {
570
+ if (menuRef.current && !menuRef.current.contains(e.target)) {
571
+ onClose();
572
+ }
573
+ };
574
+ const handleEscape = (e) => {
575
+ if (e.key === "Escape") onClose();
576
+ };
577
+ document.addEventListener("mousedown", handleClick);
578
+ document.addEventListener("keydown", handleEscape);
579
+ return () => {
580
+ document.removeEventListener("mousedown", handleClick);
581
+ document.removeEventListener("keydown", handleEscape);
582
+ };
583
+ }, [onClose]);
584
+ useEffect3(() => {
585
+ if (!menuRef.current) return;
586
+ const rect = menuRef.current.getBoundingClientRect();
587
+ const maxX = window.innerWidth - rect.width - 8;
588
+ const maxY = window.innerHeight - rect.height - 8;
589
+ if (x > maxX) menuRef.current.style.left = `${maxX}px`;
590
+ if (y > maxY) menuRef.current.style.top = `${maxY}px`;
591
+ }, [x, y]);
592
+ const spanOptions = getColSpanOptions(columns);
593
+ return /* @__PURE__ */ jsxs6(
594
+ "div",
595
+ {
596
+ ref: menuRef,
597
+ className: "widget-ctx",
598
+ style: { left: x, top: y },
599
+ "data-testid": "widget-context-menu",
600
+ children: [
601
+ /* @__PURE__ */ jsxs6("div", { className: "widget-ctx__group", children: [
602
+ /* @__PURE__ */ jsx7("span", { className: "widget-ctx__label", children: "Width" }),
603
+ spanOptions.map((span) => /* @__PURE__ */ jsx7(
604
+ "button",
605
+ {
606
+ type: "button",
607
+ className: `widget-ctx__item${span === currentColSpan ? " widget-ctx__item--active" : ""}`,
608
+ onClick: () => {
609
+ onColSpanChange(widgetId, span);
610
+ onClose();
611
+ },
612
+ children: span === columns ? "Full width" : `${span} col${span > 1 ? "s" : ""}`
613
+ },
614
+ span
615
+ ))
616
+ ] }),
617
+ /* @__PURE__ */ jsx7("div", { className: "widget-ctx__divider" }),
618
+ /* @__PURE__ */ jsx7(
619
+ "button",
620
+ {
621
+ type: "button",
622
+ className: "widget-ctx__item",
623
+ onClick: () => {
624
+ onHide(widgetId);
625
+ onClose();
626
+ },
627
+ children: "Hide Widget"
628
+ }
629
+ ),
630
+ /* @__PURE__ */ jsx7(
631
+ "button",
632
+ {
633
+ type: "button",
634
+ className: "widget-ctx__item",
635
+ onClick: () => {
636
+ onLock(widgetId);
637
+ onClose();
638
+ },
639
+ children: isLocked ? "Unlock Widget" : "Lock Widget"
640
+ }
641
+ )
642
+ ]
643
+ }
644
+ );
645
+ };
646
+
647
+ // src/ProjectsSidebar/ProjectsSidebar.tsx
648
+ import { useState as useState4, useCallback as useCallback5, useMemo as useMemo2 } from "react";
649
+ import { BoxIcon as BoxIcon2 } from "@orchestra-mcp/icons";
650
+ import { EmptyState } from "@orchestra-mcp/ui";
651
+
652
+ // src/ProjectsSidebar/ProjectItem.tsx
653
+ import { useCallback as useCallback4, useEffect as useEffect4, useLayoutEffect, useRef as useRef6, useState as useState3 } from "react";
654
+ import { BoxIcon } from "@orchestra-mcp/icons";
655
+ import { Fragment as Fragment2, jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
656
+ var PROJECT_ICONS = [
657
+ "bx-folder",
658
+ "bx-code-alt",
659
+ "bx-bug",
660
+ "bx-rocket",
661
+ "bx-terminal",
662
+ "bx-git-branch",
663
+ "bx-wrench",
664
+ "bx-book-open",
665
+ "bx-data",
666
+ "bx-shield",
667
+ "bx-world",
668
+ "bx-star",
669
+ "bx-bolt",
670
+ "bx-layer",
671
+ "bx-cube"
672
+ ];
673
+ var PROJECT_COLORS = [
674
+ "#6366f1",
675
+ "#8b5cf6",
676
+ "#a855f7",
677
+ "#ec4899",
678
+ "#ef4444",
679
+ "#f59e0b",
680
+ "#22c55e",
681
+ "#14b8a6",
682
+ "#06b6d4",
683
+ "#3b82f6"
684
+ ];
685
+ var SWIPE_THRESHOLD = 70;
686
+ var HOLD_DELAY2 = 200;
687
+ var LONG_PRESS_DELAY = 500;
688
+ function ProjectItem({
689
+ slug,
690
+ name,
691
+ status,
692
+ taskCount,
693
+ completionPercent,
694
+ active = false,
695
+ pinned = false,
696
+ icon,
697
+ color,
698
+ selected = false,
699
+ selectionMode = false,
700
+ onSelect,
701
+ onDelete,
702
+ onPin,
703
+ onRename,
704
+ onIconChange,
705
+ onColorChange,
706
+ onUpdateKey,
707
+ onToggleSelect,
708
+ onIconClick
709
+ }) {
710
+ const [offsetX, setOffsetX] = useState3(0);
711
+ const swipingRef = useRef6(false);
712
+ const startX = useRef6(0);
713
+ const holdTimer = useRef6(null);
714
+ const longPressTimer = useRef6(null);
715
+ const longPressFired = useRef6(false);
716
+ const cleanup = useCallback4(() => {
717
+ if (holdTimer.current) {
718
+ clearTimeout(holdTimer.current);
719
+ holdTimer.current = null;
720
+ }
721
+ if (longPressTimer.current) {
722
+ clearTimeout(longPressTimer.current);
723
+ longPressTimer.current = null;
724
+ }
725
+ document.removeEventListener("pointermove", handleDocMove);
726
+ document.removeEventListener("pointerup", handleDocUp);
727
+ }, []);
728
+ const handleDocMove = useCallback4((e) => {
729
+ const dx = e.clientX - startX.current;
730
+ swipingRef.current = true;
731
+ if (longPressTimer.current && Math.abs(dx) > 8) {
732
+ clearTimeout(longPressTimer.current);
733
+ longPressTimer.current = null;
734
+ }
735
+ const max = SWIPE_THRESHOLD + 20;
736
+ const clamped2 = Math.max(-max, Math.min(max, dx));
737
+ if (clamped2 > 0 && !onPin) return;
738
+ if (clamped2 < 0 && !onDelete) return;
739
+ setOffsetX(clamped2);
740
+ }, [onPin, onDelete]);
741
+ const handleDocUp = useCallback4(() => {
742
+ cleanup();
743
+ setOffsetX((prev) => {
744
+ if (prev >= SWIPE_THRESHOLD && onPin) onPin(slug);
745
+ else if (prev <= -SWIPE_THRESHOLD && onDelete) setConfirmDelete(true);
746
+ return 0;
747
+ });
748
+ setTimeout(() => {
749
+ swipingRef.current = false;
750
+ }, 50);
751
+ }, [slug, onPin, onDelete, cleanup]);
752
+ const onPointerDown = useCallback4((e) => {
753
+ if (e.button !== 0) return;
754
+ startX.current = e.clientX;
755
+ longPressFired.current = false;
756
+ cleanup();
757
+ if (onToggleSelect) {
758
+ longPressTimer.current = setTimeout(() => {
759
+ longPressFired.current = true;
760
+ onToggleSelect(slug);
761
+ }, LONG_PRESS_DELAY);
762
+ }
763
+ holdTimer.current = setTimeout(() => {
764
+ document.addEventListener("pointermove", handleDocMove);
765
+ document.addEventListener("pointerup", handleDocUp, { once: true });
766
+ }, HOLD_DELAY2);
767
+ }, [cleanup, handleDocMove, handleDocUp, onToggleSelect, slug]);
768
+ const onPointerUp = useCallback4(() => {
769
+ if (holdTimer.current) {
770
+ clearTimeout(holdTimer.current);
771
+ holdTimer.current = null;
772
+ }
773
+ if (longPressTimer.current) {
774
+ clearTimeout(longPressTimer.current);
775
+ longPressTimer.current = null;
776
+ }
777
+ }, []);
778
+ const handleClick = useCallback4(() => {
779
+ if (swipingRef.current || longPressFired.current) return;
780
+ if (selectionMode && onToggleSelect) {
781
+ onToggleSelect(slug);
782
+ return;
783
+ }
784
+ onSelect(slug);
785
+ }, [slug, onSelect, selectionMode, onToggleSelect]);
786
+ const [menuPos, setMenuPos] = useState3(null);
787
+ const [hoveredSub, setHoveredSub] = useState3(null);
788
+ const menuRef = useRef6(null);
789
+ const openMenu = useCallback4((x, y) => {
790
+ setMenuPos({ x, y });
791
+ setHoveredSub(null);
792
+ }, []);
793
+ const closeMenu = useCallback4(() => {
794
+ setMenuPos(null);
795
+ setHoveredSub(null);
796
+ }, []);
797
+ const handleContextMenu = useCallback4((e) => {
798
+ e.preventDefault();
799
+ openMenu(e.clientX, e.clientY);
800
+ }, [openMenu]);
801
+ const clamped = useRef6(false);
802
+ useLayoutEffect(() => {
803
+ if (!menuPos) {
804
+ clamped.current = false;
805
+ return;
806
+ }
807
+ if (clamped.current || !menuRef.current) return;
808
+ clamped.current = true;
809
+ const rect = menuRef.current.getBoundingClientRect();
810
+ let { x, y } = menuPos;
811
+ if (y + rect.height > window.innerHeight - 8) {
812
+ y = Math.max(8, window.innerHeight - rect.height - 8);
813
+ }
814
+ if (x + rect.width > window.innerWidth - 8) {
815
+ x = Math.max(8, window.innerWidth - rect.width - 8);
816
+ }
817
+ if (x !== menuPos.x || y !== menuPos.y) {
818
+ setMenuPos({ x, y });
819
+ }
820
+ });
821
+ useEffect4(() => {
822
+ if (!menuPos) return;
823
+ const handler = (e) => {
824
+ if (menuRef.current && !menuRef.current.contains(e.target)) {
825
+ closeMenu();
826
+ }
827
+ };
828
+ document.addEventListener("mousedown", handler);
829
+ return () => document.removeEventListener("mousedown", handler);
830
+ }, [menuPos, closeMenu]);
831
+ const [confirmDelete, setConfirmDelete] = useState3(false);
832
+ const dialogRef = useRef6(null);
833
+ useEffect4(() => {
834
+ const el = dialogRef.current;
835
+ if (!el) return;
836
+ if (confirmDelete) el.showModal();
837
+ else if (el.open) el.close();
838
+ }, [confirmDelete]);
839
+ const handleConfirmYes = useCallback4((e) => {
840
+ e.stopPropagation();
841
+ setConfirmDelete(false);
842
+ onDelete?.(slug);
843
+ }, [slug, onDelete]);
844
+ const handleConfirmNo = useCallback4((e) => {
845
+ e.stopPropagation();
846
+ setConfirmDelete(false);
847
+ }, []);
848
+ const handleDialogBackdrop = useCallback4((e) => {
849
+ if (e.target === e.currentTarget) setConfirmDelete(false);
850
+ }, []);
851
+ const [renaming, setRenaming] = useState3(false);
852
+ const [renameValue, setRenameValue] = useState3(name);
853
+ const renameInputRef = useRef6(null);
854
+ useEffect4(() => {
855
+ if (renaming) {
856
+ setRenameValue(name);
857
+ setTimeout(() => renameInputRef.current?.select(), 30);
858
+ }
859
+ }, [renaming, name]);
860
+ const commitRename = useCallback4(() => {
861
+ const v = renameValue.trim();
862
+ if (v && v !== name) onRename?.(slug, v);
863
+ setRenaming(false);
864
+ }, [slug, renameValue, name, onRename]);
865
+ const handleRenameKey = useCallback4((e) => {
866
+ if (e.key === "Enter") commitRename();
867
+ if (e.key === "Escape") setRenaming(false);
868
+ }, [commitRename]);
869
+ const [copied, setCopied] = useState3(false);
870
+ const showPin = offsetX > 10;
871
+ const showDelete = offsetX < -10;
872
+ return /* @__PURE__ */ jsxs7("div", { className: `project-item__wrapper${active ? " project-item__wrapper--active" : ""}${selected ? " project-item__wrapper--selected" : ""}`, children: [
873
+ /* @__PURE__ */ jsxs7("div", { className: `project-item__action project-item__action--pin${showPin ? " project-item__action--visible" : ""}`, children: [
874
+ /* @__PURE__ */ jsx8(BoxIcon, { name: pinned ? "bxs-pin" : "bx-pin", size: 18 }),
875
+ /* @__PURE__ */ jsx8("span", { children: pinned ? "Unpin" : "Pin" })
876
+ ] }),
877
+ /* @__PURE__ */ jsxs7("div", { className: `project-item__action project-item__action--delete${showDelete ? " project-item__action--visible" : ""}`, children: [
878
+ /* @__PURE__ */ jsx8(BoxIcon, { name: "bx-trash", size: 18 }),
879
+ /* @__PURE__ */ jsx8("span", { children: "Delete" })
880
+ ] }),
881
+ /* @__PURE__ */ jsxs7(
882
+ "button",
883
+ {
884
+ className: `project-item${active ? " project-item--active" : ""}${selected ? " project-item--selected" : ""}`,
885
+ onClick: handleClick,
886
+ onContextMenu: handleContextMenu,
887
+ type: "button",
888
+ onPointerDown,
889
+ onPointerUp,
890
+ style: {
891
+ transform: offsetX ? `translateX(${offsetX}px)` : void 0,
892
+ transition: offsetX ? "none" : "transform 0.25s ease"
893
+ },
894
+ children: [
895
+ selectionMode && /* @__PURE__ */ jsx8("span", { className: `project-item__checkbox${selected ? " project-item__checkbox--checked" : ""}`, children: selected && /* @__PURE__ */ jsx8(BoxIcon, { name: "bx-check", size: 14 }) }),
896
+ /* @__PURE__ */ jsx8(
897
+ "span",
898
+ {
899
+ className: "project-item__icon",
900
+ style: color ? { color } : void 0,
901
+ onClick: (e) => {
902
+ if (onIconClick) {
903
+ e.stopPropagation();
904
+ onIconClick(slug);
905
+ }
906
+ },
907
+ children: /* @__PURE__ */ jsx8(BoxIcon, { name: icon || "bx-folder", size: 18 })
908
+ }
909
+ ),
910
+ /* @__PURE__ */ jsxs7("div", { className: "project-item__body", children: [
911
+ renaming ? /* @__PURE__ */ jsx8(
912
+ "input",
913
+ {
914
+ ref: renameInputRef,
915
+ className: "project-item__rename-input",
916
+ value: renameValue,
917
+ onChange: (e) => setRenameValue(e.target.value),
918
+ onBlur: commitRename,
919
+ onKeyDown: handleRenameKey,
920
+ onClick: (e) => e.stopPropagation()
921
+ }
922
+ ) : /* @__PURE__ */ jsx8("span", { className: "project-item__title", children: name }),
923
+ !renaming && /* @__PURE__ */ jsxs7("span", { className: "project-item__preview", children: [
924
+ taskCount,
925
+ " tasks \xB7 ",
926
+ Math.round(completionPercent),
927
+ "%"
928
+ ] }),
929
+ /* @__PURE__ */ jsx8("span", { className: "project-item__status", children: status })
930
+ ] }),
931
+ /* @__PURE__ */ jsx8("div", { className: "project-item__meta", children: pinned && /* @__PURE__ */ jsx8("span", { className: "project-item__pin-badge", "aria-label": "Pinned", children: /* @__PURE__ */ jsx8(BoxIcon, { name: "bxs-pin", size: 12 }) }) })
932
+ ]
933
+ }
934
+ ),
935
+ menuPos && /* @__PURE__ */ jsx8(
936
+ "div",
937
+ {
938
+ ref: menuRef,
939
+ className: "project-item__menu",
940
+ style: { top: menuPos.y, left: menuPos.x },
941
+ children: selectionMode ? /* @__PURE__ */ jsxs7(Fragment2, { children: [
942
+ onPin && /* @__PURE__ */ jsxs7("button", { className: "project-item__menu-item", onClick: () => {
943
+ closeMenu();
944
+ onPin(slug);
945
+ }, type: "button", children: [
946
+ /* @__PURE__ */ jsx8(BoxIcon, { name: pinned ? "bxs-pin" : "bx-pin", size: 15 }),
947
+ " ",
948
+ pinned ? "Unpin" : "Pin"
949
+ ] }),
950
+ onDelete && /* @__PURE__ */ jsxs7("button", { className: "project-item__menu-item project-item__menu-item--danger", onClick: () => {
951
+ closeMenu();
952
+ setConfirmDelete(true);
953
+ }, type: "button", children: [
954
+ /* @__PURE__ */ jsx8(BoxIcon, { name: "bx-trash", size: 15 }),
955
+ " Delete"
956
+ ] })
957
+ ] }) : /* @__PURE__ */ jsxs7(Fragment2, { children: [
958
+ onToggleSelect && /* @__PURE__ */ jsxs7("button", { className: "project-item__menu-item", onClick: () => {
959
+ closeMenu();
960
+ onToggleSelect(slug);
961
+ }, type: "button", children: [
962
+ /* @__PURE__ */ jsx8(BoxIcon, { name: "bx-select-multiple", size: 15 }),
963
+ " Select"
964
+ ] }),
965
+ onRename && /* @__PURE__ */ jsxs7("button", { className: "project-item__menu-item", onClick: () => {
966
+ closeMenu();
967
+ setRenaming(true);
968
+ }, type: "button", children: [
969
+ /* @__PURE__ */ jsx8(BoxIcon, { name: "bx-edit-alt", size: 15 }),
970
+ " Rename"
971
+ ] }),
972
+ /* @__PURE__ */ jsxs7("button", { className: "project-item__menu-item", onClick: () => {
973
+ navigator.clipboard.writeText(slug);
974
+ setCopied(true);
975
+ setTimeout(() => {
976
+ setCopied(false);
977
+ closeMenu();
978
+ }, 600);
979
+ }, type: "button", children: [
980
+ /* @__PURE__ */ jsx8(BoxIcon, { name: copied ? "bx-check" : "bx-copy-alt", size: 15 }),
981
+ " ",
982
+ copied ? "Copied!" : "Copy Slug"
983
+ ] }),
984
+ onPin && /* @__PURE__ */ jsxs7("button", { className: "project-item__menu-item", onClick: () => {
985
+ closeMenu();
986
+ onPin(slug);
987
+ }, type: "button", children: [
988
+ /* @__PURE__ */ jsx8(BoxIcon, { name: pinned ? "bxs-pin" : "bx-pin", size: 15 }),
989
+ " ",
990
+ pinned ? "Unpin" : "Pin"
991
+ ] }),
992
+ onIconChange && /* @__PURE__ */ jsxs7(
993
+ "div",
994
+ {
995
+ className: "project-item__menu-parent",
996
+ onMouseEnter: () => setHoveredSub("icon"),
997
+ onMouseLeave: () => setHoveredSub(null),
998
+ children: [
999
+ /* @__PURE__ */ jsxs7("button", { className: "project-item__menu-item", type: "button", children: [
1000
+ /* @__PURE__ */ jsx8(BoxIcon, { name: "bx-palette", size: 15 }),
1001
+ " Set Icon",
1002
+ /* @__PURE__ */ jsx8("span", { className: "project-item__menu-chevron", children: /* @__PURE__ */ jsx8(BoxIcon, { name: "bx-chevron-right", size: 14 }) })
1003
+ ] }),
1004
+ hoveredSub === "icon" && /* @__PURE__ */ jsx8("div", { className: "project-item__submenu", children: /* @__PURE__ */ jsx8("div", { className: "project-item__icon-grid", children: PROJECT_ICONS.map((ic) => /* @__PURE__ */ jsx8(
1005
+ "button",
1006
+ {
1007
+ className: `project-item__icon-pick${ic === icon ? " project-item__icon-pick--active" : ""}`,
1008
+ onClick: () => {
1009
+ onIconChange(slug, ic);
1010
+ closeMenu();
1011
+ },
1012
+ type: "button",
1013
+ title: ic.replace("bx-", ""),
1014
+ children: /* @__PURE__ */ jsx8(BoxIcon, { name: ic, size: 16 })
1015
+ },
1016
+ ic
1017
+ )) }) })
1018
+ ]
1019
+ }
1020
+ ),
1021
+ onColorChange && /* @__PURE__ */ jsxs7(
1022
+ "div",
1023
+ {
1024
+ className: "project-item__menu-parent",
1025
+ onMouseEnter: () => setHoveredSub("color"),
1026
+ onMouseLeave: () => setHoveredSub(null),
1027
+ children: [
1028
+ /* @__PURE__ */ jsxs7("button", { className: "project-item__menu-item", type: "button", children: [
1029
+ /* @__PURE__ */ jsx8(BoxIcon, { name: "bx-color", size: 15 }),
1030
+ " Set Color",
1031
+ /* @__PURE__ */ jsx8("span", { className: "project-item__menu-chevron", children: /* @__PURE__ */ jsx8(BoxIcon, { name: "bx-chevron-right", size: 14 }) })
1032
+ ] }),
1033
+ hoveredSub === "color" && /* @__PURE__ */ jsx8("div", { className: "project-item__submenu", children: /* @__PURE__ */ jsx8("div", { className: "project-item__color-grid", children: PROJECT_COLORS.map((c) => /* @__PURE__ */ jsx8(
1034
+ "button",
1035
+ {
1036
+ className: `project-item__color-pick${c === color ? " project-item__color-pick--active" : ""}`,
1037
+ style: { background: c },
1038
+ onClick: () => {
1039
+ onColorChange(slug, c);
1040
+ closeMenu();
1041
+ },
1042
+ type: "button"
1043
+ },
1044
+ c
1045
+ )) }) })
1046
+ ]
1047
+ }
1048
+ ),
1049
+ onUpdateKey && /* @__PURE__ */ jsxs7("button", { className: "project-item__menu-item", onClick: () => {
1050
+ closeMenu();
1051
+ onUpdateKey(slug);
1052
+ }, type: "button", children: [
1053
+ /* @__PURE__ */ jsx8(BoxIcon, { name: "bx-key", size: 15 }),
1054
+ " Update Key"
1055
+ ] }),
1056
+ onDelete && /* @__PURE__ */ jsxs7("button", { className: "project-item__menu-item project-item__menu-item--danger", onClick: () => {
1057
+ closeMenu();
1058
+ setConfirmDelete(true);
1059
+ }, type: "button", children: [
1060
+ /* @__PURE__ */ jsx8(BoxIcon, { name: "bx-trash", size: 15 }),
1061
+ " Delete"
1062
+ ] })
1063
+ ] })
1064
+ }
1065
+ ),
1066
+ /* @__PURE__ */ jsx8(
1067
+ "dialog",
1068
+ {
1069
+ ref: dialogRef,
1070
+ className: "project-item__dialog",
1071
+ onClick: handleDialogBackdrop,
1072
+ children: /* @__PURE__ */ jsxs7("div", { className: "project-item__dialog-inner", children: [
1073
+ /* @__PURE__ */ jsx8("div", { className: "project-item__dialog-icon", children: /* @__PURE__ */ jsx8(BoxIcon, { name: "bx-trash", size: 28 }) }),
1074
+ /* @__PURE__ */ jsx8("h3", { className: "project-item__dialog-title", children: "Delete project?" }),
1075
+ /* @__PURE__ */ jsxs7("p", { className: "project-item__dialog-body", children: [
1076
+ '"',
1077
+ /* @__PURE__ */ jsx8("strong", { children: name }),
1078
+ '" will be permanently deleted and cannot be recovered.'
1079
+ ] }),
1080
+ /* @__PURE__ */ jsxs7("div", { className: "project-item__dialog-actions", children: [
1081
+ /* @__PURE__ */ jsx8(
1082
+ "button",
1083
+ {
1084
+ className: "project-item__dialog-btn project-item__dialog-btn--cancel",
1085
+ onClick: handleConfirmNo,
1086
+ type: "button",
1087
+ children: "Cancel"
1088
+ }
1089
+ ),
1090
+ /* @__PURE__ */ jsxs7(
1091
+ "button",
1092
+ {
1093
+ className: "project-item__dialog-btn project-item__dialog-btn--delete",
1094
+ onClick: handleConfirmYes,
1095
+ type: "button",
1096
+ children: [
1097
+ /* @__PURE__ */ jsx8(BoxIcon, { name: "bx-trash", size: 14 }),
1098
+ "Delete"
1099
+ ]
1100
+ }
1101
+ )
1102
+ ] })
1103
+ ] })
1104
+ }
1105
+ )
1106
+ ] });
1107
+ }
1108
+
1109
+ // src/ProjectsSidebar/ProjectsSidebar.tsx
1110
+ import { jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
1111
+ var ProjectsSidebar = ({
1112
+ projects,
1113
+ activeProject,
1114
+ onProjectClick,
1115
+ onCreateProject,
1116
+ onDeleteProject,
1117
+ onPinProject,
1118
+ onRenameProject,
1119
+ onIconChange,
1120
+ onColorChange,
1121
+ onUpdateKey,
1122
+ onIconClick,
1123
+ loading = false,
1124
+ searchQuery: controlledQuery,
1125
+ onSearchChange: controlledOnChange
1126
+ }) => {
1127
+ const [internalQuery, setInternalQuery] = useState4("");
1128
+ const query = controlledQuery ?? internalQuery;
1129
+ const setQuery = controlledOnChange ?? setInternalQuery;
1130
+ const [selectedSlugs, setSelectedSlugs] = useState4(/* @__PURE__ */ new Set());
1131
+ const selectionMode = selectedSlugs.size > 0;
1132
+ const handleToggleSelect = useCallback5((slug) => {
1133
+ setSelectedSlugs((prev) => {
1134
+ const next = new Set(prev);
1135
+ if (next.has(slug)) next.delete(slug);
1136
+ else next.add(slug);
1137
+ return next;
1138
+ });
1139
+ }, []);
1140
+ const sorted = useMemo2(() => {
1141
+ const filtered = query.trim() ? projects.filter(
1142
+ (p) => p.name.toLowerCase().includes(query.toLowerCase()) || p.slug.toLowerCase().includes(query.toLowerCase())
1143
+ ) : projects;
1144
+ return [...filtered].sort((a, b) => {
1145
+ if (a.pinned && !b.pinned) return -1;
1146
+ if (!a.pinned && b.pinned) return 1;
1147
+ return a.name.localeCompare(b.name);
1148
+ });
1149
+ }, [projects, query]);
1150
+ const handleSearch = useCallback5((e) => {
1151
+ setQuery(e.target.value);
1152
+ }, [setQuery]);
1153
+ return /* @__PURE__ */ jsxs8("aside", { className: "projects-sidebar", "data-testid": "projects-sidebar", children: [
1154
+ /* @__PURE__ */ jsxs8("div", { className: "projects-sidebar__header", children: [
1155
+ /* @__PURE__ */ jsx9("h2", { className: "projects-sidebar__title", children: "Projects" }),
1156
+ onCreateProject && /* @__PURE__ */ jsx9(
1157
+ "button",
1158
+ {
1159
+ className: "projects-sidebar__new-btn",
1160
+ "data-testid": "create-project-btn",
1161
+ onClick: onCreateProject,
1162
+ "aria-label": "Create project",
1163
+ children: /* @__PURE__ */ jsx9(BoxIcon2, { name: "bx-plus", size: 18 })
1164
+ }
1165
+ )
1166
+ ] }),
1167
+ /* @__PURE__ */ jsxs8("div", { className: "projects-sidebar__search", children: [
1168
+ /* @__PURE__ */ jsx9("span", { className: "projects-sidebar__search-icon", children: /* @__PURE__ */ jsx9(BoxIcon2, { name: "bx-search", size: 16 }) }),
1169
+ /* @__PURE__ */ jsx9(
1170
+ "input",
1171
+ {
1172
+ className: "projects-sidebar__search-input",
1173
+ type: "text",
1174
+ placeholder: "Search projects...",
1175
+ value: query,
1176
+ onChange: handleSearch
1177
+ }
1178
+ )
1179
+ ] }),
1180
+ loading ? /* @__PURE__ */ jsx9("div", { className: "projects-sidebar__loading", "aria-label": "Loading projects", children: /* @__PURE__ */ jsx9(BoxIcon2, { name: "bx-loader-alt", size: 20, className: "projects-sidebar__spinner" }) }) : sorted.length > 0 ? /* @__PURE__ */ jsx9("div", { className: "projects-sidebar__list", "data-testid": "projects-list", children: sorted.map((project) => /* @__PURE__ */ jsx9(
1181
+ ProjectItem,
1182
+ {
1183
+ slug: project.slug,
1184
+ name: project.name,
1185
+ status: project.status,
1186
+ taskCount: project.taskCount,
1187
+ completionPercent: project.completionPercent,
1188
+ active: activeProject === project.slug,
1189
+ pinned: project.pinned,
1190
+ icon: project.icon,
1191
+ color: project.color,
1192
+ selected: selectedSlugs.has(project.slug),
1193
+ selectionMode,
1194
+ onSelect: onProjectClick,
1195
+ onDelete: onDeleteProject,
1196
+ onPin: onPinProject,
1197
+ onRename: onRenameProject,
1198
+ onIconChange,
1199
+ onColorChange,
1200
+ onUpdateKey,
1201
+ onToggleSelect: handleToggleSelect,
1202
+ onIconClick
1203
+ },
1204
+ project.slug
1205
+ )) }) : /* @__PURE__ */ jsx9(
1206
+ EmptyState,
1207
+ {
1208
+ icon: /* @__PURE__ */ jsx9(BoxIcon2, { name: "bx-grid-alt", size: 40 }),
1209
+ title: query.trim() ? "No matching projects" : "No projects yet",
1210
+ description: query.trim() ? "Try a different search term" : "Create a project to get started"
1211
+ }
1212
+ )
1213
+ ] });
1214
+ };
1215
+
1216
+ // src/stores/useDashboardStore.ts
1217
+ import { create } from "zustand";
1218
+ import { persist } from "zustand/middleware";
1219
+ var defaultLayout = [
1220
+ { id: "w-project-status", type: "project-status", col: 1, row: 1, colSpan: 1, rowSpan: 1, collapsed: false, hidden: false },
1221
+ { id: "w-backlog-tree", type: "backlog-tree", col: 1, row: 2, colSpan: 1, rowSpan: 1, collapsed: false, hidden: true },
1222
+ { id: "w-task-distribution", type: "task-distribution", col: 1, row: 3, colSpan: 1, rowSpan: 1, collapsed: false, hidden: true },
1223
+ { id: "w-progress-distribution", type: "progress-distribution", col: 1, row: 4, colSpan: 1, rowSpan: 1, collapsed: false, hidden: true },
1224
+ { id: "w-active-tasks", type: "active-tasks", col: 1, row: 5, colSpan: 1, rowSpan: 1, collapsed: false, hidden: true },
1225
+ { id: "w-recent-activity", type: "recent-activity", col: 1, row: 6, colSpan: 1, rowSpan: 1, collapsed: false, hidden: true },
1226
+ { id: "w-sprint-progress", type: "sprint-progress", col: 1, row: 7, colSpan: 1, rowSpan: 1, collapsed: false, hidden: true },
1227
+ { id: "w-team-workload", type: "team-workload", col: 1, row: 8, colSpan: 1, rowSpan: 1, collapsed: false, hidden: true },
1228
+ { id: "w-session-metrics", type: "session-metrics", col: 1, row: 9, colSpan: 1, rowSpan: 1, collapsed: false, hidden: true },
1229
+ { id: "w-feature-adoption", type: "feature-adoption", col: 1, row: 10, colSpan: 1, rowSpan: 1, collapsed: false, hidden: true }
1230
+ ];
1231
+ function updateWidget(state, slug, widgetId, updater) {
1232
+ const current = state.layouts[slug] ?? defaultLayout;
1233
+ return {
1234
+ layouts: {
1235
+ ...state.layouts,
1236
+ [slug]: current.map((w) => w.id === widgetId ? updater(w) : w)
1237
+ }
1238
+ };
1239
+ }
1240
+ var useDashboardStore = create()(
1241
+ persist(
1242
+ (set, get) => ({
1243
+ layouts: {},
1244
+ columns: {},
1245
+ activeProject: null,
1246
+ setActiveProject: (slug) => set({ activeProject: slug }),
1247
+ getLayout: (slug) => get().layouts[slug] ?? defaultLayout,
1248
+ updateLayout: (slug, layouts) => set((s) => ({ layouts: { ...s.layouts, [slug]: layouts } })),
1249
+ moveWidget: (slug, widgetId, col, row) => set((s) => {
1250
+ const current = s.layouts[slug] ?? defaultLayout;
1251
+ const widgetIndex = current.findIndex((w) => w.id === widgetId);
1252
+ if (widgetIndex === -1) return s;
1253
+ const widget = current[widgetIndex];
1254
+ const updatedWidget = { ...widget, col, row };
1255
+ const withoutWidget = current.filter((w) => w.id !== widgetId);
1256
+ let targetIndex = 0;
1257
+ for (let i = 0; i < withoutWidget.length; i++) {
1258
+ const w = withoutWidget[i];
1259
+ if (w.row < row || w.row === row && w.col < col) {
1260
+ targetIndex = i + 1;
1261
+ }
1262
+ }
1263
+ const reordered = [
1264
+ ...withoutWidget.slice(0, targetIndex),
1265
+ updatedWidget,
1266
+ ...withoutWidget.slice(targetIndex)
1267
+ ];
1268
+ return { layouts: { ...s.layouts, [slug]: reordered } };
1269
+ }),
1270
+ moveWidgetToIndex: (slug, widgetId, newIndex) => set((s) => {
1271
+ const current = s.layouts[slug] ?? defaultLayout;
1272
+ const widget = current.find((w) => w.id === widgetId);
1273
+ if (!widget) return s;
1274
+ const withoutWidget = current.filter((w) => w.id !== widgetId);
1275
+ const targetIndex = Math.max(0, Math.min(withoutWidget.length, newIndex));
1276
+ const reordered = [
1277
+ ...withoutWidget.slice(0, targetIndex),
1278
+ widget,
1279
+ ...withoutWidget.slice(targetIndex)
1280
+ ];
1281
+ return { layouts: { ...s.layouts, [slug]: reordered } };
1282
+ }),
1283
+ resizeWidget: (slug, widgetId, colSpan, rowSpan) => set((s) => updateWidget(s, slug, widgetId, (w) => ({ ...w, colSpan, rowSpan }))),
1284
+ toggleCollapse: (slug, widgetId) => set((s) => updateWidget(s, slug, widgetId, (w) => ({ ...w, collapsed: !w.collapsed }))),
1285
+ toggleHidden: (slug, widgetId) => set((s) => updateWidget(s, slug, widgetId, (w) => ({ ...w, hidden: !w.hidden }))),
1286
+ toggleLocked: (slug, widgetId) => set((s) => updateWidget(s, slug, widgetId, (w) => ({ ...w, locked: !w.locked }))),
1287
+ setWidgetColSpan: (slug, widgetId, colSpan) => set((s) => updateWidget(s, slug, widgetId, (w) => ({ ...w, colSpan, col: 1 }))),
1288
+ addWidget: (slug, type) => set((s) => {
1289
+ const current = s.layouts[slug] ?? defaultLayout;
1290
+ const id = `w-${type}-${Date.now()}`;
1291
+ return {
1292
+ layouts: {
1293
+ ...s.layouts,
1294
+ [slug]: [...current, { id, type, col: 1, row: 99, colSpan: 1, rowSpan: 1, collapsed: false }]
1295
+ }
1296
+ };
1297
+ }),
1298
+ removeWidget: (slug, widgetId) => set((s) => {
1299
+ const current = s.layouts[slug] ?? defaultLayout;
1300
+ return { layouts: { ...s.layouts, [slug]: current.filter((w) => w.id !== widgetId) } };
1301
+ }),
1302
+ resetLayout: (slug) => set((s) => ({ layouts: { ...s.layouts, [slug]: defaultLayout } })),
1303
+ getColumns: (slug) => get().columns[slug] ?? 1,
1304
+ setColumns: (slug, cols) => set((s) => ({ columns: { ...s.columns, [slug]: cols } }))
1305
+ }),
1306
+ {
1307
+ name: "orchestra-dashboard-layout",
1308
+ version: 8,
1309
+ migrate: () => ({ layouts: {}, columns: {}, activeProject: null })
1310
+ }
1311
+ )
1312
+ );
1313
+
1314
+ // src/stores/useIntegrationStore.ts
1315
+ import { create as create2 } from "zustand";
1316
+ import { persist as persist2 } from "zustand/middleware";
1317
+ var emptyConnection = {
1318
+ connected: false,
1319
+ user: null,
1320
+ lastSync: null
1321
+ };
1322
+ var useIntegrationStore = create2()(
1323
+ persist2(
1324
+ (set) => ({
1325
+ jira: { ...emptyConnection },
1326
+ linear: { ...emptyConnection },
1327
+ github: { ...emptyConnection },
1328
+ syncInProgress: false,
1329
+ setConnected: (provider, user) => set((s) => ({
1330
+ [provider]: { ...s[provider], connected: true, user }
1331
+ })),
1332
+ disconnect: (provider) => set(() => ({
1333
+ [provider]: { ...emptyConnection }
1334
+ })),
1335
+ setSyncInProgress: (value) => set({ syncInProgress: value }),
1336
+ updateLastSync: (provider, time) => set((s) => ({
1337
+ [provider]: { ...s[provider], lastSync: time }
1338
+ }))
1339
+ }),
1340
+ { name: "orchestra-integrations" }
1341
+ )
1342
+ );
1343
+
1344
+ // src/stores/useTaskDetailStore.ts
1345
+ import { create as create3 } from "zustand";
1346
+ var useTaskDetailStore = create3((set) => ({
1347
+ selectedTask: null,
1348
+ selectTask: (task) => set({ selectedTask: task }),
1349
+ clearSelection: () => set({ selectedTask: null })
1350
+ }));
1351
+
1352
+ // src/ProjectDashboard/ProjectDashboard.tsx
1353
+ import { useState as useState18, useCallback as useCallback19 } from "react";
1354
+
1355
+ // src/ProjectDashboard/DashboardHeader.tsx
1356
+ import { BoxIcon as BoxIcon3 } from "@orchestra-mcp/icons";
1357
+ import { jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
1358
+ var DashboardHeader = ({
1359
+ name,
1360
+ onRefresh,
1361
+ onSettingsToggle
1362
+ }) => {
1363
+ return /* @__PURE__ */ jsxs9("div", { className: "dashboard-header", "data-testid": "dashboard-header", children: [
1364
+ /* @__PURE__ */ jsx10("h2", { className: "dashboard-header__title", children: name }),
1365
+ /* @__PURE__ */ jsx10("div", { className: "dashboard-header__spacer" }),
1366
+ onRefresh && /* @__PURE__ */ jsx10(
1367
+ "button",
1368
+ {
1369
+ className: "dashboard-header__btn",
1370
+ onClick: onRefresh,
1371
+ "aria-label": "Refresh",
1372
+ type: "button",
1373
+ children: /* @__PURE__ */ jsx10(BoxIcon3, { name: "bx-refresh", size: 16 })
1374
+ }
1375
+ ),
1376
+ /* @__PURE__ */ jsx10(
1377
+ "button",
1378
+ {
1379
+ className: "dashboard-header__btn",
1380
+ onClick: onSettingsToggle,
1381
+ "aria-label": "Settings",
1382
+ type: "button",
1383
+ children: /* @__PURE__ */ jsx10(BoxIcon3, { name: "bx-slider-alt", size: 16 })
1384
+ }
1385
+ )
1386
+ ] });
1387
+ };
1388
+
1389
+ // src/ProjectDashboard/DashboardSettings.tsx
1390
+ import { useEffect as useEffect5, useRef as useRef7 } from "react";
1391
+ import { BoxIcon as BoxIcon4 } from "@orchestra-mcp/icons";
1392
+ import { jsx as jsx11, jsxs as jsxs10 } from "react/jsx-runtime";
1393
+ var COLUMN_OPTIONS = [1, 2, 4, 6, 12];
1394
+ var DashboardSettings = ({
1395
+ x,
1396
+ y,
1397
+ columns,
1398
+ onColumnsChange,
1399
+ widgets,
1400
+ onWidgetToggle,
1401
+ onClose
1402
+ }) => {
1403
+ const menuRef = useRef7(null);
1404
+ useEffect5(() => {
1405
+ const handleClick = (e) => {
1406
+ if (menuRef.current && !menuRef.current.contains(e.target)) {
1407
+ onClose();
1408
+ }
1409
+ };
1410
+ const handleEscape = (e) => {
1411
+ if (e.key === "Escape") onClose();
1412
+ };
1413
+ document.addEventListener("mousedown", handleClick);
1414
+ document.addEventListener("keydown", handleEscape);
1415
+ return () => {
1416
+ document.removeEventListener("mousedown", handleClick);
1417
+ document.removeEventListener("keydown", handleEscape);
1418
+ };
1419
+ }, [onClose]);
1420
+ useEffect5(() => {
1421
+ if (!menuRef.current) return;
1422
+ const rect = menuRef.current.getBoundingClientRect();
1423
+ const maxX = window.innerWidth - rect.width - 8;
1424
+ const maxY = window.innerHeight - rect.height - 8;
1425
+ if (x > maxX) menuRef.current.style.left = `${maxX}px`;
1426
+ if (y > maxY) menuRef.current.style.top = `${maxY}px`;
1427
+ }, [x, y]);
1428
+ return /* @__PURE__ */ jsxs10(
1429
+ "div",
1430
+ {
1431
+ ref: menuRef,
1432
+ className: "dashboard-settings",
1433
+ style: { left: x, top: y },
1434
+ "data-testid": "dashboard-settings",
1435
+ children: [
1436
+ /* @__PURE__ */ jsxs10("div", { className: "dashboard-settings__section", children: [
1437
+ /* @__PURE__ */ jsx11("label", { className: "dashboard-settings__label", children: "Columns per row" }),
1438
+ /* @__PURE__ */ jsx11("div", { className: "dashboard-settings__chips", children: COLUMN_OPTIONS.map((n) => /* @__PURE__ */ jsx11(
1439
+ "button",
1440
+ {
1441
+ type: "button",
1442
+ className: `dashboard-settings__chip${columns === n ? " dashboard-settings__chip--active" : ""}`,
1443
+ onClick: () => onColumnsChange(n),
1444
+ children: n
1445
+ },
1446
+ n
1447
+ )) })
1448
+ ] }),
1449
+ /* @__PURE__ */ jsx11("div", { className: "dashboard-settings__divider" }),
1450
+ /* @__PURE__ */ jsxs10("div", { className: "dashboard-settings__section", children: [
1451
+ /* @__PURE__ */ jsx11("label", { className: "dashboard-settings__label", children: "Widgets" }),
1452
+ /* @__PURE__ */ jsx11("ul", { className: "dashboard-settings__widget-list", children: widgets.map((w) => /* @__PURE__ */ jsxs10("li", { className: "dashboard-settings__widget-row", children: [
1453
+ /* @__PURE__ */ jsx11("span", { className: "dashboard-settings__widget-name", children: w.title }),
1454
+ /* @__PURE__ */ jsx11(
1455
+ "button",
1456
+ {
1457
+ type: "button",
1458
+ className: `dashboard-settings__toggle${w.hidden ? "" : " dashboard-settings__toggle--on"}`,
1459
+ onClick: () => onWidgetToggle(w.id),
1460
+ "aria-label": w.hidden ? `Show ${w.title}` : `Hide ${w.title}`,
1461
+ children: /* @__PURE__ */ jsx11(BoxIcon4, { name: w.hidden ? "bx-hide" : "bx-show", size: 14 })
1462
+ }
1463
+ )
1464
+ ] }, w.id)) })
1465
+ ] })
1466
+ ]
1467
+ }
1468
+ );
1469
+ };
1470
+
1471
+ // src/TaskDetailPanel/TaskDetailPanel.tsx
1472
+ import { useMemo as useMemo4, useCallback as useCallback17, useState as useState16 } from "react";
1473
+
1474
+ // src/StatusBadge/StatusBadge.tsx
1475
+ import { jsx as jsx12, jsxs as jsxs11 } from "react/jsx-runtime";
1476
+ var STATUS_COLORS = {
1477
+ backlog: "#6b7280",
1478
+ todo: "#3b82f6",
1479
+ "in-progress": "#8b5cf6",
1480
+ blocked: "#ef4444",
1481
+ "ready-for-testing": "#f59e0b",
1482
+ "in-testing": "#f97316",
1483
+ "ready-for-docs": "#06b6d4",
1484
+ "in-docs": "#0ea5e9",
1485
+ documented: "#14b8a6",
1486
+ "in-review": "#a855f7",
1487
+ done: "#22c55e",
1488
+ rejected: "#ef4444",
1489
+ cancelled: "#6b7280"
1490
+ };
1491
+ var STATUS_LABELS = {
1492
+ backlog: "Backlog",
1493
+ todo: "To Do",
1494
+ "in-progress": "In Progress",
1495
+ blocked: "Blocked",
1496
+ "ready-for-testing": "Ready for Testing",
1497
+ "in-testing": "In Testing",
1498
+ "ready-for-docs": "Ready for Docs",
1499
+ "in-docs": "In Docs",
1500
+ documented: "Documented",
1501
+ "in-review": "In Review",
1502
+ done: "Done",
1503
+ rejected: "Rejected",
1504
+ cancelled: "Cancelled"
1505
+ };
1506
+ function StatusBadge({ status, variant = "dot", size = "sm" }) {
1507
+ const color = STATUS_COLORS[status] ?? "#6b7280";
1508
+ const label = STATUS_LABELS[status] ?? status;
1509
+ if (variant === "dot") {
1510
+ return /* @__PURE__ */ jsx12(
1511
+ "span",
1512
+ {
1513
+ className: `status-badge status-badge--dot status-badge--${size}`,
1514
+ style: { backgroundColor: color },
1515
+ title: label,
1516
+ "aria-label": label
1517
+ }
1518
+ );
1519
+ }
1520
+ return /* @__PURE__ */ jsxs11(
1521
+ "span",
1522
+ {
1523
+ className: `status-badge status-badge--pill status-badge--${size}`,
1524
+ style: { backgroundColor: `${color}20`, color },
1525
+ title: label,
1526
+ children: [
1527
+ /* @__PURE__ */ jsx12("span", { className: "status-badge__dot", style: { backgroundColor: color } }),
1528
+ label
1529
+ ]
1530
+ }
1531
+ );
1532
+ }
1533
+
1534
+ // src/PriorityIcon/PriorityIcon.tsx
1535
+ import { BoxIcon as BoxIcon5 } from "@orchestra-mcp/icons";
1536
+ import { jsx as jsx13 } from "react/jsx-runtime";
1537
+ var PRIORITY_CONFIG = {
1538
+ critical: { icon: "bxs-chevrons-up", color: "#ef4444", label: "Critical" },
1539
+ high: { icon: "bx-chevron-up", color: "#f97316", label: "High" },
1540
+ medium: { icon: "bx-minus", color: "#eab308", label: "Medium" },
1541
+ low: { icon: "bx-chevron-down", color: "#6b7280", label: "Low" }
1542
+ };
1543
+ function PriorityIcon({ priority, size = 14 }) {
1544
+ const config = PRIORITY_CONFIG[priority];
1545
+ if (!config) return null;
1546
+ return /* @__PURE__ */ jsx13(
1547
+ "span",
1548
+ {
1549
+ className: "priority-icon",
1550
+ style: { color: config.color },
1551
+ title: config.label,
1552
+ "aria-label": `${config.label} priority`,
1553
+ children: /* @__PURE__ */ jsx13(BoxIcon5, { name: config.icon, size })
1554
+ }
1555
+ );
1556
+ }
1557
+
1558
+ // src/InlineEdit/InlineEdit.tsx
1559
+ import { useState as useState5, useRef as useRef8, useEffect as useEffect6, useCallback as useCallback6 } from "react";
1560
+ import { jsx as jsx14, jsxs as jsxs12 } from "react/jsx-runtime";
1561
+ function EditableText({ value, onSave, saving, className, tag: Tag = "span" }) {
1562
+ const [editing, setEditing] = useState5(false);
1563
+ const [draft, setDraft] = useState5(value);
1564
+ const inputRef = useRef8(null);
1565
+ useEffect6(() => {
1566
+ setDraft(value);
1567
+ }, [value]);
1568
+ useEffect6(() => {
1569
+ if (editing) inputRef.current?.focus();
1570
+ }, [editing]);
1571
+ const save = useCallback6(() => {
1572
+ setEditing(false);
1573
+ if (draft.trim() && draft !== value) onSave(draft.trim());
1574
+ else setDraft(value);
1575
+ }, [draft, value, onSave]);
1576
+ const handleKeyDown = useCallback6((e) => {
1577
+ if (e.key === "Enter") save();
1578
+ if (e.key === "Escape") {
1579
+ setDraft(value);
1580
+ setEditing(false);
1581
+ }
1582
+ }, [save, value]);
1583
+ if (editing) {
1584
+ return /* @__PURE__ */ jsx14(
1585
+ "input",
1586
+ {
1587
+ ref: inputRef,
1588
+ className: `inline-edit__input ${className ?? ""}`,
1589
+ value: draft,
1590
+ onChange: (e) => setDraft(e.target.value),
1591
+ onBlur: save,
1592
+ onKeyDown: handleKeyDown
1593
+ }
1594
+ );
1595
+ }
1596
+ return /* @__PURE__ */ jsxs12(
1597
+ Tag,
1598
+ {
1599
+ className: `inline-edit__text ${className ?? ""}${saving ? " inline-edit--saving" : ""}`,
1600
+ onClick: () => setEditing(true),
1601
+ role: "button",
1602
+ tabIndex: 0,
1603
+ children: [
1604
+ value || /* @__PURE__ */ jsx14("span", { className: "inline-edit__placeholder", children: "Click to edit..." }),
1605
+ saving && /* @__PURE__ */ jsx14("span", { className: "inline-edit__indicator" })
1606
+ ]
1607
+ }
1608
+ );
1609
+ }
1610
+ function EditableTextarea({ value, onSave, saving, placeholder }) {
1611
+ const [editing, setEditing] = useState5(false);
1612
+ const [draft, setDraft] = useState5(value);
1613
+ const textareaRef = useRef8(null);
1614
+ useEffect6(() => {
1615
+ setDraft(value);
1616
+ }, [value]);
1617
+ useEffect6(() => {
1618
+ if (editing && textareaRef.current) {
1619
+ textareaRef.current.focus();
1620
+ textareaRef.current.style.height = "auto";
1621
+ textareaRef.current.style.height = `${textareaRef.current.scrollHeight}px`;
1622
+ }
1623
+ }, [editing]);
1624
+ const save = useCallback6(() => {
1625
+ setEditing(false);
1626
+ if (draft !== value) onSave(draft);
1627
+ else setDraft(value);
1628
+ }, [draft, value, onSave]);
1629
+ const handleKeyDown = useCallback6((e) => {
1630
+ if (e.key === "Escape") {
1631
+ setDraft(value);
1632
+ setEditing(false);
1633
+ }
1634
+ }, [value]);
1635
+ const handleInput = useCallback6((e) => {
1636
+ setDraft(e.target.value);
1637
+ e.target.style.height = "auto";
1638
+ e.target.style.height = `${e.target.scrollHeight}px`;
1639
+ }, []);
1640
+ if (editing) {
1641
+ return /* @__PURE__ */ jsx14(
1642
+ "textarea",
1643
+ {
1644
+ ref: textareaRef,
1645
+ className: "inline-edit__textarea",
1646
+ value: draft,
1647
+ onChange: handleInput,
1648
+ onBlur: save,
1649
+ onKeyDown: handleKeyDown
1650
+ }
1651
+ );
1652
+ }
1653
+ return /* @__PURE__ */ jsxs12(
1654
+ "p",
1655
+ {
1656
+ className: `inline-edit__text inline-edit__text--multiline${saving ? " inline-edit--saving" : ""}`,
1657
+ onClick: () => setEditing(true),
1658
+ role: "button",
1659
+ tabIndex: 0,
1660
+ children: [
1661
+ value || /* @__PURE__ */ jsx14("span", { className: "inline-edit__placeholder", children: placeholder ?? "Click to add..." }),
1662
+ saving && /* @__PURE__ */ jsx14("span", { className: "inline-edit__indicator" })
1663
+ ]
1664
+ }
1665
+ );
1666
+ }
1667
+ function EditableSelect({ value, options, onSave, saving, placeholder }) {
1668
+ const [editing, setEditing] = useState5(false);
1669
+ const selectRef = useRef8(null);
1670
+ useEffect6(() => {
1671
+ if (editing) selectRef.current?.focus();
1672
+ }, [editing]);
1673
+ const handleChange = useCallback6((e) => {
1674
+ setEditing(false);
1675
+ if (e.target.value !== value) onSave(e.target.value);
1676
+ }, [value, onSave]);
1677
+ const label = options.find((o) => o.value === value)?.label ?? value;
1678
+ if (editing) {
1679
+ return /* @__PURE__ */ jsxs12(
1680
+ "select",
1681
+ {
1682
+ ref: selectRef,
1683
+ className: "inline-edit__select",
1684
+ value,
1685
+ onChange: handleChange,
1686
+ onBlur: () => setEditing(false),
1687
+ children: [
1688
+ placeholder && /* @__PURE__ */ jsx14("option", { value: "", children: placeholder }),
1689
+ options.map((o) => /* @__PURE__ */ jsx14("option", { value: o.value, children: o.label }, o.value))
1690
+ ]
1691
+ }
1692
+ );
1693
+ }
1694
+ return /* @__PURE__ */ jsxs12(
1695
+ "span",
1696
+ {
1697
+ className: `inline-edit__text${saving ? " inline-edit--saving" : ""}`,
1698
+ onClick: () => setEditing(true),
1699
+ role: "button",
1700
+ tabIndex: 0,
1701
+ children: [
1702
+ label || placeholder || "\u2014",
1703
+ saving && /* @__PURE__ */ jsx14("span", { className: "inline-edit__indicator" })
1704
+ ]
1705
+ }
1706
+ );
1707
+ }
1708
+
1709
+ // src/TaskDetailPanel/TaskDetailPanel.tsx
1710
+ import { MarkdownRenderer } from "@orchestra-mcp/editor";
1711
+
1712
+ // src/hooks/useProjects.ts
1713
+ import { useState as useState6, useEffect as useEffect7, useCallback as useCallback7 } from "react";
1714
+ function useProjects() {
1715
+ const [projects, setProjects] = useState6([]);
1716
+ const [loading, setLoading] = useState6(true);
1717
+ const [error, setError] = useState6(null);
1718
+ const refresh = useCallback7(async () => {
1719
+ setLoading(true);
1720
+ setError(null);
1721
+ try {
1722
+ const data = await mcpCall("list_projects");
1723
+ if (!data.projects || !Array.isArray(data.projects)) {
1724
+ setProjects([]);
1725
+ setLoading(false);
1726
+ return;
1727
+ }
1728
+ const summaries = await Promise.all(
1729
+ data.projects.map(async (p) => {
1730
+ const displayName = p.project || p.name || p.slug;
1731
+ try {
1732
+ const wf = await mcpCall(
1733
+ "get_workflow_status",
1734
+ { project: p.slug }
1735
+ );
1736
+ return {
1737
+ slug: p.slug,
1738
+ name: displayName,
1739
+ status: p.status ?? "active",
1740
+ epicCount: 0,
1741
+ taskCount: wf?.total ?? 0,
1742
+ completionPercent: wf?.completion_pct ? parseFloat(wf.completion_pct) : 0,
1743
+ integrations: []
1744
+ };
1745
+ } catch {
1746
+ return {
1747
+ slug: p.slug,
1748
+ name: displayName,
1749
+ status: p.status ?? "active",
1750
+ epicCount: 0,
1751
+ taskCount: 0,
1752
+ completionPercent: 0,
1753
+ integrations: []
1754
+ };
1755
+ }
1756
+ })
1757
+ );
1758
+ setProjects(summaries);
1759
+ } catch (err) {
1760
+ setError(err instanceof Error ? err.message : "Failed to load projects");
1761
+ }
1762
+ setLoading(false);
1763
+ }, []);
1764
+ useEffect7(() => {
1765
+ refresh();
1766
+ }, [refresh]);
1767
+ const createProject = useCallback7(
1768
+ async (name) => {
1769
+ await mcpCall("create_project", { name });
1770
+ await refresh();
1771
+ },
1772
+ [refresh]
1773
+ );
1774
+ return { projects, loading, error, refresh, createProject };
1775
+ }
1776
+
1777
+ // src/hooks/useProjectTree.ts
1778
+ import { useState as useState7, useEffect as useEffect8, useCallback as useCallback8 } from "react";
1779
+ function mapTree(epics) {
1780
+ return epics.map((e) => ({
1781
+ id: e.id,
1782
+ title: e.title,
1783
+ type: "epic",
1784
+ status: e.status,
1785
+ priority: e.priority,
1786
+ children: (e.stories ?? []).map((s) => ({
1787
+ id: s.id,
1788
+ title: s.title,
1789
+ type: "story",
1790
+ status: s.status,
1791
+ priority: s.priority,
1792
+ estimate: s.estimate,
1793
+ children: (s.tasks ?? []).map((t) => ({
1794
+ id: t.id,
1795
+ title: t.title,
1796
+ type: t.type || "task",
1797
+ status: t.status,
1798
+ priority: t.priority,
1799
+ description: t.description,
1800
+ labels: t.labels,
1801
+ estimate: t.estimate,
1802
+ assignedTo: t.assigned_to
1803
+ }))
1804
+ }))
1805
+ }));
1806
+ }
1807
+ function useProjectTree(slug) {
1808
+ const [tree, setTree] = useState7([]);
1809
+ const [loading, setLoading] = useState7(false);
1810
+ const [error, setError] = useState7(null);
1811
+ const refresh = useCallback8(async () => {
1812
+ if (!slug) {
1813
+ setTree([]);
1814
+ return;
1815
+ }
1816
+ setLoading(true);
1817
+ setError(null);
1818
+ try {
1819
+ const raw = await mcpCall("get_project_tree", { project: slug });
1820
+ setTree(Array.isArray(raw) ? mapTree(raw) : []);
1821
+ } catch (err) {
1822
+ setError(err instanceof Error ? err.message : "Failed to load project tree");
1823
+ }
1824
+ setLoading(false);
1825
+ }, [slug]);
1826
+ useEffect8(() => {
1827
+ refresh();
1828
+ }, [refresh]);
1829
+ return { tree, loading, error, refresh };
1830
+ }
1831
+
1832
+ // src/hooks/useTaskActions.ts
1833
+ import { useState as useState8, useCallback as useCallback9 } from "react";
1834
+ function useTaskActions() {
1835
+ const [actionState, setActionState] = useState8({});
1836
+ const getState = (key) => actionState[key] ?? { loading: false, error: null };
1837
+ const run = useCallback9(async (key, fn) => {
1838
+ setActionState((prev) => ({ ...prev, [key]: { loading: true, error: null } }));
1839
+ try {
1840
+ await fn();
1841
+ setActionState((prev) => ({ ...prev, [key]: { loading: false, error: null } }));
1842
+ } catch (err) {
1843
+ const message = err instanceof Error ? err.message : "Action failed";
1844
+ setActionState((prev) => ({ ...prev, [key]: { loading: false, error: message } }));
1845
+ }
1846
+ }, []);
1847
+ const startTask = useCallback9(
1848
+ (loc) => run("start", () => mcpCall("set_current_task", loc)),
1849
+ [run]
1850
+ );
1851
+ const advanceTask = useCallback9(
1852
+ (loc, evidence) => run("advance", () => mcpCall("advance_task", { ...loc, evidence })),
1853
+ [run]
1854
+ );
1855
+ const blockTask = useCallback9(
1856
+ (loc) => run("block", () => mcpCall("update_task", { ...loc, status: "blocked" })),
1857
+ [run]
1858
+ );
1859
+ const assignTask = useCallback9(
1860
+ (loc, assignee) => run("assign", () => mcpCall("assign_task", { ...loc, assignee })),
1861
+ [run]
1862
+ );
1863
+ const completeTask = useCallback9(
1864
+ (loc) => run("complete", () => mcpCall("complete_task", loc)),
1865
+ [run]
1866
+ );
1867
+ return {
1868
+ startTask,
1869
+ advanceTask,
1870
+ blockTask,
1871
+ assignTask,
1872
+ completeTask,
1873
+ starting: getState("start"),
1874
+ advancing: getState("advance"),
1875
+ blocking: getState("block"),
1876
+ assigning: getState("assign"),
1877
+ completing: getState("complete")
1878
+ };
1879
+ }
1880
+
1881
+ // src/hooks/useTaskDetail.ts
1882
+ import { useState as useState9, useEffect as useEffect9, useCallback as useCallback10, useRef as useRef9 } from "react";
1883
+ function useTaskDetail(params) {
1884
+ const [task, setTask] = useState9(null);
1885
+ const [loading, setLoading] = useState9(false);
1886
+ const [error, setError] = useState9(null);
1887
+ const [isStale, setIsStale] = useState9(false);
1888
+ const latestRef = useRef9(null);
1889
+ const project = params?.project ?? null;
1890
+ const epicId = params?.epicId ?? null;
1891
+ const storyId = params?.storyId ?? null;
1892
+ const taskId = params?.taskId ?? null;
1893
+ const refresh = useCallback10(async () => {
1894
+ if (!project || !epicId || !storyId || !taskId) {
1895
+ setTask(null);
1896
+ return;
1897
+ }
1898
+ setLoading(true);
1899
+ setError(null);
1900
+ try {
1901
+ const result = await mcpCall("get_task", {
1902
+ project,
1903
+ epic_id: epicId,
1904
+ story_id: storyId,
1905
+ task_id: taskId
1906
+ });
1907
+ setTask(result);
1908
+ setIsStale(false);
1909
+ latestRef.current = null;
1910
+ } catch (err) {
1911
+ setError(err instanceof Error ? err.message : "Failed to load task");
1912
+ }
1913
+ setLoading(false);
1914
+ }, [project, epicId, storyId, taskId]);
1915
+ useEffect9(() => {
1916
+ refresh();
1917
+ }, [refresh]);
1918
+ const checkForUpdates = useCallback10(async () => {
1919
+ if (!project || !epicId || !storyId || !taskId || !task) return;
1920
+ try {
1921
+ const fresh = await mcpCall("get_task", {
1922
+ project,
1923
+ epic_id: epicId,
1924
+ story_id: storyId,
1925
+ task_id: taskId
1926
+ });
1927
+ if (fresh.updated_at !== task.updated_at) {
1928
+ latestRef.current = fresh;
1929
+ setIsStale(true);
1930
+ }
1931
+ } catch {
1932
+ }
1933
+ }, [project, epicId, storyId, taskId, task]);
1934
+ const dismissStale = useCallback10(() => {
1935
+ setIsStale(false);
1936
+ latestRef.current = null;
1937
+ }, []);
1938
+ const acceptStale = useCallback10(() => {
1939
+ if (latestRef.current) {
1940
+ setTask(latestRef.current);
1941
+ latestRef.current = null;
1942
+ }
1943
+ setIsStale(false);
1944
+ }, []);
1945
+ return {
1946
+ task,
1947
+ loading,
1948
+ error,
1949
+ refresh,
1950
+ isStale,
1951
+ checkForUpdates,
1952
+ dismissStale,
1953
+ acceptStale
1954
+ };
1955
+ }
1956
+
1957
+ // src/hooks/useFilteredTree.ts
1958
+ import { useMemo as useMemo3 } from "react";
1959
+ function useFilteredTree(tree, filters) {
1960
+ return useMemo3(() => {
1961
+ const hasFilters = filters.statuses.length > 0 || filters.priorities.length > 0 || filters.assignee !== "" || filters.searchQuery !== "";
1962
+ if (!hasFilters) return tree;
1963
+ return filterTree(tree, filters);
1964
+ }, [tree, filters]);
1965
+ }
1966
+ function filterTree(nodes, filters) {
1967
+ return nodes.reduce((acc, node) => {
1968
+ if (node.type === "task" || node.type === "bug" || node.type === "hotfix") {
1969
+ if (matchesFilters(node, filters)) acc.push(node);
1970
+ } else {
1971
+ const filteredChildren = filterTree(node.children ?? [], filters);
1972
+ if (filteredChildren.length > 0) {
1973
+ acc.push({ ...node, children: filteredChildren });
1974
+ }
1975
+ }
1976
+ return acc;
1977
+ }, []);
1978
+ }
1979
+ function matchesFilters(node, filters) {
1980
+ if (filters.statuses.length > 0 && !filters.statuses.includes(node.status)) {
1981
+ return false;
1982
+ }
1983
+ if (filters.priorities.length > 0 && node.priority && !filters.priorities.includes(node.priority)) {
1984
+ return false;
1985
+ }
1986
+ if (filters.priorities.length > 0 && !node.priority) {
1987
+ return false;
1988
+ }
1989
+ if (filters.assignee && node.assignedTo !== filters.assignee) {
1990
+ return false;
1991
+ }
1992
+ if (filters.searchQuery) {
1993
+ const query = filters.searchQuery.toLowerCase();
1994
+ const titleMatch = node.title.toLowerCase().includes(query);
1995
+ const descMatch = node.description?.toLowerCase().includes(query) ?? false;
1996
+ if (!titleMatch && !descMatch) return false;
1997
+ }
1998
+ return true;
1999
+ }
2000
+ function collectMatchParentIds(tree, searchQuery) {
2001
+ if (!searchQuery) return /* @__PURE__ */ new Set();
2002
+ const ids = /* @__PURE__ */ new Set();
2003
+ const query = searchQuery.toLowerCase();
2004
+ function walk(nodes, parentIds) {
2005
+ for (const node of nodes) {
2006
+ const isLeaf = node.type === "task" || node.type === "bug" || node.type === "hotfix";
2007
+ if (isLeaf) {
2008
+ const matches = node.title.toLowerCase().includes(query) || (node.description?.toLowerCase().includes(query) ?? false);
2009
+ if (matches) {
2010
+ for (const id of parentIds) ids.add(id);
2011
+ }
2012
+ } else {
2013
+ walk(node.children ?? [], [...parentIds, node.id]);
2014
+ }
2015
+ }
2016
+ }
2017
+ walk(tree, []);
2018
+ return ids;
2019
+ }
2020
+ function extractAssignees(tree) {
2021
+ const set = /* @__PURE__ */ new Set();
2022
+ function walk(nodes) {
2023
+ for (const n of nodes) {
2024
+ if (n.assignedTo) set.add(n.assignedTo);
2025
+ if (n.children) walk(n.children);
2026
+ }
2027
+ }
2028
+ walk(tree);
2029
+ return [...set].sort();
2030
+ }
2031
+
2032
+ // src/hooks/useDebounce.ts
2033
+ import { useState as useState10, useEffect as useEffect10 } from "react";
2034
+ function useDebounce(value, delay) {
2035
+ const [debounced, setDebounced] = useState10(value);
2036
+ useEffect10(() => {
2037
+ const timer = setTimeout(() => setDebounced(value), delay);
2038
+ return () => clearTimeout(timer);
2039
+ }, [value, delay]);
2040
+ return debounced;
2041
+ }
2042
+
2043
+ // src/hooks/useConnectionStatus.ts
2044
+ import { useState as useState11, useEffect as useEffect11, useRef as useRef10, useCallback as useCallback11 } from "react";
2045
+ var DEFAULT_WS_URL = "ws://127.0.0.1:8765";
2046
+ var RECONNECT_DELAY = 3e3;
2047
+ var MAX_RETRIES = 3;
2048
+ function useConnectionStatus(wsUrl) {
2049
+ const url = wsUrl ?? DEFAULT_WS_URL;
2050
+ const [status, setStatus] = useState11("reconnecting");
2051
+ const [lastConnected, setLastConnected] = useState11(null);
2052
+ const wsRef = useRef10(null);
2053
+ const retriesRef = useRef10(0);
2054
+ const timerRef = useRef10(null);
2055
+ const mountedRef = useRef10(true);
2056
+ const pausedRef = useRef10(false);
2057
+ const cleanup = useCallback11(() => {
2058
+ if (timerRef.current) {
2059
+ clearTimeout(timerRef.current);
2060
+ timerRef.current = null;
2061
+ }
2062
+ if (wsRef.current) {
2063
+ wsRef.current.onopen = null;
2064
+ wsRef.current.onclose = null;
2065
+ wsRef.current.onerror = null;
2066
+ wsRef.current.close();
2067
+ wsRef.current = null;
2068
+ }
2069
+ }, []);
2070
+ const connect = useCallback11(() => {
2071
+ if (!mountedRef.current || pausedRef.current) return;
2072
+ cleanup();
2073
+ try {
2074
+ const ws = new WebSocket(url);
2075
+ wsRef.current = ws;
2076
+ ws.onopen = () => {
2077
+ if (!mountedRef.current) return;
2078
+ retriesRef.current = 0;
2079
+ setStatus("connected");
2080
+ setLastConnected(/* @__PURE__ */ new Date());
2081
+ };
2082
+ ws.onclose = () => {
2083
+ if (!mountedRef.current) return;
2084
+ wsRef.current = null;
2085
+ if (retriesRef.current >= MAX_RETRIES) {
2086
+ setStatus("disconnected");
2087
+ return;
2088
+ }
2089
+ retriesRef.current += 1;
2090
+ setStatus("reconnecting");
2091
+ timerRef.current = setTimeout(connect, RECONNECT_DELAY);
2092
+ };
2093
+ ws.onerror = () => {
2094
+ };
2095
+ } catch {
2096
+ if (!mountedRef.current) return;
2097
+ if (retriesRef.current >= MAX_RETRIES) {
2098
+ setStatus("disconnected");
2099
+ return;
2100
+ }
2101
+ retriesRef.current += 1;
2102
+ setStatus("reconnecting");
2103
+ timerRef.current = setTimeout(connect, RECONNECT_DELAY);
2104
+ }
2105
+ }, [url, cleanup]);
2106
+ useEffect11(() => {
2107
+ mountedRef.current = true;
2108
+ connect();
2109
+ const handleVisibility = () => {
2110
+ if (document.hidden) {
2111
+ pausedRef.current = true;
2112
+ cleanup();
2113
+ } else {
2114
+ pausedRef.current = false;
2115
+ retriesRef.current = 0;
2116
+ setStatus("reconnecting");
2117
+ connect();
2118
+ }
2119
+ };
2120
+ document.addEventListener("visibilitychange", handleVisibility);
2121
+ return () => {
2122
+ mountedRef.current = false;
2123
+ document.removeEventListener("visibilitychange", handleVisibility);
2124
+ cleanup();
2125
+ };
2126
+ }, [connect, cleanup]);
2127
+ return { status, lastConnected };
2128
+ }
2129
+
2130
+ // src/hooks/useOptimisticTaskActions.ts
2131
+ import { useState as useState12, useCallback as useCallback12, useRef as useRef11 } from "react";
2132
+ var ADVANCE_MAP = {
2133
+ "in-progress": "ready-for-testing",
2134
+ "ready-for-testing": "in-testing",
2135
+ "in-testing": "ready-for-docs",
2136
+ "ready-for-docs": "in-docs",
2137
+ "in-docs": "documented",
2138
+ "documented": "in-review",
2139
+ "in-review": "done"
2140
+ };
2141
+ function predictStatus(action, currentStatus) {
2142
+ switch (action) {
2143
+ case "start":
2144
+ return "in-progress";
2145
+ case "advance":
2146
+ return ADVANCE_MAP[currentStatus] ?? null;
2147
+ case "block":
2148
+ return "blocked";
2149
+ case "complete":
2150
+ return "done";
2151
+ default:
2152
+ return null;
2153
+ }
2154
+ }
2155
+ function useOptimisticTaskActions(options = {}) {
2156
+ const [pendingTasks, setPendingTasks] = useState12(/* @__PURE__ */ new Set());
2157
+ const optionsRef = useRef11(options);
2158
+ optionsRef.current = options;
2159
+ const addPending = useCallback12((taskId) => {
2160
+ setPendingTasks((prev) => {
2161
+ const next = new Set(prev);
2162
+ next.add(taskId);
2163
+ return next;
2164
+ });
2165
+ }, []);
2166
+ const removePending = useCallback12((taskId) => {
2167
+ setPendingTasks((prev) => {
2168
+ const next = new Set(prev);
2169
+ next.delete(taskId);
2170
+ return next;
2171
+ });
2172
+ }, []);
2173
+ const runOptimistic = useCallback12(
2174
+ async (action, currentStatus, loc, mcpFn) => {
2175
+ const taskId = loc.task_id;
2176
+ const predicted = predictStatus(action, currentStatus);
2177
+ if (predicted) {
2178
+ addPending(taskId);
2179
+ optionsRef.current.onOptimisticUpdate?.(taskId, predicted);
2180
+ }
2181
+ try {
2182
+ await mcpFn();
2183
+ optionsRef.current.onSettled?.(taskId);
2184
+ } catch {
2185
+ if (predicted) {
2186
+ optionsRef.current.onRevert?.(taskId, currentStatus);
2187
+ }
2188
+ optionsRef.current.onSettled?.(taskId);
2189
+ } finally {
2190
+ removePending(taskId);
2191
+ }
2192
+ },
2193
+ [addPending, removePending]
2194
+ );
2195
+ const startTask = useCallback12(
2196
+ (loc, currentStatus) => runOptimistic(
2197
+ "start",
2198
+ currentStatus,
2199
+ loc,
2200
+ () => mcpCall("set_current_task", loc)
2201
+ ),
2202
+ [runOptimistic]
2203
+ );
2204
+ const advanceTask = useCallback12(
2205
+ (loc, currentStatus, evidence) => runOptimistic(
2206
+ "advance",
2207
+ currentStatus,
2208
+ loc,
2209
+ () => mcpCall("advance_task", { ...loc, evidence })
2210
+ ),
2211
+ [runOptimistic]
2212
+ );
2213
+ const blockTask = useCallback12(
2214
+ (loc, currentStatus) => runOptimistic(
2215
+ "block",
2216
+ currentStatus,
2217
+ loc,
2218
+ () => mcpCall("update_task", { ...loc, status: "blocked" })
2219
+ ),
2220
+ [runOptimistic]
2221
+ );
2222
+ const completeTask = useCallback12(
2223
+ (loc, currentStatus) => runOptimistic(
2224
+ "complete",
2225
+ currentStatus,
2226
+ loc,
2227
+ () => mcpCall("complete_task", loc)
2228
+ ),
2229
+ [runOptimistic]
2230
+ );
2231
+ return {
2232
+ startTask,
2233
+ advanceTask,
2234
+ blockTask,
2235
+ completeTask,
2236
+ pendingTasks
2237
+ };
2238
+ }
2239
+
2240
+ // src/LabelsSection/LabelsSection.tsx
2241
+ import { useState as useState13, useCallback as useCallback13, useRef as useRef12 } from "react";
2242
+ import { jsx as jsx15, jsxs as jsxs13 } from "react/jsx-runtime";
2243
+ function LabelsSection({ labels, project, epicId, storyId, taskId, onUpdate }) {
2244
+ const [adding, setAdding] = useState13(false);
2245
+ const [removingLabel, setRemovingLabel] = useState13(null);
2246
+ const inputRef = useRef12(null);
2247
+ const removeLabel = useCallback13(async (label) => {
2248
+ setRemovingLabel(label);
2249
+ try {
2250
+ await mcpCall("remove_labels", {
2251
+ project,
2252
+ epic_id: epicId,
2253
+ story_id: storyId,
2254
+ task_id: taskId,
2255
+ labels: [label]
2256
+ });
2257
+ onUpdate?.();
2258
+ } catch {
2259
+ }
2260
+ setRemovingLabel(null);
2261
+ }, [project, epicId, storyId, taskId, onUpdate]);
2262
+ const addLabel = useCallback13(async (value) => {
2263
+ const trimmed = value.trim();
2264
+ if (!trimmed) {
2265
+ setAdding(false);
2266
+ return;
2267
+ }
2268
+ try {
2269
+ await mcpCall("add_labels", {
2270
+ project,
2271
+ epic_id: epicId,
2272
+ story_id: storyId,
2273
+ task_id: taskId,
2274
+ labels: [trimmed]
2275
+ });
2276
+ onUpdate?.();
2277
+ } catch {
2278
+ }
2279
+ setAdding(false);
2280
+ }, [project, epicId, storyId, taskId, onUpdate]);
2281
+ const handleKeyDown = useCallback13((e) => {
2282
+ if (e.key === "Enter") {
2283
+ e.preventDefault();
2284
+ addLabel(e.currentTarget.value);
2285
+ } else if (e.key === "Escape") {
2286
+ setAdding(false);
2287
+ }
2288
+ }, [addLabel]);
2289
+ const handleBlur = useCallback13((e) => {
2290
+ const value = e.currentTarget.value.trim();
2291
+ if (value) {
2292
+ addLabel(value);
2293
+ } else {
2294
+ setAdding(false);
2295
+ }
2296
+ }, [addLabel]);
2297
+ const handleAddClick = useCallback13(() => {
2298
+ setAdding(true);
2299
+ setTimeout(() => inputRef.current?.focus(), 0);
2300
+ }, []);
2301
+ return /* @__PURE__ */ jsxs13("section", { className: "labels-section", children: [
2302
+ /* @__PURE__ */ jsx15("h3", { className: "task-detail__section-title", children: "Labels" }),
2303
+ /* @__PURE__ */ jsxs13("div", { className: "labels-section__list", children: [
2304
+ labels.length === 0 && !adding && /* @__PURE__ */ jsxs13("span", { className: "labels-section__empty", children: [
2305
+ "No labels",
2306
+ /* @__PURE__ */ jsx15(
2307
+ "button",
2308
+ {
2309
+ className: "labels-section__add",
2310
+ onClick: handleAddClick,
2311
+ "aria-label": "Add label",
2312
+ children: "+"
2313
+ }
2314
+ )
2315
+ ] }),
2316
+ labels.map((label) => /* @__PURE__ */ jsxs13(
2317
+ "span",
2318
+ {
2319
+ className: `labels-section__pill${removingLabel === label ? " labels-section__pill--removing" : ""}`,
2320
+ children: [
2321
+ label,
2322
+ /* @__PURE__ */ jsx15(
2323
+ "button",
2324
+ {
2325
+ className: "labels-section__remove",
2326
+ onClick: () => removeLabel(label),
2327
+ "aria-label": `Remove ${label}`,
2328
+ children: "\xD7"
2329
+ }
2330
+ )
2331
+ ]
2332
+ },
2333
+ label
2334
+ )),
2335
+ labels.length > 0 && !adding && /* @__PURE__ */ jsx15(
2336
+ "button",
2337
+ {
2338
+ className: "labels-section__add",
2339
+ onClick: handleAddClick,
2340
+ "aria-label": "Add label",
2341
+ children: "+"
2342
+ }
2343
+ ),
2344
+ adding && /* @__PURE__ */ jsx15(
2345
+ "input",
2346
+ {
2347
+ ref: inputRef,
2348
+ className: "labels-section__input",
2349
+ type: "text",
2350
+ placeholder: "Label...",
2351
+ onKeyDown: handleKeyDown,
2352
+ onBlur: handleBlur,
2353
+ autoFocus: true
2354
+ }
2355
+ )
2356
+ ] })
2357
+ ] });
2358
+ }
2359
+
2360
+ // src/LinksSection/LinksSection.tsx
2361
+ import { useState as useState14, useCallback as useCallback14 } from "react";
2362
+ import { jsx as jsx16, jsxs as jsxs14 } from "react/jsx-runtime";
2363
+ var TYPE_ICONS3 = {
2364
+ pr: "PR",
2365
+ commit: "C",
2366
+ issue: "#"
2367
+ };
2368
+ function typeIcon(type) {
2369
+ return TYPE_ICONS3[type] ?? "\u{1F517}";
2370
+ }
2371
+ var LINK_TYPES = ["pr", "commit", "url", "issue"];
2372
+ function LinksSection({ links, project, epicId, storyId, taskId, onUpdate }) {
2373
+ const [adding, setAdding] = useState14(false);
2374
+ const [type, setType] = useState14("url");
2375
+ const [url, setUrl] = useState14("");
2376
+ const [title, setTitle] = useState14("");
2377
+ const resetForm = useCallback14(() => {
2378
+ setAdding(false);
2379
+ setType("url");
2380
+ setUrl("");
2381
+ setTitle("");
2382
+ }, []);
2383
+ const handleSave = useCallback14(async () => {
2384
+ const trimmedUrl = url.trim();
2385
+ if (!trimmedUrl) return;
2386
+ try {
2387
+ await mcpCall("add_link", {
2388
+ project,
2389
+ epic_id: epicId,
2390
+ story_id: storyId,
2391
+ task_id: taskId,
2392
+ type,
2393
+ url: trimmedUrl,
2394
+ title: title.trim() || void 0
2395
+ });
2396
+ onUpdate?.();
2397
+ } catch {
2398
+ }
2399
+ resetForm();
2400
+ }, [project, epicId, storyId, taskId, type, url, title, onUpdate, resetForm]);
2401
+ const handleKeyDown = useCallback14((e) => {
2402
+ if (e.key === "Escape") {
2403
+ resetForm();
2404
+ }
2405
+ }, [resetForm]);
2406
+ return /* @__PURE__ */ jsxs14("section", { className: "links-section", children: [
2407
+ /* @__PURE__ */ jsx16("h3", { className: "task-detail__section-title", children: "Links" }),
2408
+ links.length === 0 && !adding && /* @__PURE__ */ jsxs14("span", { className: "links-section__empty", children: [
2409
+ "No links",
2410
+ /* @__PURE__ */ jsx16(
2411
+ "button",
2412
+ {
2413
+ className: "links-section__add",
2414
+ onClick: () => setAdding(true),
2415
+ "aria-label": "Add link",
2416
+ children: "+"
2417
+ }
2418
+ )
2419
+ ] }),
2420
+ links.length > 0 && /* @__PURE__ */ jsx16("ul", { className: "links-section__list", children: links.map((link, i) => /* @__PURE__ */ jsxs14("li", { className: "links-section__item", children: [
2421
+ /* @__PURE__ */ jsx16("span", { className: "links-section__type-badge", children: typeIcon(link.type) }),
2422
+ /* @__PURE__ */ jsx16(
2423
+ "a",
2424
+ {
2425
+ href: link.url,
2426
+ target: "_blank",
2427
+ rel: "noopener noreferrer",
2428
+ className: "links-section__link",
2429
+ children: link.title || link.url
2430
+ }
2431
+ )
2432
+ ] }, i)) }),
2433
+ adding ? /* @__PURE__ */ jsxs14("div", { className: "links-section__form", onKeyDown: handleKeyDown, children: [
2434
+ /* @__PURE__ */ jsx16(
2435
+ "select",
2436
+ {
2437
+ value: type,
2438
+ onChange: (e) => setType(e.target.value),
2439
+ "aria-label": "Link type",
2440
+ children: LINK_TYPES.map((t) => /* @__PURE__ */ jsx16("option", { value: t, children: t }, t))
2441
+ }
2442
+ ),
2443
+ /* @__PURE__ */ jsx16(
2444
+ "input",
2445
+ {
2446
+ placeholder: "URL",
2447
+ value: url,
2448
+ onChange: (e) => setUrl(e.target.value),
2449
+ autoFocus: true
2450
+ }
2451
+ ),
2452
+ /* @__PURE__ */ jsx16(
2453
+ "input",
2454
+ {
2455
+ placeholder: "Title (optional)",
2456
+ value: title,
2457
+ onChange: (e) => setTitle(e.target.value)
2458
+ }
2459
+ ),
2460
+ /* @__PURE__ */ jsx16("button", { className: "links-section__save", onClick: handleSave, children: "Save" }),
2461
+ /* @__PURE__ */ jsx16("button", { className: "links-section__cancel", onClick: resetForm, children: "Cancel" })
2462
+ ] }) : links.length > 0 ? /* @__PURE__ */ jsx16(
2463
+ "button",
2464
+ {
2465
+ className: "links-section__add",
2466
+ onClick: () => setAdding(true),
2467
+ "aria-label": "Add link",
2468
+ children: "+ Add link"
2469
+ }
2470
+ ) : null
2471
+ ] });
2472
+ }
2473
+
2474
+ // src/TaskDetailPanel/StatusActions.tsx
2475
+ import { useCallback as useCallback15 } from "react";
2476
+ import { jsx as jsx17, jsxs as jsxs15 } from "react/jsx-runtime";
2477
+ var TERMINAL = /* @__PURE__ */ new Set(["done", "cancelled", "rejected"]);
2478
+ var STARTABLE = /* @__PURE__ */ new Set(["backlog", "todo", "blocked"]);
2479
+ var IN_PROGRESS = "in-progress";
2480
+ function StatusActions({ status, project, epicId, storyId, taskId, onAction }) {
2481
+ const {
2482
+ startTask,
2483
+ advanceTask,
2484
+ blockTask,
2485
+ completeTask,
2486
+ starting,
2487
+ advancing,
2488
+ blocking,
2489
+ completing
2490
+ } = useTaskActions();
2491
+ const loc = {
2492
+ project,
2493
+ epic_id: epicId,
2494
+ story_id: storyId,
2495
+ task_id: taskId
2496
+ };
2497
+ const handleStart = useCallback15(async () => {
2498
+ await startTask(loc);
2499
+ onAction?.();
2500
+ }, [startTask, loc, onAction]);
2501
+ const handleAdvance = useCallback15(async () => {
2502
+ await advanceTask(loc, void 0);
2503
+ onAction?.();
2504
+ }, [advanceTask, loc, onAction]);
2505
+ const handleBlock = useCallback15(async () => {
2506
+ await blockTask(loc);
2507
+ onAction?.();
2508
+ }, [blockTask, loc, onAction]);
2509
+ const handleComplete = useCallback15(async () => {
2510
+ await completeTask(loc);
2511
+ onAction?.();
2512
+ }, [completeTask, loc, onAction]);
2513
+ const isTerminal = TERMINAL.has(status);
2514
+ if (isTerminal) return null;
2515
+ const showStart = STARTABLE.has(status);
2516
+ const showAdvance = status === IN_PROGRESS || !TERMINAL.has(status) && !STARTABLE.has(status);
2517
+ const showBlock = status === IN_PROGRESS;
2518
+ const showComplete = status === IN_PROGRESS;
2519
+ return /* @__PURE__ */ jsxs15("div", { className: "status-actions", children: [
2520
+ showStart && /* @__PURE__ */ jsxs15(
2521
+ "button",
2522
+ {
2523
+ type: "button",
2524
+ "aria-label": "Start task",
2525
+ className: `status-actions__btn status-actions__btn--start${starting.loading ? " status-actions__btn--loading" : ""}`,
2526
+ onClick: handleStart,
2527
+ disabled: starting.loading,
2528
+ children: [
2529
+ starting.loading ? /* @__PURE__ */ jsx17("span", { className: "status-actions__spinner" }) : "\u25B6",
2530
+ "Start"
2531
+ ]
2532
+ }
2533
+ ),
2534
+ showAdvance && /* @__PURE__ */ jsxs15(
2535
+ "button",
2536
+ {
2537
+ type: "button",
2538
+ "aria-label": "Advance task",
2539
+ className: `status-actions__btn status-actions__btn--advance${advancing.loading ? " status-actions__btn--loading" : ""}`,
2540
+ onClick: handleAdvance,
2541
+ disabled: advancing.loading,
2542
+ children: [
2543
+ advancing.loading ? /* @__PURE__ */ jsx17("span", { className: "status-actions__spinner" }) : "\u23E9",
2544
+ "Advance"
2545
+ ]
2546
+ }
2547
+ ),
2548
+ showBlock && /* @__PURE__ */ jsxs15(
2549
+ "button",
2550
+ {
2551
+ type: "button",
2552
+ "aria-label": "Block task",
2553
+ className: `status-actions__btn status-actions__btn--block${blocking.loading ? " status-actions__btn--loading" : ""}`,
2554
+ onClick: handleBlock,
2555
+ disabled: blocking.loading,
2556
+ children: [
2557
+ blocking.loading ? /* @__PURE__ */ jsx17("span", { className: "status-actions__spinner" }) : "\u26D4",
2558
+ "Block"
2559
+ ]
2560
+ }
2561
+ ),
2562
+ showComplete && /* @__PURE__ */ jsxs15(
2563
+ "button",
2564
+ {
2565
+ type: "button",
2566
+ "aria-label": "Complete task",
2567
+ className: `status-actions__btn status-actions__btn--complete${completing.loading ? " status-actions__btn--loading" : ""}`,
2568
+ onClick: handleComplete,
2569
+ disabled: completing.loading,
2570
+ children: [
2571
+ completing.loading ? /* @__PURE__ */ jsx17("span", { className: "status-actions__spinner" }) : "\u2714",
2572
+ "Complete"
2573
+ ]
2574
+ }
2575
+ )
2576
+ ] });
2577
+ }
2578
+
2579
+ // src/EvidenceLog/EvidenceLog.tsx
2580
+ import { useState as useState15, useCallback as useCallback16 } from "react";
2581
+ import { jsx as jsx18, jsxs as jsxs16 } from "react/jsx-runtime";
2582
+ var GATE_LABELS = {
2583
+ testing: "Testing",
2584
+ verification: "Verification",
2585
+ documentation: "Documentation",
2586
+ review: "Review"
2587
+ };
2588
+ var GATE_ICONS = {
2589
+ testing: "\u{1F9EA}",
2590
+ verification: "\u2705",
2591
+ documentation: "\u{1F4DD}",
2592
+ review: "\u{1F50D}"
2593
+ };
2594
+ var TRUNCATE_LENGTH = 120;
2595
+ function EvidenceLog({ evidence }) {
2596
+ const [expanded, setExpanded] = useState15(() => /* @__PURE__ */ new Set());
2597
+ const toggle = useCallback16((index) => {
2598
+ setExpanded((prev) => {
2599
+ const next = new Set(prev);
2600
+ if (next.has(index)) {
2601
+ next.delete(index);
2602
+ } else {
2603
+ next.add(index);
2604
+ }
2605
+ return next;
2606
+ });
2607
+ }, []);
2608
+ if (evidence.length === 0) return null;
2609
+ return /* @__PURE__ */ jsxs16("section", { className: "evidence-log", children: [
2610
+ /* @__PURE__ */ jsx18("h3", { className: "task-detail__section-title", children: "Evidence Log" }),
2611
+ /* @__PURE__ */ jsx18("div", { className: "evidence-log__timeline", children: evidence.map((entry, i) => {
2612
+ const isExpanded = expanded.has(i);
2613
+ const text = entry.description;
2614
+ const isLong = text.length > TRUNCATE_LENGTH;
2615
+ return /* @__PURE__ */ jsxs16("div", { className: "evidence-log__entry", children: [
2616
+ /* @__PURE__ */ jsx18("div", { className: "evidence-log__marker" }),
2617
+ /* @__PURE__ */ jsxs16("div", { className: "evidence-log__content", children: [
2618
+ /* @__PURE__ */ jsxs16("div", { className: "evidence-log__header", children: [
2619
+ /* @__PURE__ */ jsx18("span", { className: "evidence-log__icon", children: GATE_ICONS[entry.gate] ?? "\u{1F4CB}" }),
2620
+ /* @__PURE__ */ jsx18("span", { className: "evidence-log__gate", children: GATE_LABELS[entry.gate] ?? entry.gate }),
2621
+ /* @__PURE__ */ jsx18("span", { className: "evidence-log__time", children: new Date(entry.timestamp).toLocaleDateString() })
2622
+ ] }),
2623
+ /* @__PURE__ */ jsx18("p", { className: "evidence-log__text", children: isExpanded || !isLong ? text : text.slice(0, TRUNCATE_LENGTH) + "..." }),
2624
+ isLong && /* @__PURE__ */ jsx18(
2625
+ "button",
2626
+ {
2627
+ className: "evidence-log__toggle",
2628
+ onClick: () => toggle(i),
2629
+ children: isExpanded ? "Show less" : "Show more"
2630
+ }
2631
+ )
2632
+ ] })
2633
+ ] }, i);
2634
+ }) })
2635
+ ] });
2636
+ }
2637
+
2638
+ // src/StaleNotification/StaleNotification.tsx
2639
+ import { jsx as jsx19, jsxs as jsxs17 } from "react/jsx-runtime";
2640
+ var StaleNotification = ({
2641
+ onRefresh,
2642
+ onDismiss
2643
+ }) => {
2644
+ return /* @__PURE__ */ jsxs17("div", { className: "stale-notification", role: "status", "data-testid": "stale-notification", children: [
2645
+ /* @__PURE__ */ jsx19("span", { className: "stale-notification__text", children: "Task updated externally" }),
2646
+ /* @__PURE__ */ jsx19(
2647
+ "button",
2648
+ {
2649
+ type: "button",
2650
+ className: "stale-notification__btn stale-notification__btn--refresh",
2651
+ onClick: onRefresh,
2652
+ children: "Refresh"
2653
+ }
2654
+ ),
2655
+ /* @__PURE__ */ jsx19(
2656
+ "button",
2657
+ {
2658
+ type: "button",
2659
+ className: "stale-notification__btn stale-notification__btn--dismiss",
2660
+ onClick: onDismiss,
2661
+ "aria-label": "Dismiss",
2662
+ children: "\u2715"
2663
+ }
2664
+ )
2665
+ ] });
2666
+ };
2667
+
2668
+ // src/TaskDetailPanel/TaskDetailPanel.tsx
2669
+ import { Fragment as Fragment3, jsx as jsx20, jsxs as jsxs18 } from "react/jsx-runtime";
2670
+ var PRIORITY_OPTIONS = [
2671
+ { value: "critical", label: "Critical" },
2672
+ { value: "high", label: "High" },
2673
+ { value: "medium", label: "Medium" },
2674
+ { value: "low", label: "Low" }
2675
+ ];
2676
+ function TaskDetailPanel({ isOpen, onClose, params }) {
2677
+ const {
2678
+ task,
2679
+ loading,
2680
+ error,
2681
+ refresh,
2682
+ isStale,
2683
+ acceptStale,
2684
+ dismissStale
2685
+ } = useTaskDetail(isOpen ? params : null);
2686
+ const [savingField, setSavingField] = useState16(null);
2687
+ const [isEditingDescription, setIsEditingDescription] = useState16(false);
2688
+ const saveField = useCallback17(async (field, value) => {
2689
+ if (!params) return;
2690
+ setSavingField(field);
2691
+ try {
2692
+ await mcpCall("update_task", {
2693
+ project: params.project,
2694
+ epic_id: params.epicId,
2695
+ story_id: params.storyId,
2696
+ task_id: params.taskId,
2697
+ [field]: value
2698
+ });
2699
+ await refresh();
2700
+ } catch {
2701
+ }
2702
+ setSavingField(null);
2703
+ }, [params, refresh]);
2704
+ const fields = useMemo4(() => {
2705
+ if (!task) return [];
2706
+ const result = [];
2707
+ result.push({ label: "Status", value: task.status });
2708
+ result.push({ label: "Type", value: task.type });
2709
+ if (task.estimate !== void 0) result.push({ label: "Estimate", value: `${task.estimate} pts` });
2710
+ return result;
2711
+ }, [task]);
2712
+ return /* @__PURE__ */ jsxs18(
2713
+ DetailPanel,
2714
+ {
2715
+ isOpen,
2716
+ onClose,
2717
+ title: task?.title ?? "Loading...",
2718
+ subtitle: task?.id,
2719
+ fields,
2720
+ children: [
2721
+ loading && /* @__PURE__ */ jsx20("div", { className: "task-detail__loading", children: "Loading task..." }),
2722
+ error && /* @__PURE__ */ jsx20("div", { className: "task-detail__error", children: error }),
2723
+ task && params && /* @__PURE__ */ jsxs18(Fragment3, { children: [
2724
+ isStale && /* @__PURE__ */ jsx20(StaleNotification, { onRefresh: acceptStale, onDismiss: dismissStale }),
2725
+ /* @__PURE__ */ jsxs18("div", { className: "task-detail__status-row", children: [
2726
+ /* @__PURE__ */ jsx20(StatusBadge, { status: task.status, variant: "pill", size: "md" }),
2727
+ task.priority && /* @__PURE__ */ jsx20(PriorityIcon, { priority: task.priority, size: 16 })
2728
+ ] }),
2729
+ /* @__PURE__ */ jsx20(
2730
+ StatusActions,
2731
+ {
2732
+ status: task.status,
2733
+ project: params.project,
2734
+ epicId: params.epicId,
2735
+ storyId: params.storyId,
2736
+ taskId: params.taskId,
2737
+ onAction: refresh
2738
+ }
2739
+ ),
2740
+ /* @__PURE__ */ jsxs18("section", { className: "task-detail__section", children: [
2741
+ /* @__PURE__ */ jsx20("h3", { className: "task-detail__section-title", children: "Title" }),
2742
+ /* @__PURE__ */ jsx20(
2743
+ EditableText,
2744
+ {
2745
+ value: task.title,
2746
+ onSave: (v) => saveField("title", v),
2747
+ saving: savingField === "title",
2748
+ tag: "h2",
2749
+ className: "task-detail__editable-title"
2750
+ }
2751
+ )
2752
+ ] }),
2753
+ /* @__PURE__ */ jsxs18("section", { className: "task-detail__section", children: [
2754
+ /* @__PURE__ */ jsxs18("div", { className: "task-detail__section-header", children: [
2755
+ /* @__PURE__ */ jsx20("h3", { className: "task-detail__section-title", children: "Description" }),
2756
+ /* @__PURE__ */ jsx20(
2757
+ "button",
2758
+ {
2759
+ type: "button",
2760
+ className: "task-detail__edit-btn",
2761
+ onClick: () => setIsEditingDescription(!isEditingDescription),
2762
+ children: isEditingDescription ? "View" : "Edit"
2763
+ }
2764
+ )
2765
+ ] }),
2766
+ isEditingDescription ? /* @__PURE__ */ jsx20(
2767
+ EditableTextarea,
2768
+ {
2769
+ value: task.description ?? "",
2770
+ onSave: async (v) => {
2771
+ await saveField("description", v);
2772
+ setIsEditingDescription(false);
2773
+ },
2774
+ saving: savingField === "description",
2775
+ placeholder: "Click to add description..."
2776
+ }
2777
+ ) : /* @__PURE__ */ jsx20("div", { className: "task-detail__markdown", children: task.description ? /* @__PURE__ */ jsx20(MarkdownRenderer, { content: task.description }) : /* @__PURE__ */ jsx20("p", { className: "task-detail__empty", onClick: () => setIsEditingDescription(true), children: "Click edit to add description..." }) })
2778
+ ] }),
2779
+ /* @__PURE__ */ jsxs18("section", { className: "task-detail__section", children: [
2780
+ /* @__PURE__ */ jsx20("h3", { className: "task-detail__section-title", children: "Priority" }),
2781
+ /* @__PURE__ */ jsx20(
2782
+ EditableSelect,
2783
+ {
2784
+ value: task.priority ?? "",
2785
+ options: PRIORITY_OPTIONS,
2786
+ onSave: (v) => saveField("priority", v),
2787
+ saving: savingField === "priority",
2788
+ placeholder: "Select priority"
2789
+ }
2790
+ )
2791
+ ] }),
2792
+ /* @__PURE__ */ jsxs18("section", { className: "task-detail__section", children: [
2793
+ /* @__PURE__ */ jsx20("h3", { className: "task-detail__section-title", children: "Assignee" }),
2794
+ /* @__PURE__ */ jsx20(
2795
+ EditableText,
2796
+ {
2797
+ value: task.assigned_to ?? "",
2798
+ onSave: (v) => saveField("assigned_to", v),
2799
+ saving: savingField === "assigned_to"
2800
+ }
2801
+ )
2802
+ ] }),
2803
+ /* @__PURE__ */ jsx20(
2804
+ LabelsSection,
2805
+ {
2806
+ labels: task.labels ?? [],
2807
+ project: params.project,
2808
+ epicId: params.epicId,
2809
+ storyId: params.storyId,
2810
+ taskId: params.taskId,
2811
+ onUpdate: refresh
2812
+ }
2813
+ ),
2814
+ task.depends_on && task.depends_on.length > 0 && /* @__PURE__ */ jsxs18("section", { className: "task-detail__section", children: [
2815
+ /* @__PURE__ */ jsx20("h3", { className: "task-detail__section-title", children: "Dependencies" }),
2816
+ /* @__PURE__ */ jsx20("ul", { className: "task-detail__deps", children: task.depends_on.map((dep) => /* @__PURE__ */ jsx20("li", { className: "task-detail__dep", children: dep }, dep)) })
2817
+ ] }),
2818
+ /* @__PURE__ */ jsx20(
2819
+ LinksSection,
2820
+ {
2821
+ links: task.links ?? [],
2822
+ project: params.project,
2823
+ epicId: params.epicId,
2824
+ storyId: params.storyId,
2825
+ taskId: params.taskId,
2826
+ onUpdate: refresh
2827
+ }
2828
+ ),
2829
+ (task.created_at || task.updated_at) && /* @__PURE__ */ jsxs18("section", { className: "task-detail__section task-detail__timestamps", children: [
2830
+ task.created_at && /* @__PURE__ */ jsxs18("span", { className: "task-detail__timestamp", children: [
2831
+ "Created: ",
2832
+ new Date(task.created_at).toLocaleDateString()
2833
+ ] }),
2834
+ task.updated_at && /* @__PURE__ */ jsxs18("span", { className: "task-detail__timestamp", children: [
2835
+ "Updated: ",
2836
+ new Date(task.updated_at).toLocaleDateString()
2837
+ ] })
2838
+ ] }),
2839
+ task.evidence && task.evidence.length > 0 && /* @__PURE__ */ jsx20(EvidenceLog, { evidence: task.evidence })
2840
+ ] })
2841
+ ]
2842
+ }
2843
+ );
2844
+ }
2845
+
2846
+ // src/widgets/BacklogTreeWidget.tsx
2847
+ import { useState as useState17, useMemo as useMemo5, useCallback as useCallback18 } from "react";
2848
+ import { Widget } from "@orchestra-mcp/widgets";
2849
+ import { BoxIcon as BoxIcon6 } from "@orchestra-mcp/icons";
2850
+ import { ContextMenu } from "@orchestra-mcp/ui";
2851
+
2852
+ // src/widgets/BacklogTreeHeader.tsx
2853
+ import { jsx as jsx21, jsxs as jsxs19 } from "react/jsx-runtime";
2854
+ var FILTERS = [
2855
+ { key: "all", label: "All" },
2856
+ { key: "todo", label: "To Do" },
2857
+ { key: "in-progress", label: "In Progress" },
2858
+ { key: "blocked", label: "Blocked" },
2859
+ { key: "done", label: "Done" }
2860
+ ];
2861
+ var BacklogTreeHeader = ({
2862
+ totalTasks,
2863
+ doneTasks,
2864
+ searchQuery,
2865
+ onSearchChange,
2866
+ activeFilter,
2867
+ onFilterChange
2868
+ }) => {
2869
+ return /* @__PURE__ */ jsxs19("div", { className: "backlog-header", "data-testid": "backlog-tree-header", children: [
2870
+ /* @__PURE__ */ jsxs19("div", { className: "backlog-header__top", children: [
2871
+ /* @__PURE__ */ jsx21(
2872
+ "input",
2873
+ {
2874
+ type: "text",
2875
+ className: "backlog-header__search",
2876
+ placeholder: "Search backlog...",
2877
+ value: searchQuery,
2878
+ onChange: (e) => onSearchChange(e.target.value),
2879
+ "data-testid": "backlog-search"
2880
+ }
2881
+ ),
2882
+ /* @__PURE__ */ jsxs19("span", { className: "backlog-header__count", children: [
2883
+ doneTasks,
2884
+ "/",
2885
+ totalTasks
2886
+ ] })
2887
+ ] }),
2888
+ /* @__PURE__ */ jsx21("div", { className: "backlog-header__filters", children: FILTERS.map((f) => /* @__PURE__ */ jsx21(
2889
+ "button",
2890
+ {
2891
+ type: "button",
2892
+ className: `backlog-header__filter${activeFilter === f.key ? " backlog-header__filter--active" : ""}`,
2893
+ onClick: () => onFilterChange(f.key),
2894
+ "data-testid": `backlog-filter-${f.key}`,
2895
+ children: f.label
2896
+ },
2897
+ f.key
2898
+ )) })
2899
+ ] });
2900
+ };
2901
+
2902
+ // src/widgets/BacklogTreeWidget.tsx
2903
+ import { Fragment as Fragment4, jsx as jsx22, jsxs as jsxs20 } from "react/jsx-runtime";
2904
+ var TYPE_ICON_NAMES = {
2905
+ task: "bx-checkbox-checked",
2906
+ bug: "bx-bug",
2907
+ hotfix: "bx-badge-check",
2908
+ story: "bx-book-bookmark",
2909
+ epic: "bx-layer"
2910
+ };
2911
+ var PRIORITY_COLORS2 = {
2912
+ low: "#22c55e",
2913
+ medium: "#eab308",
2914
+ high: "#f97316",
2915
+ critical: "#ef4444"
2916
+ };
2917
+ var STATUS_FILTER_MAP = {
2918
+ all: [],
2919
+ todo: ["backlog", "todo"],
2920
+ "in-progress": ["in-progress", "in-testing", "in-docs", "in-review"],
2921
+ blocked: ["blocked"],
2922
+ done: ["done", "documented", "cancelled"]
2923
+ };
2924
+ function nodeMatches(node, query, statusFilter) {
2925
+ const titleMatch = !query || node.title.toLowerCase().includes(query.toLowerCase());
2926
+ const statusMatch = statusFilter.length === 0 || statusFilter.includes(node.status);
2927
+ if (titleMatch && statusMatch) return true;
2928
+ if (node.children?.some((c) => nodeMatches(c, query, statusFilter))) return true;
2929
+ return false;
2930
+ }
2931
+ function countTasks(node) {
2932
+ if (!node.children || node.children.length === 0) {
2933
+ return { total: 1, done: node.status === "done" ? 1 : 0 };
2934
+ }
2935
+ let total = 0;
2936
+ let done = 0;
2937
+ for (const child of node.children) {
2938
+ const c = countTasks(child);
2939
+ total += c.total;
2940
+ done += c.done;
2941
+ }
2942
+ return { total, done };
2943
+ }
2944
+ function sumEstimate(node) {
2945
+ if (node.estimate) return node.estimate;
2946
+ if (!node.children) return 0;
2947
+ return node.children.reduce((sum, c) => sum + sumEstimate(c), 0);
2948
+ }
2949
+ var LABEL_COLORS = [
2950
+ "#ef4444",
2951
+ "#f97316",
2952
+ "#eab308",
2953
+ "#22c55e",
2954
+ "#06b6d4",
2955
+ "#3b82f6",
2956
+ "#8b5cf6",
2957
+ "#ec4899",
2958
+ "#14b8a6",
2959
+ "#f59e0b"
2960
+ ];
2961
+ function labelColor(label) {
2962
+ let hash = 0;
2963
+ for (let i = 0; i < label.length; i++) hash = hash * 31 + label.charCodeAt(i) | 0;
2964
+ return LABEL_COLORS[Math.abs(hash) % LABEL_COLORS.length];
2965
+ }
2966
+ var TreeNodeRow = ({
2967
+ node,
2968
+ depth,
2969
+ expandedNodes,
2970
+ selectedNode,
2971
+ onNodeClick,
2972
+ onNodeExpand,
2973
+ onNodeAction
2974
+ }) => {
2975
+ const hasChildren = node.children && node.children.length > 0;
2976
+ const isExpanded = expandedNodes.has(node.id);
2977
+ const isSelected = selectedNode === node.id;
2978
+ const handleClick = (e) => {
2979
+ if (node.type === "task" || node.type === "bug" || node.type === "hotfix") {
2980
+ onNodeClick?.(node);
2981
+ return;
2982
+ }
2983
+ if (hasChildren) {
2984
+ onNodeExpand?.(node.id);
2985
+ }
2986
+ onNodeClick?.(node);
2987
+ };
2988
+ const contextMenuItems = useMemo5(() => {
2989
+ const items = [];
2990
+ items.push({
2991
+ id: "view",
2992
+ label: "View Details",
2993
+ icon: /* @__PURE__ */ jsx22(BoxIcon6, { name: "bx-show", size: 14 })
2994
+ });
2995
+ if (node.type === "task" || node.type === "bug" || node.type === "hotfix") {
2996
+ items.push({ id: "sep-1", label: "", separator: true });
2997
+ if (node.status === "backlog" || node.status === "todo") {
2998
+ items.push({
2999
+ id: "start",
3000
+ label: "Start Task",
3001
+ icon: /* @__PURE__ */ jsx22(BoxIcon6, { name: "bx-play", size: 14 }),
3002
+ color: "success"
3003
+ });
3004
+ }
3005
+ if (node.status === "in-progress") {
3006
+ items.push({
3007
+ id: "complete",
3008
+ label: "Mark Complete",
3009
+ icon: /* @__PURE__ */ jsx22(BoxIcon6, { name: "bx-check", size: 14 }),
3010
+ color: "success"
3011
+ });
3012
+ }
3013
+ if (node.status !== "blocked") {
3014
+ items.push({
3015
+ id: "block",
3016
+ label: "Block Task",
3017
+ icon: /* @__PURE__ */ jsx22(BoxIcon6, { name: "bx-block", size: 14 }),
3018
+ color: "warning"
3019
+ });
3020
+ }
3021
+ items.push({ id: "sep-2", label: "", separator: true });
3022
+ items.push({
3023
+ id: "delete",
3024
+ label: "Delete Task",
3025
+ icon: /* @__PURE__ */ jsx22(BoxIcon6, { name: "bx-trash", size: 14 }),
3026
+ color: "danger"
3027
+ });
3028
+ }
3029
+ return items;
3030
+ }, [node.type, node.status]);
3031
+ const handleContextAction = useCallback18((actionId) => {
3032
+ if (actionId === "view") {
3033
+ onNodeClick?.(node);
3034
+ } else {
3035
+ onNodeAction?.(actionId, node);
3036
+ }
3037
+ }, [node, onNodeClick, onNodeAction]);
3038
+ const nodeClasses = [
3039
+ "backlog-tree__node",
3040
+ isSelected ? "backlog-tree__node--selected" : ""
3041
+ ].filter(Boolean).join(" ");
3042
+ const expandClasses = [
3043
+ "backlog-tree__expand",
3044
+ hasChildren && isExpanded ? "backlog-tree__expand--open" : "",
3045
+ !hasChildren ? "backlog-tree__expand--hidden" : ""
3046
+ ].filter(Boolean).join(" ");
3047
+ const counts = hasChildren ? countTasks(node) : null;
3048
+ const pct = counts && counts.total > 0 ? counts.done / counts.total * 100 : 0;
3049
+ const est = sumEstimate(node);
3050
+ return /* @__PURE__ */ jsxs20(Fragment4, { children: [
3051
+ /* @__PURE__ */ jsx22(ContextMenu, { items: contextMenuItems, onAction: handleContextAction, children: /* @__PURE__ */ jsxs20(
3052
+ "button",
3053
+ {
3054
+ type: "button",
3055
+ className: nodeClasses,
3056
+ style: { paddingLeft: `${10 + depth * 20}px` },
3057
+ "data-testid": `tree-node-${node.id}`,
3058
+ onClick: handleClick,
3059
+ children: [
3060
+ /* @__PURE__ */ jsx22("span", { className: expandClasses, "aria-hidden": "true", children: /* @__PURE__ */ jsx22(BoxIcon6, { name: isExpanded ? "bx-chevron-down" : "bx-chevron-right", size: 14 }) }),
3061
+ /* @__PURE__ */ jsx22("span", { className: "backlog-tree__type-icon", children: /* @__PURE__ */ jsx22(BoxIcon6, { name: TYPE_ICON_NAMES[node.type] || "bx-circle", size: 14 }) }),
3062
+ /* @__PURE__ */ jsx22("span", { className: "backlog-tree__title", children: node.title }),
3063
+ node.labels && node.labels.length > 0 && /* @__PURE__ */ jsx22("span", { className: "backlog-tree__labels", children: node.labels.map((label) => /* @__PURE__ */ jsx22(
3064
+ "span",
3065
+ {
3066
+ className: "backlog-tree__label",
3067
+ style: { backgroundColor: labelColor(label) },
3068
+ children: label
3069
+ },
3070
+ label
3071
+ )) }),
3072
+ est > 0 && /* @__PURE__ */ jsxs20("span", { className: "backlog-tree__estimate", children: [
3073
+ est,
3074
+ "pts"
3075
+ ] }),
3076
+ counts && /* @__PURE__ */ jsxs20("span", { className: "backlog-tree__task-count", title: `${counts.done}/${counts.total} done`, children: [
3077
+ counts.total,
3078
+ " tasks"
3079
+ ] }),
3080
+ counts && counts.total > 0 && /* @__PURE__ */ jsx22("span", { className: "backlog-tree__mini-bar", children: /* @__PURE__ */ jsx22("span", { className: "backlog-tree__mini-bar-fill", style: { width: `${pct}%` } }) }),
3081
+ /* @__PURE__ */ jsx22("span", { className: "backlog-tree__status", children: node.status }),
3082
+ node.priority && /* @__PURE__ */ jsx22(
3083
+ "span",
3084
+ {
3085
+ className: "backlog-tree__priority-dot",
3086
+ style: { backgroundColor: PRIORITY_COLORS2[node.priority] },
3087
+ title: node.priority
3088
+ }
3089
+ )
3090
+ ]
3091
+ }
3092
+ ) }),
3093
+ hasChildren && isExpanded && /* @__PURE__ */ jsx22("div", { className: "backlog-tree__children", "data-testid": `tree-children-${node.id}`, children: node.children.map((child) => /* @__PURE__ */ jsx22(
3094
+ TreeNodeRow,
3095
+ {
3096
+ node: child,
3097
+ depth: depth + 1,
3098
+ expandedNodes,
3099
+ selectedNode,
3100
+ onNodeClick,
3101
+ onNodeExpand,
3102
+ onNodeAction
3103
+ },
3104
+ child.id
3105
+ )) })
3106
+ ] });
3107
+ };
3108
+ var EMPTY_SET = /* @__PURE__ */ new Set();
3109
+ var BacklogTreeWidget = ({
3110
+ tree,
3111
+ onNodeClick,
3112
+ onNodeExpand,
3113
+ onNodeAction,
3114
+ expandedNodes = EMPTY_SET,
3115
+ selectedNode = null,
3116
+ totalTasks = 0,
3117
+ doneTasks = 0,
3118
+ showHeader = true,
3119
+ loading,
3120
+ onRefresh,
3121
+ onFullView
3122
+ }) => {
3123
+ const [searchQuery, setSearchQuery] = useState17("");
3124
+ const [activeFilter, setActiveFilter] = useState17("all");
3125
+ const [internalExpanded, setInternalExpanded] = useState17(/* @__PURE__ */ new Set());
3126
+ const effectiveExpanded = expandedNodes !== EMPTY_SET ? expandedNodes : internalExpanded;
3127
+ const isControlled = expandedNodes !== EMPTY_SET;
3128
+ const handleNodeExpand = (nodeId) => {
3129
+ if (isControlled) {
3130
+ onNodeExpand?.(nodeId);
3131
+ } else {
3132
+ setInternalExpanded((prev) => {
3133
+ const next = new Set(prev);
3134
+ if (next.has(nodeId)) {
3135
+ next.delete(nodeId);
3136
+ } else {
3137
+ next.add(nodeId);
3138
+ }
3139
+ return next;
3140
+ });
3141
+ }
3142
+ };
3143
+ const handleExpandAll = () => {
3144
+ const allIds = /* @__PURE__ */ new Set();
3145
+ const collectIds = (nodes) => {
3146
+ nodes.forEach((node) => {
3147
+ if (node.children && node.children.length > 0) {
3148
+ allIds.add(node.id);
3149
+ collectIds(node.children);
3150
+ }
3151
+ });
3152
+ };
3153
+ collectIds(tree);
3154
+ if (!isControlled) {
3155
+ setInternalExpanded(allIds);
3156
+ }
3157
+ };
3158
+ const handleCollapseAll = () => {
3159
+ if (!isControlled) {
3160
+ setInternalExpanded(/* @__PURE__ */ new Set());
3161
+ }
3162
+ };
3163
+ const filteredTree = useMemo5(() => {
3164
+ const statusFilter = STATUS_FILTER_MAP[activeFilter] ?? [];
3165
+ if (!searchQuery && statusFilter.length === 0) return tree;
3166
+ return tree.filter((node) => nodeMatches(node, searchQuery, statusFilter));
3167
+ }, [tree, searchQuery, activeFilter]);
3168
+ const widgetActions = [
3169
+ {
3170
+ id: "expand-all",
3171
+ label: "Expand All",
3172
+ icon: /* @__PURE__ */ jsx22(BoxIcon6, { name: "bx-expand-alt", size: 16 }),
3173
+ onClick: handleExpandAll
3174
+ },
3175
+ {
3176
+ id: "collapse-all",
3177
+ label: "Collapse All",
3178
+ icon: /* @__PURE__ */ jsx22(BoxIcon6, { name: "bx-collapse-alt", size: 16 }),
3179
+ onClick: handleCollapseAll
3180
+ },
3181
+ ...onFullView ? [
3182
+ {
3183
+ id: "full-view",
3184
+ label: "Full View",
3185
+ icon: /* @__PURE__ */ jsx22(BoxIcon6, { name: "bx-fullscreen", size: 16 }),
3186
+ onClick: onFullView
3187
+ }
3188
+ ] : []
3189
+ ];
3190
+ return /* @__PURE__ */ jsx22(
3191
+ Widget,
3192
+ {
3193
+ title: "Backlog",
3194
+ icon: /* @__PURE__ */ jsx22(BoxIcon6, { name: "bx-list-ul", size: 16 }),
3195
+ collapsible: true,
3196
+ size: "medium",
3197
+ loading,
3198
+ onRefresh,
3199
+ actions: widgetActions,
3200
+ children: /* @__PURE__ */ jsxs20("div", { className: "backlog-tree-wrapper", "data-testid": "backlog-tree-widget", children: [
3201
+ showHeader && /* @__PURE__ */ jsx22(
3202
+ BacklogTreeHeader,
3203
+ {
3204
+ totalTasks,
3205
+ doneTasks,
3206
+ searchQuery,
3207
+ onSearchChange: setSearchQuery,
3208
+ activeFilter,
3209
+ onFilterChange: setActiveFilter
3210
+ }
3211
+ ),
3212
+ filteredTree.length === 0 ? /* @__PURE__ */ jsx22("div", { className: "backlog-tree__empty", "data-testid": "backlog-tree-empty", children: searchQuery || activeFilter !== "all" ? "No matching items" : "No items in backlog" }) : /* @__PURE__ */ jsx22("div", { className: "backlog-tree", children: filteredTree.map((node) => /* @__PURE__ */ jsx22(
3213
+ TreeNodeRow,
3214
+ {
3215
+ node,
3216
+ depth: 0,
3217
+ expandedNodes: effectiveExpanded,
3218
+ selectedNode,
3219
+ onNodeClick,
3220
+ onNodeExpand: handleNodeExpand,
3221
+ onNodeAction
3222
+ },
3223
+ node.id
3224
+ )) })
3225
+ ] })
3226
+ }
3227
+ );
3228
+ };
3229
+
3230
+ // src/widgets/TaskDistributionWidget.tsx
3231
+ import { Widget as Widget2 } from "@orchestra-mcp/widgets";
3232
+ import { BoxIcon as BoxIcon7 } from "@orchestra-mcp/icons";
3233
+ import { jsx as jsx23, jsxs as jsxs21 } from "react/jsx-runtime";
3234
+ var TAB_LABELS = {
3235
+ status: "By Status",
3236
+ priority: "By Priority",
3237
+ type: "By Type"
3238
+ };
3239
+ var TABS = ["status", "priority", "type"];
3240
+ var TaskDistributionWidget = ({
3241
+ byStatus,
3242
+ byPriority,
3243
+ byType,
3244
+ activeChart = "status",
3245
+ onChartChange,
3246
+ loading,
3247
+ onRefresh
3248
+ }) => {
3249
+ const dataMap = {
3250
+ status: byStatus,
3251
+ priority: byPriority,
3252
+ type: byType
3253
+ };
3254
+ const data = dataMap[activeChart] || [];
3255
+ const maxValue = Math.max(...data.map((d) => d.value), 1);
3256
+ return /* @__PURE__ */ jsx23(
3257
+ Widget2,
3258
+ {
3259
+ title: "Task Distribution",
3260
+ icon: /* @__PURE__ */ jsx23(BoxIcon7, { name: "bx-bar-chart-square", size: 16 }),
3261
+ collapsible: true,
3262
+ size: "medium",
3263
+ loading,
3264
+ onRefresh,
3265
+ children: data.length === 0 ? /* @__PURE__ */ jsx23("div", { className: "task-distribution__empty", "data-testid": "task-distribution-empty", children: "No distribution data" }) : /* @__PURE__ */ jsxs21("div", { className: "task-distribution", "data-testid": "task-distribution-widget", children: [
3266
+ /* @__PURE__ */ jsx23("div", { className: "task-distribution__tabs", "data-testid": "task-distribution-tabs", children: TABS.map((key) => /* @__PURE__ */ jsx23(
3267
+ "button",
3268
+ {
3269
+ type: "button",
3270
+ className: [
3271
+ "task-distribution__tab",
3272
+ activeChart === key ? "task-distribution__tab--active" : ""
3273
+ ].filter(Boolean).join(" "),
3274
+ "data-testid": `tab-${key}`,
3275
+ onClick: () => onChartChange?.(key),
3276
+ children: TAB_LABELS[key]
3277
+ },
3278
+ key
3279
+ )) }),
3280
+ /* @__PURE__ */ jsx23("div", { className: "task-distribution__chart", "data-testid": "task-distribution-chart", children: data.map((point) => {
3281
+ const widthPercent = point.value / maxValue * 100;
3282
+ return /* @__PURE__ */ jsxs21("div", { className: "task-distribution__row", children: [
3283
+ /* @__PURE__ */ jsx23("span", { className: "task-distribution__label", title: point.label, children: point.label }),
3284
+ /* @__PURE__ */ jsx23("div", { className: "task-distribution__bar-track", children: /* @__PURE__ */ jsx23(
3285
+ "div",
3286
+ {
3287
+ className: "task-distribution__bar-fill",
3288
+ style: {
3289
+ width: `${widthPercent}%`,
3290
+ backgroundColor: point.color
3291
+ }
3292
+ }
3293
+ ) }),
3294
+ /* @__PURE__ */ jsx23("span", { className: "task-distribution__value", children: point.value })
3295
+ ] }, point.label);
3296
+ }) })
3297
+ ] })
3298
+ }
3299
+ );
3300
+ };
3301
+
3302
+ // src/widgets/RecentActivityWidget.tsx
3303
+ import { Widget as Widget3 } from "@orchestra-mcp/widgets";
3304
+ import { BoxIcon as BoxIcon8 } from "@orchestra-mcp/icons";
3305
+ import { jsx as jsx24, jsxs as jsxs22 } from "react/jsx-runtime";
3306
+ var TYPE_ICONS4 = {
3307
+ created: "\u2728",
3308
+ updated: "\u270F\uFE0F",
3309
+ completed: "\u2705",
3310
+ commented: "\u{1F4AC}",
3311
+ synced: "\u{1F504}"
3312
+ };
3313
+ function formatRelativeTime(timestamp) {
3314
+ const now = Date.now();
3315
+ const then = new Date(timestamp).getTime();
3316
+ const diffSeconds = Math.floor((now - then) / 1e3);
3317
+ if (diffSeconds < 60) return "just now";
3318
+ const diffMinutes = Math.floor(diffSeconds / 60);
3319
+ if (diffMinutes < 60) return `${diffMinutes}m ago`;
3320
+ const diffHours = Math.floor(diffMinutes / 60);
3321
+ if (diffHours < 24) return `${diffHours}h ago`;
3322
+ const diffDays = Math.floor(diffHours / 24);
3323
+ if (diffDays < 30) return `${diffDays}d ago`;
3324
+ return `${Math.floor(diffDays / 30)}mo ago`;
3325
+ }
3326
+ var RecentActivityWidget = ({
3327
+ activities,
3328
+ onActivityClick,
3329
+ maxItems = 20,
3330
+ loading,
3331
+ onRefresh
3332
+ }) => {
3333
+ const visibleItems = activities.slice(0, maxItems);
3334
+ return /* @__PURE__ */ jsx24(
3335
+ Widget3,
3336
+ {
3337
+ title: "Recent Activity",
3338
+ icon: /* @__PURE__ */ jsx24(BoxIcon8, { name: "bx-time-five", size: 16 }),
3339
+ collapsible: true,
3340
+ size: "medium",
3341
+ loading,
3342
+ onRefresh,
3343
+ children: visibleItems.length === 0 ? /* @__PURE__ */ jsx24("div", { className: "recent-activity__empty", "data-testid": "recent-activity-empty", children: "No recent activity" }) : /* @__PURE__ */ jsx24("div", { className: "recent-activity", "data-testid": "recent-activity-widget", children: visibleItems.map((item) => /* @__PURE__ */ jsxs22(
3344
+ "button",
3345
+ {
3346
+ type: "button",
3347
+ className: "recent-activity__item",
3348
+ "data-testid": `activity-${item.id}`,
3349
+ onClick: () => onActivityClick?.(item),
3350
+ children: [
3351
+ /* @__PURE__ */ jsx24("span", { className: "recent-activity__icon", children: TYPE_ICONS4[item.type] }),
3352
+ /* @__PURE__ */ jsxs22("div", { className: "recent-activity__body", children: [
3353
+ /* @__PURE__ */ jsx24("span", { className: "recent-activity__title", children: item.issueTitle }),
3354
+ item.detail && /* @__PURE__ */ jsx24("span", { className: "recent-activity__detail", children: item.detail })
3355
+ ] }),
3356
+ /* @__PURE__ */ jsxs22("div", { className: "recent-activity__meta", children: [
3357
+ /* @__PURE__ */ jsx24("span", { className: "recent-activity__time", children: formatRelativeTime(item.timestamp) }),
3358
+ item.provider && item.provider !== "local" && /* @__PURE__ */ jsx24("span", { className: "recent-activity__provider", children: item.provider })
3359
+ ] })
3360
+ ]
3361
+ },
3362
+ item.id
3363
+ )) })
3364
+ }
3365
+ );
3366
+ };
3367
+
3368
+ // src/widgets/SprintProgressWidget.tsx
3369
+ import { Widget as Widget4 } from "@orchestra-mcp/widgets";
3370
+ import { BoxIcon as BoxIcon9 } from "@orchestra-mcp/icons";
3371
+ import { jsx as jsx25, jsxs as jsxs23 } from "react/jsx-runtime";
3372
+ function computeDaysRemaining(endDate) {
3373
+ const end = new Date(endDate).getTime();
3374
+ const now = Date.now();
3375
+ const diff = end - now;
3376
+ return Math.max(0, Math.ceil(diff / (1e3 * 60 * 60 * 24)));
3377
+ }
3378
+ function formatDate(dateStr) {
3379
+ const d = new Date(dateStr);
3380
+ return d.toLocaleDateString(void 0, { month: "short", day: "numeric" });
3381
+ }
3382
+ function percent(completed, total) {
3383
+ if (total === 0) return 0;
3384
+ return Math.min(100, Math.round(completed / total * 100));
3385
+ }
3386
+ var SprintProgressWidget = ({
3387
+ sprint,
3388
+ noSprintMessage = "No active sprint",
3389
+ loading,
3390
+ onRefresh
3391
+ }) => {
3392
+ const daysLeft = sprint ? computeDaysRemaining(sprint.endDate) : 0;
3393
+ const pointsPercent = sprint ? percent(sprint.completedPoints, sprint.totalPoints) : 0;
3394
+ const tasksPercent = sprint ? percent(sprint.completedTasks, sprint.totalTasks) : 0;
3395
+ return /* @__PURE__ */ jsx25(
3396
+ Widget4,
3397
+ {
3398
+ title: "Sprint Progress",
3399
+ icon: /* @__PURE__ */ jsx25(BoxIcon9, { name: "bx-line-chart", size: 16 }),
3400
+ collapsible: true,
3401
+ size: "medium",
3402
+ loading,
3403
+ onRefresh,
3404
+ children: !sprint ? /* @__PURE__ */ jsx25("div", { className: "sprint-progress__empty", "data-testid": "sprint-progress-empty", children: noSprintMessage }) : /* @__PURE__ */ jsxs23("div", { className: "sprint-progress", "data-testid": "sprint-progress-widget", children: [
3405
+ /* @__PURE__ */ jsxs23("div", { className: "sprint-progress__header", children: [
3406
+ /* @__PURE__ */ jsxs23("div", { children: [
3407
+ /* @__PURE__ */ jsx25("div", { className: "sprint-progress__name", children: sprint.name }),
3408
+ /* @__PURE__ */ jsxs23("div", { className: "sprint-progress__dates", children: [
3409
+ formatDate(sprint.startDate),
3410
+ " \u2013 ",
3411
+ formatDate(sprint.endDate)
3412
+ ] })
3413
+ ] }),
3414
+ /* @__PURE__ */ jsxs23("div", { className: "sprint-progress__days", "data-testid": "sprint-days-remaining", children: [
3415
+ daysLeft,
3416
+ " ",
3417
+ daysLeft === 1 ? "day" : "days",
3418
+ " left"
3419
+ ] })
3420
+ ] }),
3421
+ /* @__PURE__ */ jsxs23("div", { className: "sprint-progress__bars", children: [
3422
+ /* @__PURE__ */ jsxs23("div", { className: "sprint-progress__metric", children: [
3423
+ /* @__PURE__ */ jsxs23("div", { className: "sprint-progress__metric-header", children: [
3424
+ /* @__PURE__ */ jsx25("span", { children: "Points" }),
3425
+ /* @__PURE__ */ jsxs23("span", { className: "sprint-progress__metric-value", children: [
3426
+ sprint.completedPoints,
3427
+ "/",
3428
+ sprint.totalPoints
3429
+ ] })
3430
+ ] }),
3431
+ /* @__PURE__ */ jsx25("div", { className: "sprint-progress__bar-track", children: /* @__PURE__ */ jsx25(
3432
+ "div",
3433
+ {
3434
+ className: "sprint-progress__bar-fill sprint-progress__bar-fill--points",
3435
+ style: { width: `${pointsPercent}%` },
3436
+ "data-testid": "sprint-points-bar"
3437
+ }
3438
+ ) })
3439
+ ] }),
3440
+ /* @__PURE__ */ jsxs23("div", { className: "sprint-progress__metric", children: [
3441
+ /* @__PURE__ */ jsxs23("div", { className: "sprint-progress__metric-header", children: [
3442
+ /* @__PURE__ */ jsx25("span", { children: "Tasks" }),
3443
+ /* @__PURE__ */ jsxs23("span", { className: "sprint-progress__metric-value", children: [
3444
+ sprint.completedTasks,
3445
+ "/",
3446
+ sprint.totalTasks
3447
+ ] })
3448
+ ] }),
3449
+ /* @__PURE__ */ jsx25("div", { className: "sprint-progress__bar-track", children: /* @__PURE__ */ jsx25(
3450
+ "div",
3451
+ {
3452
+ className: "sprint-progress__bar-fill sprint-progress__bar-fill--tasks",
3453
+ style: { width: `${tasksPercent}%` },
3454
+ "data-testid": "sprint-tasks-bar"
3455
+ }
3456
+ ) })
3457
+ ] })
3458
+ ] })
3459
+ ] })
3460
+ }
3461
+ );
3462
+ };
3463
+
3464
+ // src/widgets/TeamWorkloadWidget.tsx
3465
+ import { Widget as Widget5 } from "@orchestra-mcp/widgets";
3466
+ import { BoxIcon as BoxIcon10 } from "@orchestra-mcp/icons";
3467
+ import { jsx as jsx26, jsxs as jsxs24 } from "react/jsx-runtime";
3468
+ function getInitials2(name) {
3469
+ return name.split(" ").map((w) => w[0]).join("").toUpperCase().slice(0, 2);
3470
+ }
3471
+ function segmentPercent(value, total) {
3472
+ if (total === 0) return 0;
3473
+ return value / total * 100;
3474
+ }
3475
+ var TeamWorkloadWidget = ({
3476
+ members,
3477
+ onMemberClick,
3478
+ loading,
3479
+ onRefresh
3480
+ }) => {
3481
+ return /* @__PURE__ */ jsx26(
3482
+ Widget5,
3483
+ {
3484
+ title: "Team Workload",
3485
+ icon: /* @__PURE__ */ jsx26(BoxIcon10, { name: "bx-group", size: 16 }),
3486
+ collapsible: true,
3487
+ size: "medium",
3488
+ loading,
3489
+ onRefresh,
3490
+ children: members.length === 0 ? /* @__PURE__ */ jsx26("div", { className: "team-workload__empty", "data-testid": "team-workload-empty", children: "No team members" }) : /* @__PURE__ */ jsxs24("div", { className: "team-workload", "data-testid": "team-workload-widget", children: [
3491
+ members.map((member) => {
3492
+ const total = member.assigned + member.inProgress + member.completed;
3493
+ return /* @__PURE__ */ jsxs24(
3494
+ "button",
3495
+ {
3496
+ type: "button",
3497
+ className: "team-workload__row",
3498
+ "data-testid": `member-${member.name}`,
3499
+ onClick: () => onMemberClick?.(member.name),
3500
+ children: [
3501
+ /* @__PURE__ */ jsx26("div", { className: "team-workload__avatar", children: member.avatar ? /* @__PURE__ */ jsx26("img", { src: member.avatar, alt: member.name }) : getInitials2(member.name) }),
3502
+ /* @__PURE__ */ jsxs24("div", { className: "team-workload__info", children: [
3503
+ /* @__PURE__ */ jsx26("span", { className: "team-workload__name", children: member.name }),
3504
+ /* @__PURE__ */ jsxs24("div", { className: "team-workload__bar-track", children: [
3505
+ /* @__PURE__ */ jsx26(
3506
+ "div",
3507
+ {
3508
+ className: "team-workload__segment team-workload__segment--completed",
3509
+ style: { width: `${segmentPercent(member.completed, total)}%` }
3510
+ }
3511
+ ),
3512
+ /* @__PURE__ */ jsx26(
3513
+ "div",
3514
+ {
3515
+ className: "team-workload__segment team-workload__segment--in-progress",
3516
+ style: { width: `${segmentPercent(member.inProgress, total)}%` }
3517
+ }
3518
+ ),
3519
+ /* @__PURE__ */ jsx26(
3520
+ "div",
3521
+ {
3522
+ className: "team-workload__segment team-workload__segment--assigned",
3523
+ style: { width: `${segmentPercent(member.assigned, total)}%` }
3524
+ }
3525
+ )
3526
+ ] })
3527
+ ] }),
3528
+ /* @__PURE__ */ jsx26("span", { className: "team-workload__count", children: total })
3529
+ ]
3530
+ },
3531
+ member.name
3532
+ );
3533
+ }),
3534
+ /* @__PURE__ */ jsxs24("div", { className: "team-workload__legend", "data-testid": "team-workload-legend", children: [
3535
+ /* @__PURE__ */ jsxs24("span", { className: "team-workload__legend-item", children: [
3536
+ /* @__PURE__ */ jsx26("span", { className: "team-workload__legend-dot", style: { background: "#22c55e" } }),
3537
+ "Done"
3538
+ ] }),
3539
+ /* @__PURE__ */ jsxs24("span", { className: "team-workload__legend-item", children: [
3540
+ /* @__PURE__ */ jsx26("span", { className: "team-workload__legend-dot", style: { background: "#3b82f6" } }),
3541
+ "In Progress"
3542
+ ] }),
3543
+ /* @__PURE__ */ jsxs24("span", { className: "team-workload__legend-item", children: [
3544
+ /* @__PURE__ */ jsx26("span", { className: "team-workload__legend-dot", style: { background: "#888", opacity: 0.4 } }),
3545
+ "Assigned"
3546
+ ] })
3547
+ ] })
3548
+ ] })
3549
+ }
3550
+ );
3551
+ };
3552
+
3553
+ // src/widgets/ProgressDistributionWidget.tsx
3554
+ import { Widget as Widget6, DonutChart } from "@orchestra-mcp/widgets";
3555
+ import { BoxIcon as BoxIcon11 } from "@orchestra-mcp/icons";
3556
+ import { jsx as jsx27, jsxs as jsxs25 } from "react/jsx-runtime";
3557
+ var ProgressDistributionWidget = ({
3558
+ data,
3559
+ completionPercent,
3560
+ loading,
3561
+ onRefresh
3562
+ }) => {
3563
+ return /* @__PURE__ */ jsx27(
3564
+ Widget6,
3565
+ {
3566
+ title: "Progress Distribution",
3567
+ icon: /* @__PURE__ */ jsx27(BoxIcon11, { name: "bx-doughnut-chart", size: 16 }),
3568
+ collapsible: true,
3569
+ size: "medium",
3570
+ loading,
3571
+ onRefresh,
3572
+ children: data.length === 0 ? /* @__PURE__ */ jsx27("div", { className: "progress-dist__empty", children: "No data" }) : /* @__PURE__ */ jsxs25("div", { className: "progress-dist", children: [
3573
+ /* @__PURE__ */ jsx27(
3574
+ DonutChart,
3575
+ {
3576
+ data,
3577
+ width: 180,
3578
+ height: 180,
3579
+ showLegend: false,
3580
+ innerRatio: 0.65,
3581
+ centerLabel: `${Math.round(completionPercent)}%`,
3582
+ className: "progress-dist__chart"
3583
+ }
3584
+ ),
3585
+ /* @__PURE__ */ jsx27("div", { className: "progress-dist__legend", children: data.map((point) => /* @__PURE__ */ jsxs25("div", { className: "progress-dist__legend-row", children: [
3586
+ /* @__PURE__ */ jsx27(
3587
+ "span",
3588
+ {
3589
+ className: "progress-dist__legend-dot",
3590
+ style: { backgroundColor: point.color }
3591
+ }
3592
+ ),
3593
+ /* @__PURE__ */ jsx27("span", { className: "progress-dist__legend-label", children: point.label }),
3594
+ /* @__PURE__ */ jsx27("span", { className: "progress-dist__legend-value", children: point.value })
3595
+ ] }, point.label)) })
3596
+ ] })
3597
+ }
3598
+ );
3599
+ };
3600
+
3601
+ // src/widgets/ActiveTasksWidget.tsx
3602
+ import { Widget as Widget7 } from "@orchestra-mcp/widgets";
3603
+ import { BoxIcon as BoxIcon12 } from "@orchestra-mcp/icons";
3604
+ import { jsx as jsx28, jsxs as jsxs26 } from "react/jsx-runtime";
3605
+ var PRIORITY_COLORS3 = {
3606
+ low: "#22c55e",
3607
+ medium: "#eab308",
3608
+ high: "#f97316",
3609
+ critical: "#ef4444"
3610
+ };
3611
+ var ActiveTasksWidget = ({
3612
+ tasks,
3613
+ onTaskClick,
3614
+ loading,
3615
+ onRefresh
3616
+ }) => {
3617
+ const breadcrumb = (task) => {
3618
+ const parts = [];
3619
+ if (task.epicTitle) parts.push(task.epicTitle);
3620
+ if (task.storyTitle) parts.push(task.storyTitle);
3621
+ return parts.length > 0 ? parts.join(" > ") : null;
3622
+ };
3623
+ return /* @__PURE__ */ jsx28(
3624
+ Widget7,
3625
+ {
3626
+ title: "Active Tasks",
3627
+ icon: /* @__PURE__ */ jsx28(BoxIcon12, { name: "bx-task", size: 16 }),
3628
+ collapsible: true,
3629
+ size: "medium",
3630
+ loading,
3631
+ onRefresh,
3632
+ children: tasks.length === 0 ? /* @__PURE__ */ jsxs26("div", { className: "active-tasks__empty", "data-testid": "active-tasks-empty", children: [
3633
+ /* @__PURE__ */ jsxs26(
3634
+ "svg",
3635
+ {
3636
+ className: "active-tasks__empty-icon",
3637
+ viewBox: "0 0 48 48",
3638
+ fill: "none",
3639
+ xmlns: "http://www.w3.org/2000/svg",
3640
+ children: [
3641
+ /* @__PURE__ */ jsx28(
3642
+ "circle",
3643
+ {
3644
+ cx: "24",
3645
+ cy: "24",
3646
+ r: "20",
3647
+ stroke: "var(--color-accent, #22c55e)",
3648
+ strokeWidth: "3",
3649
+ fill: "none"
3650
+ }
3651
+ ),
3652
+ /* @__PURE__ */ jsx28(
3653
+ "path",
3654
+ {
3655
+ d: "M15 25l6 6 12-12",
3656
+ stroke: "var(--color-accent, #22c55e)",
3657
+ strokeWidth: "3",
3658
+ strokeLinecap: "round",
3659
+ strokeLinejoin: "round",
3660
+ fill: "none"
3661
+ }
3662
+ )
3663
+ ]
3664
+ }
3665
+ ),
3666
+ /* @__PURE__ */ jsx28("h3", { children: "All Clear" }),
3667
+ /* @__PURE__ */ jsx28("p", { children: "No tasks currently in progress. Ready to start something new?" })
3668
+ ] }) : /* @__PURE__ */ jsxs26("div", { className: "active-tasks", "data-testid": "active-tasks-widget", children: [
3669
+ /* @__PURE__ */ jsxs26("span", { className: "active-tasks__count", children: [
3670
+ tasks.length,
3671
+ " ",
3672
+ tasks.length === 1 ? "task" : "tasks"
3673
+ ] }),
3674
+ /* @__PURE__ */ jsx28("div", { className: "active-tasks__list", children: tasks.map((task) => /* @__PURE__ */ jsxs26(
3675
+ "button",
3676
+ {
3677
+ type: "button",
3678
+ className: "active-tasks__item",
3679
+ "data-testid": `active-task-${task.id}`,
3680
+ onClick: () => onTaskClick?.(task.id),
3681
+ children: [
3682
+ /* @__PURE__ */ jsx28(
3683
+ "span",
3684
+ {
3685
+ className: "active-tasks__priority-dot",
3686
+ style: {
3687
+ backgroundColor: PRIORITY_COLORS3[task.priority ?? ""] ?? "#555"
3688
+ }
3689
+ }
3690
+ ),
3691
+ /* @__PURE__ */ jsxs26("div", { className: "active-tasks__info", children: [
3692
+ /* @__PURE__ */ jsx28("span", { className: "active-tasks__title", children: task.title }),
3693
+ breadcrumb(task) && /* @__PURE__ */ jsx28("span", { className: "active-tasks__breadcrumb", children: breadcrumb(task) })
3694
+ ] }),
3695
+ /* @__PURE__ */ jsx28("span", { className: "active-tasks__badge", children: task.status })
3696
+ ]
3697
+ },
3698
+ task.id
3699
+ )) })
3700
+ ] })
3701
+ }
3702
+ );
3703
+ };
3704
+
3705
+ // src/widgets/SessionMetricsWidget.tsx
3706
+ import { Widget as Widget8 } from "@orchestra-mcp/widgets";
3707
+ import { BoxIcon as BoxIcon13 } from "@orchestra-mcp/icons";
3708
+ import { jsx as jsx29, jsxs as jsxs27 } from "react/jsx-runtime";
3709
+ var ACCENT_COLOR = "var(--color-accent, #7c3aed)";
3710
+ var SessionMetricsWidget = ({
3711
+ kpis,
3712
+ loading,
3713
+ onRefresh
3714
+ }) => {
3715
+ return /* @__PURE__ */ jsx29(
3716
+ Widget8,
3717
+ {
3718
+ title: "Session Metrics",
3719
+ icon: /* @__PURE__ */ jsx29(BoxIcon13, { name: "bx-trending-up", size: 16 }),
3720
+ collapsible: true,
3721
+ size: "medium",
3722
+ loading,
3723
+ onRefresh,
3724
+ children: kpis.length === 0 ? /* @__PURE__ */ jsx29("div", { className: "session-metrics__empty", "data-testid": "session-metrics-empty", children: "No session data" }) : /* @__PURE__ */ jsx29("div", { className: "session-metrics", "data-testid": "session-metrics-widget", children: kpis.map((kpi) => /* @__PURE__ */ jsxs27(
3725
+ "div",
3726
+ {
3727
+ className: "session-metrics__card",
3728
+ style: { borderTopColor: kpi.color || ACCENT_COLOR },
3729
+ "data-testid": `session-kpi-${kpi.label.toLowerCase().replace(/\s+/g, "-")}`,
3730
+ children: [
3731
+ /* @__PURE__ */ jsx29("span", { className: "session-metrics__label", children: kpi.label }),
3732
+ /* @__PURE__ */ jsx29("span", { className: "session-metrics__value", children: kpi.value })
3733
+ ]
3734
+ },
3735
+ kpi.label
3736
+ )) })
3737
+ }
3738
+ );
3739
+ };
3740
+
3741
+ // src/widgets/FeatureAdoptionWidget.tsx
3742
+ import { Widget as Widget9 } from "@orchestra-mcp/widgets";
3743
+ import { BoxIcon as BoxIcon14 } from "@orchestra-mcp/icons";
3744
+ import { jsx as jsx30, jsxs as jsxs28 } from "react/jsx-runtime";
3745
+ var BAR_COLOR = "var(--color-accent, #7c3aed)";
3746
+ var FeatureAdoptionWidget = ({
3747
+ features,
3748
+ maxCount,
3749
+ loading,
3750
+ onRefresh
3751
+ }) => {
3752
+ const resolvedMax = maxCount ?? Math.max(...features.map((f) => f.count), 1);
3753
+ return /* @__PURE__ */ jsx30(
3754
+ Widget9,
3755
+ {
3756
+ title: "Feature Adoption",
3757
+ icon: /* @__PURE__ */ jsx30(BoxIcon14, { name: "bx-star", size: 16 }),
3758
+ collapsible: true,
3759
+ size: "medium",
3760
+ loading,
3761
+ onRefresh,
3762
+ children: features.length === 0 ? /* @__PURE__ */ jsx30("div", { className: "feature-adoption__empty", "data-testid": "feature-adoption-empty", children: "No feature data" }) : /* @__PURE__ */ jsx30("div", { className: "feature-adoption", "data-testid": "feature-adoption-widget", children: features.map((entry) => {
3763
+ const widthPercent = Math.round(entry.count / resolvedMax * 100);
3764
+ return /* @__PURE__ */ jsxs28(
3765
+ "div",
3766
+ {
3767
+ className: "feature-adoption__row",
3768
+ "data-testid": `feature-row-${entry.feature}`,
3769
+ children: [
3770
+ /* @__PURE__ */ jsx30("span", { className: "feature-adoption__name", children: entry.feature }),
3771
+ /* @__PURE__ */ jsx30("div", { className: "feature-adoption__bar-track", children: /* @__PURE__ */ jsx30(
3772
+ "div",
3773
+ {
3774
+ className: "feature-adoption__bar-fill",
3775
+ style: {
3776
+ width: `${widthPercent}%`,
3777
+ backgroundColor: entry.color || BAR_COLOR
3778
+ }
3779
+ }
3780
+ ) }),
3781
+ /* @__PURE__ */ jsx30("span", { className: "feature-adoption__count", children: entry.count })
3782
+ ]
3783
+ },
3784
+ entry.feature
3785
+ );
3786
+ }) })
3787
+ }
3788
+ );
3789
+ };
3790
+
3791
+ // src/widgets/ProjectStatusWidget.tsx
3792
+ import { Widget as Widget10 } from "@orchestra-mcp/widgets";
3793
+ import { BoxIcon as BoxIcon15 } from "@orchestra-mcp/icons";
3794
+ import { jsx as jsx31, jsxs as jsxs29 } from "react/jsx-runtime";
3795
+ var ProjectStatusWidget = ({
3796
+ totalTasks,
3797
+ doneTasks,
3798
+ completionPercent,
3799
+ statuses,
3800
+ loading,
3801
+ onRefresh
3802
+ }) => {
3803
+ const clampedPercent = Math.min(100, Math.max(0, completionPercent));
3804
+ return /* @__PURE__ */ jsx31(
3805
+ Widget10,
3806
+ {
3807
+ title: "Project Status",
3808
+ icon: /* @__PURE__ */ jsx31(BoxIcon15, { name: "bx-bar-chart-alt-2", size: 16 }),
3809
+ collapsible: true,
3810
+ size: "medium",
3811
+ loading,
3812
+ onRefresh,
3813
+ children: /* @__PURE__ */ jsxs29("div", { className: "project-status-widget", children: [
3814
+ /* @__PURE__ */ jsxs29("div", { className: "project-status-widget__summary", children: [
3815
+ /* @__PURE__ */ jsxs29("div", { className: "project-status-widget__stat", children: [
3816
+ /* @__PURE__ */ jsx31("span", { className: "project-status-widget__stat-value", children: totalTasks }),
3817
+ /* @__PURE__ */ jsx31("span", { className: "project-status-widget__stat-label", children: "Total Tasks" })
3818
+ ] }),
3819
+ /* @__PURE__ */ jsxs29("div", { className: "project-status-widget__stat", children: [
3820
+ /* @__PURE__ */ jsx31("span", { className: "project-status-widget__stat-value", children: doneTasks }),
3821
+ /* @__PURE__ */ jsx31("span", { className: "project-status-widget__stat-label", children: "Completed" })
3822
+ ] }),
3823
+ /* @__PURE__ */ jsxs29("div", { className: "project-status-widget__stat", children: [
3824
+ /* @__PURE__ */ jsxs29("span", { className: "project-status-widget__stat-value", children: [
3825
+ clampedPercent,
3826
+ "%"
3827
+ ] }),
3828
+ /* @__PURE__ */ jsx31("span", { className: "project-status-widget__stat-label", children: "Completion" })
3829
+ ] })
3830
+ ] }),
3831
+ /* @__PURE__ */ jsx31("div", { className: "project-status-widget__progress", children: /* @__PURE__ */ jsx31(
3832
+ "div",
3833
+ {
3834
+ className: "project-status-widget__progress-fill",
3835
+ style: { width: `${clampedPercent}%` }
3836
+ }
3837
+ ) }),
3838
+ /* @__PURE__ */ jsx31("div", { className: "project-status-widget__statuses", children: statuses.map((s) => /* @__PURE__ */ jsxs29("div", { className: "project-status-widget__status-chip", children: [
3839
+ /* @__PURE__ */ jsx31(
3840
+ "span",
3841
+ {
3842
+ className: "project-status-widget__status-dot",
3843
+ style: { backgroundColor: s.color }
3844
+ }
3845
+ ),
3846
+ /* @__PURE__ */ jsx31("span", { className: "project-status-widget__status-label", children: s.label }),
3847
+ /* @__PURE__ */ jsx31("span", { className: "project-status-widget__status-count", children: s.count })
3848
+ ] }, s.status)) })
3849
+ ] })
3850
+ }
3851
+ );
3852
+ };
3853
+
3854
+ // src/ProjectDashboard/defaultLayout.ts
3855
+ var WIDGET_TITLES = {
3856
+ "project-status": "Project Status",
3857
+ "backlog-tree": "Backlog",
3858
+ "task-distribution": "Task Distribution",
3859
+ "progress-distribution": "Progress Distribution",
3860
+ "active-tasks": "Active Tasks",
3861
+ "recent-activity": "Recent Activity",
3862
+ "sprint-progress": "Sprint Progress",
3863
+ "team-workload": "Team Workload",
3864
+ "session-metrics": "Session Metrics",
3865
+ "feature-adoption": "Feature Adoption"
3866
+ };
3867
+
3868
+ // src/ProjectDashboard/ProjectDashboard.tsx
3869
+ import { jsx as jsx32, jsxs as jsxs30 } from "react/jsx-runtime";
3870
+ var ProjectDashboard = ({
3871
+ slug,
3872
+ name,
3873
+ totalTasks = 0,
3874
+ doneTasks = 0,
3875
+ completionPercent = 0,
3876
+ statuses = [],
3877
+ epicCount = 0,
3878
+ storyCount = 0,
3879
+ activeTasks = [],
3880
+ tree,
3881
+ loading,
3882
+ onRefresh
3883
+ }) => {
3884
+ const [settingsPos, setSettingsPos] = useState18(null);
3885
+ const [contextMenu, setContextMenu] = useState18(null);
3886
+ const [selectedNodeId, setSelectedNodeId] = useState18(null);
3887
+ const selectedTask = useTaskDetailStore((s) => s.selectedTask);
3888
+ const selectTask = useTaskDetailStore((s) => s.selectTask);
3889
+ const clearSelection = useTaskDetailStore((s) => s.clearSelection);
3890
+ const layout = useDashboardStore((s) => s.getLayout(slug));
3891
+ const columns = useDashboardStore((s) => s.getColumns(slug));
3892
+ const moveWidget = useDashboardStore((s) => s.moveWidget);
3893
+ const moveWidgetToIndex = useDashboardStore((s) => s.moveWidgetToIndex);
3894
+ const toggleHidden = useDashboardStore((s) => s.toggleHidden);
3895
+ const toggleLocked = useDashboardStore((s) => s.toggleLocked);
3896
+ const setWidgetColSpan = useDashboardStore((s) => s.setWidgetColSpan);
3897
+ const setColumns = useDashboardStore((s) => s.setColumns);
3898
+ const visibleWidgets = layout.filter((w) => !w.hidden);
3899
+ const handleSettingsToggle = useCallback19((e) => {
3900
+ if (settingsPos) {
3901
+ setSettingsPos(null);
3902
+ return;
3903
+ }
3904
+ const rect = e.currentTarget.getBoundingClientRect();
3905
+ setSettingsPos({ x: rect.right - 260, y: rect.bottom + 6 });
3906
+ }, [settingsPos]);
3907
+ const handleWidgetContextMenu = useCallback19((widgetId, e) => {
3908
+ e.preventDefault();
3909
+ setContextMenu({ x: e.clientX, y: e.clientY, widgetId });
3910
+ }, []);
3911
+ const handleWidgetMove = useCallback19((widgetId, newVisibleIndex) => {
3912
+ const fullLayout = layout;
3913
+ const draggedWidget = fullLayout.find((w) => w.id === widgetId);
3914
+ if (!draggedWidget) return;
3915
+ const visibleOnly = fullLayout.filter((w) => !w.hidden && w.id !== widgetId);
3916
+ const targetIndex = Math.max(0, Math.min(visibleOnly.length, newVisibleIndex));
3917
+ const reorderedVisible = [
3918
+ ...visibleOnly.slice(0, targetIndex),
3919
+ draggedWidget,
3920
+ ...visibleOnly.slice(targetIndex)
3921
+ ];
3922
+ const hiddenWidgets = fullLayout.filter((w) => w.hidden);
3923
+ const newFullLayout = [...reorderedVisible, ...hiddenWidgets];
3924
+ useDashboardStore.getState().updateLayout(slug, newFullLayout);
3925
+ }, [slug, layout]);
3926
+ const handleNodeClick = useCallback19((node) => {
3927
+ if (node.type !== "task" && node.type !== "bug" && node.type !== "hotfix") {
3928
+ setSelectedNodeId(node.id);
3929
+ return;
3930
+ }
3931
+ const treeArray = tree;
3932
+ if (!treeArray) return;
3933
+ for (const epic of treeArray) {
3934
+ if (!epic.children) continue;
3935
+ for (const story of epic.children) {
3936
+ if (!story.children) continue;
3937
+ const task = story.children.find((t) => t.id === node.id);
3938
+ if (task) {
3939
+ setSelectedNodeId(node.id);
3940
+ selectTask({
3941
+ taskId: node.id,
3942
+ epicId: epic.id,
3943
+ storyId: story.id
3944
+ });
3945
+ return;
3946
+ }
3947
+ }
3948
+ }
3949
+ }, [tree, selectTask]);
3950
+ const handleNodeAction = useCallback19(async (actionId, node) => {
3951
+ const treeArray = tree;
3952
+ if (!treeArray) return;
3953
+ let epicId = "";
3954
+ let storyId = "";
3955
+ for (const epic of treeArray) {
3956
+ if (!epic.children) continue;
3957
+ for (const story of epic.children) {
3958
+ if (!story.children) continue;
3959
+ const task = story.children.find((t) => t.id === node.id);
3960
+ if (task) {
3961
+ epicId = epic.id;
3962
+ storyId = story.id;
3963
+ break;
3964
+ }
3965
+ }
3966
+ if (epicId) break;
3967
+ }
3968
+ if (!epicId || !storyId) return;
3969
+ const { mcpCall: mcpCall2 } = await import("./client-YQRASEZG.js");
3970
+ try {
3971
+ switch (actionId) {
3972
+ case "start":
3973
+ await mcpCall2("set_current_task", {
3974
+ project: slug,
3975
+ epic_id: epicId,
3976
+ story_id: storyId,
3977
+ task_id: node.id
3978
+ });
3979
+ onRefresh?.();
3980
+ break;
3981
+ case "complete":
3982
+ await mcpCall2("complete_task", {
3983
+ project: slug,
3984
+ epic_id: epicId,
3985
+ story_id: storyId,
3986
+ task_id: node.id
3987
+ });
3988
+ onRefresh?.();
3989
+ break;
3990
+ case "block":
3991
+ await mcpCall2("update_task", {
3992
+ project: slug,
3993
+ epic_id: epicId,
3994
+ story_id: storyId,
3995
+ task_id: node.id,
3996
+ status: "blocked"
3997
+ });
3998
+ onRefresh?.();
3999
+ break;
4000
+ case "delete":
4001
+ if (confirm(`Delete task "${node.title}"?`)) {
4002
+ await mcpCall2("delete_task", {
4003
+ project: slug,
4004
+ epic_id: epicId,
4005
+ story_id: storyId,
4006
+ task_id: node.id
4007
+ });
4008
+ onRefresh?.();
4009
+ }
4010
+ break;
4011
+ }
4012
+ } catch (error) {
4013
+ console.error("Action failed:", error);
4014
+ }
4015
+ }, [tree, slug, onRefresh]);
4016
+ const renderWidget = useCallback19(
4017
+ (widget) => {
4018
+ switch (widget.type) {
4019
+ case "project-status":
4020
+ return /* @__PURE__ */ jsx32(
4021
+ ProjectStatusWidget,
4022
+ {
4023
+ slug,
4024
+ totalTasks,
4025
+ doneTasks,
4026
+ completionPercent,
4027
+ statuses,
4028
+ epicCount,
4029
+ storyCount,
4030
+ loading,
4031
+ onRefresh
4032
+ }
4033
+ );
4034
+ case "backlog-tree":
4035
+ return /* @__PURE__ */ jsx32(
4036
+ BacklogTreeWidget,
4037
+ {
4038
+ tree: Array.isArray(tree) ? tree : [],
4039
+ totalTasks,
4040
+ doneTasks,
4041
+ loading,
4042
+ selectedNode: selectedNodeId,
4043
+ onNodeClick: handleNodeClick,
4044
+ onNodeAction: handleNodeAction,
4045
+ onFullView: () => {
4046
+ console.log("Full view requested for project:", slug);
4047
+ }
4048
+ }
4049
+ );
4050
+ case "task-distribution":
4051
+ return /* @__PURE__ */ jsx32(
4052
+ TaskDistributionWidget,
4053
+ {
4054
+ byStatus: statuses.map((s) => ({ label: s.label, value: s.count, color: s.color })),
4055
+ byPriority: [],
4056
+ byType: [],
4057
+ loading
4058
+ }
4059
+ );
4060
+ case "recent-activity":
4061
+ return /* @__PURE__ */ jsx32(
4062
+ RecentActivityWidget,
4063
+ {
4064
+ activities: [],
4065
+ loading
4066
+ }
4067
+ );
4068
+ case "sprint-progress":
4069
+ return /* @__PURE__ */ jsx32(
4070
+ SprintProgressWidget,
4071
+ {
4072
+ sprint: null,
4073
+ loading
4074
+ }
4075
+ );
4076
+ case "team-workload":
4077
+ return /* @__PURE__ */ jsx32(
4078
+ TeamWorkloadWidget,
4079
+ {
4080
+ members: [],
4081
+ loading
4082
+ }
4083
+ );
4084
+ case "active-tasks":
4085
+ return /* @__PURE__ */ jsx32(
4086
+ ActiveTasksWidget,
4087
+ {
4088
+ tasks: activeTasks,
4089
+ loading
4090
+ }
4091
+ );
4092
+ case "progress-distribution":
4093
+ return /* @__PURE__ */ jsx32(
4094
+ ProgressDistributionWidget,
4095
+ {
4096
+ data: statuses.map((s) => ({ label: s.label, value: s.count, color: s.color })),
4097
+ completionPercent,
4098
+ loading
4099
+ }
4100
+ );
4101
+ case "session-metrics":
4102
+ return /* @__PURE__ */ jsx32(
4103
+ SessionMetricsWidget,
4104
+ {
4105
+ kpis: []
4106
+ }
4107
+ );
4108
+ case "feature-adoption":
4109
+ return /* @__PURE__ */ jsx32(
4110
+ FeatureAdoptionWidget,
4111
+ {
4112
+ features: []
4113
+ }
4114
+ );
4115
+ default:
4116
+ return /* @__PURE__ */ jsx32("div", { className: "project-dashboard__placeholder", children: WIDGET_TITLES[widget.type] ?? widget.type });
4117
+ }
4118
+ },
4119
+ [slug, totalTasks, doneTasks, completionPercent, statuses, epicCount, storyCount, activeTasks, tree, loading, onRefresh, selectedNodeId, handleNodeClick, handleNodeAction]
4120
+ );
4121
+ return /* @__PURE__ */ jsxs30("div", { className: "project-dashboard", "data-testid": "project-dashboard", children: [
4122
+ /* @__PURE__ */ jsx32(
4123
+ DashboardHeader,
4124
+ {
4125
+ name,
4126
+ onRefresh,
4127
+ onSettingsToggle: handleSettingsToggle
4128
+ }
4129
+ ),
4130
+ /* @__PURE__ */ jsx32("div", { className: "project-dashboard__body", children: /* @__PURE__ */ jsx32(
4131
+ DashboardGrid,
4132
+ {
4133
+ widgets: visibleWidgets,
4134
+ columns,
4135
+ onMoveToIndex: handleWidgetMove,
4136
+ onWidgetContextMenu: handleWidgetContextMenu,
4137
+ renderWidget,
4138
+ className: `project-dashboard__grid--cols-${columns}`
4139
+ }
4140
+ ) }),
4141
+ settingsPos && /* @__PURE__ */ jsx32(
4142
+ DashboardSettings,
4143
+ {
4144
+ x: settingsPos.x,
4145
+ y: settingsPos.y,
4146
+ columns,
4147
+ onColumnsChange: (cols) => setColumns(slug, cols),
4148
+ widgets: layout.map((w) => ({
4149
+ id: w.id,
4150
+ type: w.type,
4151
+ title: WIDGET_TITLES[w.type] ?? w.type,
4152
+ hidden: !!w.hidden
4153
+ })),
4154
+ onWidgetToggle: (id) => toggleHidden(slug, id),
4155
+ onClose: () => setSettingsPos(null)
4156
+ }
4157
+ ),
4158
+ contextMenu && /* @__PURE__ */ jsx32(
4159
+ WidgetContextMenu,
4160
+ {
4161
+ x: contextMenu.x,
4162
+ y: contextMenu.y,
4163
+ widgetId: contextMenu.widgetId,
4164
+ columns,
4165
+ currentColSpan: layout.find((w) => w.id === contextMenu.widgetId)?.colSpan ?? columns,
4166
+ isLocked: layout.find((w) => w.id === contextMenu.widgetId)?.locked,
4167
+ onColSpanChange: (id, colSpan) => setWidgetColSpan(slug, id, colSpan),
4168
+ onHide: (id) => toggleHidden(slug, id),
4169
+ onLock: (id) => toggleLocked(slug, id),
4170
+ onClose: () => setContextMenu(null)
4171
+ }
4172
+ ),
4173
+ /* @__PURE__ */ jsx32(
4174
+ TaskDetailPanel,
4175
+ {
4176
+ isOpen: !!selectedTask,
4177
+ onClose: () => {
4178
+ clearSelection();
4179
+ setSelectedNodeId(null);
4180
+ },
4181
+ params: selectedTask ? {
4182
+ project: slug,
4183
+ epicId: selectedTask.epicId,
4184
+ storyId: selectedTask.storyId,
4185
+ taskId: selectedTask.taskId
4186
+ } : null
4187
+ }
4188
+ )
4189
+ ] });
4190
+ };
4191
+
4192
+ // src/BacklogTree/BacklogTree.tsx
4193
+ import { useState as useState19, useCallback as useCallback20, useEffect as useEffect12 } from "react";
4194
+ import { BoxIcon as BoxIcon16 } from "@orchestra-mcp/icons";
4195
+ import { EmptyState as EmptyState2 } from "@orchestra-mcp/ui";
4196
+
4197
+ // src/BacklogTree/QuickActions.tsx
4198
+ import { jsx as jsx33, jsxs as jsxs31 } from "react/jsx-runtime";
4199
+ var TERMINAL2 = /* @__PURE__ */ new Set(["done", "cancelled", "rejected"]);
4200
+ var STARTABLE2 = /* @__PURE__ */ new Set(["backlog", "todo", "blocked"]);
4201
+ var IN_PROGRESS2 = "in-progress";
4202
+ function QuickActions({ taskId, status, projectSlug, epicId, storyId, pending, onOptimisticUpdate }) {
4203
+ const { startTask, advanceTask, blockTask, starting, advancing, blocking } = useTaskActions();
4204
+ const loc = {
4205
+ project: projectSlug,
4206
+ epic_id: epicId ?? "",
4207
+ story_id: storyId ?? "",
4208
+ task_id: taskId
4209
+ };
4210
+ const handleClick = (e) => e.stopPropagation();
4211
+ const fireOptimistic = (action) => {
4212
+ const predicted = predictStatus(action, status);
4213
+ if (predicted && onOptimisticUpdate) {
4214
+ onOptimisticUpdate(taskId, predicted);
4215
+ }
4216
+ };
4217
+ const showStart = STARTABLE2.has(status);
4218
+ const showAdvance = status === IN_PROGRESS2 || !TERMINAL2.has(status) && !STARTABLE2.has(status);
4219
+ const showBlock = status === IN_PROGRESS2;
4220
+ const isTerminal = TERMINAL2.has(status);
4221
+ const isDisabled = pending === true;
4222
+ return /* @__PURE__ */ jsxs31("div", { className: `quick-actions${isDisabled ? " quick-actions--disabled" : ""}`, onClick: handleClick, children: [
4223
+ !isTerminal && showStart && /* @__PURE__ */ jsxs31(
4224
+ "button",
4225
+ {
4226
+ type: "button",
4227
+ "aria-label": "Start task",
4228
+ disabled: isDisabled,
4229
+ className: `quick-actions__btn quick-actions__btn--start${starting.loading ? " quick-actions__btn--loading" : ""}`,
4230
+ onClick: () => {
4231
+ fireOptimistic("start");
4232
+ startTask(loc);
4233
+ },
4234
+ children: [
4235
+ starting.loading ? /* @__PURE__ */ jsx33("span", { className: "quick-actions__spinner" }) : "\u25B6",
4236
+ " Start"
4237
+ ]
4238
+ }
4239
+ ),
4240
+ !isTerminal && showAdvance && /* @__PURE__ */ jsxs31(
4241
+ "button",
4242
+ {
4243
+ type: "button",
4244
+ "aria-label": "Advance task",
4245
+ disabled: isDisabled,
4246
+ className: `quick-actions__btn quick-actions__btn--advance${advancing.loading ? " quick-actions__btn--loading" : ""}`,
4247
+ onClick: () => {
4248
+ fireOptimistic("advance");
4249
+ advanceTask(loc, void 0);
4250
+ },
4251
+ children: [
4252
+ advancing.loading ? /* @__PURE__ */ jsx33("span", { className: "quick-actions__spinner" }) : "\u23E9",
4253
+ " Advance"
4254
+ ]
4255
+ }
4256
+ ),
4257
+ !isTerminal && showBlock && /* @__PURE__ */ jsxs31(
4258
+ "button",
4259
+ {
4260
+ type: "button",
4261
+ "aria-label": "Block task",
4262
+ disabled: isDisabled,
4263
+ className: `quick-actions__btn quick-actions__btn--block${blocking.loading ? " quick-actions__btn--loading" : ""}`,
4264
+ onClick: () => {
4265
+ fireOptimistic("block");
4266
+ blockTask(loc);
4267
+ },
4268
+ children: [
4269
+ blocking.loading ? /* @__PURE__ */ jsx33("span", { className: "quick-actions__spinner" }) : "\u26D4",
4270
+ " Block"
4271
+ ]
4272
+ }
4273
+ )
4274
+ ] });
4275
+ }
4276
+
4277
+ // src/BacklogTree/BacklogTree.tsx
4278
+ import { jsx as jsx34, jsxs as jsxs32 } from "react/jsx-runtime";
4279
+ var STORAGE_KEY = "orchestra-backlog-expanded";
4280
+ function loadExpanded(slug) {
4281
+ try {
4282
+ const raw = localStorage.getItem(`${STORAGE_KEY}:${slug}`);
4283
+ if (raw) return new Set(JSON.parse(raw));
4284
+ } catch {
4285
+ }
4286
+ return /* @__PURE__ */ new Set();
4287
+ }
4288
+ function saveExpanded(slug, expanded) {
4289
+ try {
4290
+ localStorage.setItem(`${STORAGE_KEY}:${slug}`, JSON.stringify([...expanded]));
4291
+ } catch {
4292
+ }
4293
+ }
4294
+ function BacklogTree({ tree, projectSlug, loading, onTaskClick, onTaskSelect, autoExpandIds, pendingTaskIds }) {
4295
+ const [expanded, setExpanded] = useState19(() => loadExpanded(projectSlug));
4296
+ useEffect12(() => {
4297
+ setExpanded(loadExpanded(projectSlug));
4298
+ }, [projectSlug]);
4299
+ useEffect12(() => {
4300
+ if (autoExpandIds && autoExpandIds.size > 0) {
4301
+ setExpanded((prev) => {
4302
+ const next = new Set(prev);
4303
+ for (const id of autoExpandIds) next.add(id);
4304
+ return next;
4305
+ });
4306
+ }
4307
+ }, [autoExpandIds]);
4308
+ const toggle = useCallback20((id) => {
4309
+ setExpanded((prev) => {
4310
+ const next = new Set(prev);
4311
+ if (next.has(id)) next.delete(id);
4312
+ else next.add(id);
4313
+ saveExpanded(projectSlug, next);
4314
+ return next;
4315
+ });
4316
+ }, [projectSlug]);
4317
+ if (loading) {
4318
+ return /* @__PURE__ */ jsx34("div", { className: "backlog-sidebar__loading", children: "Loading..." });
4319
+ }
4320
+ if (tree.length === 0) {
4321
+ return /* @__PURE__ */ jsx34(
4322
+ EmptyState2,
4323
+ {
4324
+ icon: /* @__PURE__ */ jsx34(BoxIcon16, { name: "bx-list-ul", size: 40 }),
4325
+ title: "No epics yet",
4326
+ description: "Create an epic to get started"
4327
+ }
4328
+ );
4329
+ }
4330
+ return /* @__PURE__ */ jsx34("div", { className: "backlog-sidebar", "data-testid": "backlog-tree", children: tree.map((epic) => /* @__PURE__ */ jsx34(
4331
+ EpicNode,
4332
+ {
4333
+ node: epic,
4334
+ expanded,
4335
+ onToggle: toggle,
4336
+ onTaskClick,
4337
+ onTaskSelect,
4338
+ projectSlug,
4339
+ pendingTaskIds
4340
+ },
4341
+ epic.id
4342
+ )) });
4343
+ }
4344
+ function EpicNode({
4345
+ node,
4346
+ expanded,
4347
+ onToggle,
4348
+ onTaskClick,
4349
+ onTaskSelect,
4350
+ projectSlug,
4351
+ pendingTaskIds
4352
+ }) {
4353
+ const isOpen = expanded.has(node.id);
4354
+ const stories = node.children ?? [];
4355
+ return /* @__PURE__ */ jsxs32("div", { className: "backlog-sidebar__epic", children: [
4356
+ /* @__PURE__ */ jsxs32(
4357
+ "button",
4358
+ {
4359
+ type: "button",
4360
+ className: "backlog-sidebar__row backlog-sidebar__row--epic",
4361
+ onClick: () => onToggle(node.id),
4362
+ children: [
4363
+ /* @__PURE__ */ jsx34("span", { className: `backlog-sidebar__chevron${isOpen ? " backlog-sidebar__chevron--open" : ""}`, children: "\u25B6" }),
4364
+ /* @__PURE__ */ jsx34("span", { className: "backlog-sidebar__title", children: node.title }),
4365
+ node.priority && /* @__PURE__ */ jsx34(PriorityIcon, { priority: node.priority, size: 12 }),
4366
+ /* @__PURE__ */ jsx34(StatusBadge, { status: node.status, variant: "dot", size: "sm" })
4367
+ ]
4368
+ }
4369
+ ),
4370
+ isOpen && stories.length > 0 && /* @__PURE__ */ jsx34("div", { className: "backlog-sidebar__children", children: stories.map((story) => /* @__PURE__ */ jsx34(
4371
+ StoryNode,
4372
+ {
4373
+ node: story,
4374
+ expanded,
4375
+ onToggle,
4376
+ onTaskClick,
4377
+ onTaskSelect,
4378
+ projectSlug,
4379
+ epicId: node.id,
4380
+ pendingTaskIds
4381
+ },
4382
+ story.id
4383
+ )) })
4384
+ ] });
4385
+ }
4386
+ function StoryNode({
4387
+ node,
4388
+ expanded,
4389
+ onToggle,
4390
+ onTaskClick,
4391
+ onTaskSelect,
4392
+ projectSlug,
4393
+ epicId,
4394
+ pendingTaskIds
4395
+ }) {
4396
+ const isOpen = expanded.has(node.id);
4397
+ const tasks = node.children ?? [];
4398
+ return /* @__PURE__ */ jsxs32("div", { className: "backlog-sidebar__story", children: [
4399
+ /* @__PURE__ */ jsxs32(
4400
+ "button",
4401
+ {
4402
+ type: "button",
4403
+ className: "backlog-sidebar__row backlog-sidebar__row--story",
4404
+ onClick: () => onToggle(node.id),
4405
+ children: [
4406
+ /* @__PURE__ */ jsx34("span", { className: `backlog-sidebar__chevron${isOpen ? " backlog-sidebar__chevron--open" : ""}`, children: "\u25B6" }),
4407
+ /* @__PURE__ */ jsx34("span", { className: "backlog-sidebar__title", children: node.title }),
4408
+ /* @__PURE__ */ jsx34("span", { className: "backlog-sidebar__count", children: tasks.length }),
4409
+ /* @__PURE__ */ jsx34(StatusBadge, { status: node.status, variant: "dot", size: "sm" })
4410
+ ]
4411
+ }
4412
+ ),
4413
+ isOpen && tasks.length > 0 && /* @__PURE__ */ jsx34("div", { className: "backlog-sidebar__children", children: tasks.map((task) => /* @__PURE__ */ jsx34(
4414
+ TaskNode,
4415
+ {
4416
+ node: task,
4417
+ onTaskClick,
4418
+ onTaskSelect,
4419
+ projectSlug,
4420
+ epicId,
4421
+ storyId: node.id,
4422
+ pending: pendingTaskIds?.has(task.id)
4423
+ },
4424
+ task.id
4425
+ )) })
4426
+ ] });
4427
+ }
4428
+ function getInitials3(name) {
4429
+ return name.split(/[\s-]+/).map((w) => w[0]).join("").toUpperCase().slice(0, 2);
4430
+ }
4431
+ function TaskNode({
4432
+ node,
4433
+ onTaskClick,
4434
+ onTaskSelect,
4435
+ projectSlug,
4436
+ epicId,
4437
+ storyId,
4438
+ pending
4439
+ }) {
4440
+ const wrapClass = `backlog-sidebar__task-wrap${pending ? " backlog-sidebar__task-wrap--pending" : ""}`;
4441
+ return /* @__PURE__ */ jsxs32("div", { className: wrapClass, "data-testid": pending ? `task-pending-${node.id}` : void 0, children: [
4442
+ /* @__PURE__ */ jsxs32(
4443
+ "button",
4444
+ {
4445
+ type: "button",
4446
+ className: "backlog-sidebar__row backlog-sidebar__row--task",
4447
+ onClick: () => {
4448
+ onTaskClick?.(node);
4449
+ onTaskSelect?.(node.id, epicId, storyId);
4450
+ },
4451
+ children: [
4452
+ /* @__PURE__ */ jsx34(StatusBadge, { status: node.status, variant: "dot", size: "sm" }),
4453
+ /* @__PURE__ */ jsx34("span", { className: "backlog-sidebar__title", children: node.title }),
4454
+ node.priority && /* @__PURE__ */ jsx34(PriorityIcon, { priority: node.priority, size: 12 }),
4455
+ node.assignedTo && /* @__PURE__ */ jsx34("span", { className: "backlog-sidebar__assignee", title: node.assignedTo, children: getInitials3(node.assignedTo) })
4456
+ ]
4457
+ }
4458
+ ),
4459
+ /* @__PURE__ */ jsx34(
4460
+ QuickActions,
4461
+ {
4462
+ taskId: node.id,
4463
+ status: node.status,
4464
+ projectSlug,
4465
+ epicId,
4466
+ storyId,
4467
+ pending
4468
+ }
4469
+ )
4470
+ ] });
4471
+ }
4472
+
4473
+ // src/TasksSidebar/TasksSidebar.tsx
4474
+ import { useState as useState21, useMemo as useMemo6 } from "react";
4475
+
4476
+ // src/TaskFilter/TaskFilter.tsx
4477
+ import { useState as useState20, useCallback as useCallback21, useEffect as useEffect13, useRef as useRef13 } from "react";
4478
+ import { jsx as jsx35, jsxs as jsxs33 } from "react/jsx-runtime";
4479
+ var ALL_STATUSES = [
4480
+ "backlog",
4481
+ "todo",
4482
+ "in-progress",
4483
+ "blocked",
4484
+ "ready-for-testing",
4485
+ "in-testing",
4486
+ "ready-for-docs",
4487
+ "in-docs",
4488
+ "documented",
4489
+ "in-review",
4490
+ "done",
4491
+ "rejected",
4492
+ "cancelled"
4493
+ ];
4494
+ var ALL_PRIORITIES = ["critical", "high", "medium", "low"];
4495
+ var STATUS_LABELS2 = {
4496
+ backlog: "Backlog",
4497
+ todo: "To Do",
4498
+ "in-progress": "In Progress",
4499
+ blocked: "Blocked",
4500
+ "ready-for-testing": "Ready Test",
4501
+ "in-testing": "Testing",
4502
+ "ready-for-docs": "Ready Docs",
4503
+ "in-docs": "In Docs",
4504
+ documented: "Documented",
4505
+ "in-review": "In Review",
4506
+ done: "Done",
4507
+ rejected: "Rejected",
4508
+ cancelled: "Cancelled"
4509
+ };
4510
+ var PRIORITY_LABELS = {
4511
+ critical: "Critical",
4512
+ high: "High",
4513
+ medium: "Medium",
4514
+ low: "Low"
4515
+ };
4516
+ function TaskFilter({ value, onChange, assignees = [] }) {
4517
+ const [open, setOpen] = useState20(null);
4518
+ const ref = useRef13(null);
4519
+ useEffect13(() => {
4520
+ const handler = (e) => {
4521
+ if (ref.current && !ref.current.contains(e.target)) {
4522
+ setOpen(null);
4523
+ }
4524
+ };
4525
+ document.addEventListener("mousedown", handler);
4526
+ return () => document.removeEventListener("mousedown", handler);
4527
+ }, []);
4528
+ const toggleDropdown = useCallback21((key) => {
4529
+ setOpen((prev) => prev === key ? null : key);
4530
+ }, []);
4531
+ const toggleStatus = useCallback21((status) => {
4532
+ const next = value.statuses.includes(status) ? value.statuses.filter((s) => s !== status) : [...value.statuses, status];
4533
+ onChange({ ...value, statuses: next });
4534
+ }, [value, onChange]);
4535
+ const togglePriority = useCallback21((priority) => {
4536
+ const next = value.priorities.includes(priority) ? value.priorities.filter((p) => p !== priority) : [...value.priorities, priority];
4537
+ onChange({ ...value, priorities: next });
4538
+ }, [value, onChange]);
4539
+ const setAssignee = useCallback21((assignee) => {
4540
+ onChange({ ...value, assignee });
4541
+ setOpen(null);
4542
+ }, [value, onChange]);
4543
+ const clearAll = useCallback21(() => {
4544
+ onChange({ statuses: [], priorities: [], assignee: "", searchQuery: "" });
4545
+ }, [onChange]);
4546
+ const selectAllStatuses = useCallback21(() => {
4547
+ onChange({ ...value, statuses: [...ALL_STATUSES] });
4548
+ }, [value, onChange]);
4549
+ const clearStatuses = useCallback21(() => {
4550
+ onChange({ ...value, statuses: [] });
4551
+ }, [value, onChange]);
4552
+ const onSearchChange = useCallback21((e) => {
4553
+ onChange({ ...value, searchQuery: e.target.value });
4554
+ }, [value, onChange]);
4555
+ const clearSearch = useCallback21(() => {
4556
+ onChange({ ...value, searchQuery: "" });
4557
+ }, [value, onChange]);
4558
+ const hasFilters = value.statuses.length > 0 || value.priorities.length > 0 || value.assignee !== "" || value.searchQuery !== "";
4559
+ return /* @__PURE__ */ jsxs33("div", { className: "task-filter", ref, "data-testid": "task-filter", children: [
4560
+ /* @__PURE__ */ jsxs33("div", { className: "task-filter__search-wrap", children: [
4561
+ /* @__PURE__ */ jsx35(
4562
+ "input",
4563
+ {
4564
+ className: "task-filter__search",
4565
+ type: "text",
4566
+ placeholder: "Search tasks...",
4567
+ value: value.searchQuery,
4568
+ onChange: onSearchChange,
4569
+ "data-testid": "task-filter-search"
4570
+ }
4571
+ ),
4572
+ value.searchQuery && /* @__PURE__ */ jsx35(
4573
+ "button",
4574
+ {
4575
+ type: "button",
4576
+ className: "task-filter__search-clear",
4577
+ onClick: clearSearch,
4578
+ "aria-label": "Clear search",
4579
+ children: "\xD7"
4580
+ }
4581
+ )
4582
+ ] }),
4583
+ /* @__PURE__ */ jsxs33(
4584
+ FilterDropdown,
4585
+ {
4586
+ label: "Status",
4587
+ count: value.statuses.length,
4588
+ isOpen: open === "status",
4589
+ onToggle: () => toggleDropdown("status"),
4590
+ children: [
4591
+ /* @__PURE__ */ jsxs33("div", { className: "task-filter__actions", children: [
4592
+ /* @__PURE__ */ jsx35("button", { type: "button", className: "task-filter__action-btn", onClick: selectAllStatuses, children: "Select All" }),
4593
+ /* @__PURE__ */ jsx35("button", { type: "button", className: "task-filter__action-btn", onClick: clearStatuses, children: "Clear" })
4594
+ ] }),
4595
+ ALL_STATUSES.map((s) => /* @__PURE__ */ jsx35(
4596
+ CheckboxOption,
4597
+ {
4598
+ label: STATUS_LABELS2[s] ?? s,
4599
+ checked: value.statuses.includes(s),
4600
+ onChange: () => toggleStatus(s)
4601
+ },
4602
+ s
4603
+ ))
4604
+ ]
4605
+ }
4606
+ ),
4607
+ /* @__PURE__ */ jsx35(
4608
+ FilterDropdown,
4609
+ {
4610
+ label: "Priority",
4611
+ count: value.priorities.length,
4612
+ isOpen: open === "priority",
4613
+ onToggle: () => toggleDropdown("priority"),
4614
+ children: ALL_PRIORITIES.map((p) => /* @__PURE__ */ jsx35(
4615
+ CheckboxOption,
4616
+ {
4617
+ label: PRIORITY_LABELS[p] ?? p,
4618
+ checked: value.priorities.includes(p),
4619
+ onChange: () => togglePriority(p)
4620
+ },
4621
+ p
4622
+ ))
4623
+ }
4624
+ ),
4625
+ /* @__PURE__ */ jsxs33(
4626
+ FilterDropdown,
4627
+ {
4628
+ label: "Assignee",
4629
+ count: value.assignee ? 1 : 0,
4630
+ isOpen: open === "assignee",
4631
+ onToggle: () => toggleDropdown("assignee"),
4632
+ children: [
4633
+ /* @__PURE__ */ jsx35(
4634
+ "button",
4635
+ {
4636
+ type: "button",
4637
+ className: `task-filter__option${value.assignee === "" ? " task-filter__option--selected" : ""}`,
4638
+ onClick: () => setAssignee(""),
4639
+ children: "All"
4640
+ }
4641
+ ),
4642
+ assignees.map((a) => /* @__PURE__ */ jsx35(
4643
+ "button",
4644
+ {
4645
+ type: "button",
4646
+ className: `task-filter__option${value.assignee === a ? " task-filter__option--selected" : ""}`,
4647
+ onClick: () => setAssignee(a),
4648
+ children: a
4649
+ },
4650
+ a
4651
+ ))
4652
+ ]
4653
+ }
4654
+ ),
4655
+ hasFilters && /* @__PURE__ */ jsx35("button", { type: "button", className: "task-filter__clear", onClick: clearAll, children: "Clear all" })
4656
+ ] });
4657
+ }
4658
+ function FilterDropdown({
4659
+ label,
4660
+ count,
4661
+ isOpen,
4662
+ onToggle,
4663
+ children
4664
+ }) {
4665
+ return /* @__PURE__ */ jsxs33("div", { className: "task-filter__dropdown-wrap", children: [
4666
+ /* @__PURE__ */ jsxs33(
4667
+ "button",
4668
+ {
4669
+ type: "button",
4670
+ className: `task-filter__trigger${isOpen ? " task-filter__trigger--active" : ""}`,
4671
+ onClick: onToggle,
4672
+ "data-testid": `filter-trigger-${label.toLowerCase()}`,
4673
+ children: [
4674
+ label,
4675
+ count > 0 && /* @__PURE__ */ jsx35("span", { className: "task-filter__count", children: count })
4676
+ ]
4677
+ }
4678
+ ),
4679
+ isOpen && /* @__PURE__ */ jsx35("div", { className: "task-filter__dropdown", "data-testid": `filter-dropdown-${label.toLowerCase()}`, children })
4680
+ ] });
4681
+ }
4682
+ function CheckboxOption({
4683
+ label,
4684
+ checked,
4685
+ onChange
4686
+ }) {
4687
+ return /* @__PURE__ */ jsxs33("button", { type: "button", className: "task-filter__option", onClick: onChange, children: [
4688
+ /* @__PURE__ */ jsx35("span", { className: `task-filter__checkbox${checked ? " task-filter__checkbox--checked" : ""}`, children: checked ? "\u2713" : "" }),
4689
+ label
4690
+ ] });
4691
+ }
4692
+
4693
+ // src/ConnectionStatus/ConnectionStatus.tsx
4694
+ import { jsx as jsx36, jsxs as jsxs34 } from "react/jsx-runtime";
4695
+ var STATUS_LABELS3 = {
4696
+ connected: "Connected",
4697
+ reconnecting: "Reconnecting...",
4698
+ disconnected: "Connection lost"
4699
+ };
4700
+ var BANNER_TEXT = {
4701
+ reconnecting: "Reconnecting to server...",
4702
+ disconnected: "Connection lost. Click refresh to retry."
4703
+ };
4704
+ var ConnectionStatus = ({ status }) => {
4705
+ const label = STATUS_LABELS3[status];
4706
+ const bannerText = BANNER_TEXT[status];
4707
+ return /* @__PURE__ */ jsxs34("div", { className: "connection-status", "data-testid": "connection-status", children: [
4708
+ /* @__PURE__ */ jsx36(
4709
+ "span",
4710
+ {
4711
+ className: `connection-status__dot connection-status__dot--${status}`,
4712
+ title: label,
4713
+ "aria-label": label,
4714
+ role: "status"
4715
+ }
4716
+ ),
4717
+ bannerText && /* @__PURE__ */ jsx36(
4718
+ "div",
4719
+ {
4720
+ className: `connection-status__banner connection-status__banner--${status}`,
4721
+ role: "alert",
4722
+ "data-testid": "connection-status-banner",
4723
+ children: bannerText
4724
+ }
4725
+ )
4726
+ ] });
4727
+ };
4728
+ var ConnectionStatusDot = ({ status }) => {
4729
+ const label = STATUS_LABELS3[status];
4730
+ return /* @__PURE__ */ jsx36(
4731
+ "span",
4732
+ {
4733
+ className: `connection-status__dot connection-status__dot--${status}`,
4734
+ title: label,
4735
+ "aria-label": label,
4736
+ role: "status",
4737
+ "data-testid": "connection-status-dot"
4738
+ }
4739
+ );
4740
+ };
4741
+ var ConnectionStatusBanner = ({ status }) => {
4742
+ const bannerText = BANNER_TEXT[status];
4743
+ if (!bannerText) return null;
4744
+ return /* @__PURE__ */ jsx36(
4745
+ "div",
4746
+ {
4747
+ className: `connection-status__banner connection-status__banner--${status}`,
4748
+ role: "alert",
4749
+ "data-testid": "connection-status-banner",
4750
+ children: bannerText
4751
+ }
4752
+ );
4753
+ };
4754
+
4755
+ // src/TasksSidebar/TasksSidebar.tsx
4756
+ import { jsx as jsx37, jsxs as jsxs35 } from "react/jsx-runtime";
4757
+ var INITIAL_FILTERS = { statuses: [], priorities: [], assignee: "", searchQuery: "" };
4758
+ function TasksSidebar({
4759
+ projectName,
4760
+ projectSlug,
4761
+ tree,
4762
+ loading,
4763
+ connectionStatus,
4764
+ onBack,
4765
+ onTaskClick,
4766
+ onTaskSelect,
4767
+ onRefresh
4768
+ }) {
4769
+ const [filters, setFilters] = useState21(INITIAL_FILTERS);
4770
+ const debouncedQuery = useDebounce(filters.searchQuery, 300);
4771
+ const debouncedFilters = useMemo6(
4772
+ () => ({ ...filters, searchQuery: debouncedQuery }),
4773
+ [filters, debouncedQuery]
4774
+ );
4775
+ const filteredTree = useFilteredTree(tree, debouncedFilters);
4776
+ const autoExpandIds = useMemo6(
4777
+ () => collectMatchParentIds(tree, debouncedQuery),
4778
+ [tree, debouncedQuery]
4779
+ );
4780
+ const assignees = extractAssignees(tree);
4781
+ return /* @__PURE__ */ jsxs35("div", { className: "tasks-sidebar", "data-testid": "tasks-sidebar", children: [
4782
+ /* @__PURE__ */ jsxs35("div", { className: "tasks-sidebar__header", children: [
4783
+ onBack && /* @__PURE__ */ jsx37(
4784
+ "button",
4785
+ {
4786
+ type: "button",
4787
+ className: "tasks-sidebar__back",
4788
+ onClick: onBack,
4789
+ "aria-label": "Back to projects",
4790
+ children: "\u2190"
4791
+ }
4792
+ ),
4793
+ /* @__PURE__ */ jsx37("span", { className: "tasks-sidebar__project-name", children: projectName }),
4794
+ connectionStatus && /* @__PURE__ */ jsx37(ConnectionStatusDot, { status: connectionStatus }),
4795
+ onRefresh && /* @__PURE__ */ jsx37(
4796
+ "button",
4797
+ {
4798
+ type: "button",
4799
+ className: "tasks-sidebar__refresh",
4800
+ onClick: onRefresh,
4801
+ "aria-label": "Refresh",
4802
+ children: "\u21BB"
4803
+ }
4804
+ )
4805
+ ] }),
4806
+ connectionStatus && /* @__PURE__ */ jsx37(ConnectionStatusBanner, { status: connectionStatus }),
4807
+ /* @__PURE__ */ jsx37(TaskFilter, { value: filters, onChange: setFilters, assignees }),
4808
+ /* @__PURE__ */ jsx37(
4809
+ BacklogTree,
4810
+ {
4811
+ tree: filteredTree,
4812
+ projectSlug,
4813
+ loading,
4814
+ onTaskClick,
4815
+ onTaskSelect,
4816
+ autoExpandIds
4817
+ }
4818
+ )
4819
+ ] });
4820
+ }
4821
+
4822
+ // src/LifecycleProgressBar/LifecycleProgressBar.tsx
4823
+ import React from "react";
4824
+ import { jsx as jsx38, jsxs as jsxs36 } from "react/jsx-runtime";
4825
+ var LIFECYCLE_STEPS = [
4826
+ { key: "backlog", label: "Backlog", color: "#6b7280" },
4827
+ { key: "todo", label: "To Do", color: "#3b82f6" },
4828
+ { key: "in-progress", label: "In Progress", color: "#8b5cf6" },
4829
+ { key: "ready-for-testing", label: "Ready for Testing", color: "#f59e0b" },
4830
+ { key: "in-testing", label: "In Testing", color: "#f97316" },
4831
+ { key: "ready-for-docs", label: "Ready for Docs", color: "#06b6d4" },
4832
+ { key: "in-docs", label: "In Docs", color: "#0ea5e9" },
4833
+ { key: "documented", label: "Documented", color: "#14b8a6" },
4834
+ { key: "in-review", label: "In Review", color: "#a855f7" },
4835
+ { key: "done", label: "Done", color: "#22c55e" }
4836
+ ];
4837
+ var SPECIAL_STATUSES = {
4838
+ blocked: { label: "Blocked", color: "#ef4444" },
4839
+ rejected: { label: "Rejected", color: "#ef4444" },
4840
+ cancelled: { label: "Cancelled", color: "#6b7280" }
4841
+ };
4842
+ function getDotState(stepIndex, currentIndex, isSpecial) {
4843
+ if (isSpecial) return "future";
4844
+ if (stepIndex < currentIndex) return "past";
4845
+ if (stepIndex === currentIndex) return "current";
4846
+ return "future";
4847
+ }
4848
+ function getLineState(lineIndex, currentIndex, isSpecial) {
4849
+ if (isSpecial) return "future";
4850
+ if (lineIndex < currentIndex) return "past";
4851
+ return "future";
4852
+ }
4853
+ function LifecycleProgressBar({ status, size = "sm" }) {
4854
+ const isSpecial = status in SPECIAL_STATUSES;
4855
+ const specialInfo = isSpecial ? SPECIAL_STATUSES[status] : null;
4856
+ const currentIndex = LIFECYCLE_STEPS.findIndex((s) => s.key === status);
4857
+ return /* @__PURE__ */ jsxs36("div", { className: `lifecycle-bar lifecycle-bar--${size}`, "data-testid": "lifecycle-bar", children: [
4858
+ /* @__PURE__ */ jsx38("div", { className: "lifecycle-bar__track", children: LIFECYCLE_STEPS.map((step, i) => {
4859
+ const dotState = getDotState(i, currentIndex, isSpecial);
4860
+ const dotColor = dotState === "future" ? void 0 : step.color;
4861
+ return /* @__PURE__ */ jsxs36(React.Fragment, { children: [
4862
+ i > 0 && /* @__PURE__ */ jsx38(
4863
+ "div",
4864
+ {
4865
+ className: `lifecycle-bar__line lifecycle-bar__line--${getLineState(i, currentIndex, isSpecial)}`,
4866
+ "data-testid": `line-${i}`
4867
+ }
4868
+ ),
4869
+ /* @__PURE__ */ jsx38(
4870
+ "div",
4871
+ {
4872
+ className: `lifecycle-bar__dot lifecycle-bar__dot--${dotState}`,
4873
+ style: dotColor ? { backgroundColor: dotColor, color: dotColor } : void 0,
4874
+ title: step.label,
4875
+ "data-testid": `step-${step.key}`
4876
+ }
4877
+ )
4878
+ ] }, step.key);
4879
+ }) }),
4880
+ isSpecial && specialInfo && /* @__PURE__ */ jsx38(
4881
+ "div",
4882
+ {
4883
+ className: "lifecycle-bar__special",
4884
+ style: { color: specialInfo.color },
4885
+ "data-testid": "special-status",
4886
+ children: specialInfo.label
4887
+ }
4888
+ )
4889
+ ] });
4890
+ }
4891
+ export {
4892
+ ActiveTasksWidget,
4893
+ BacklogTree,
4894
+ BacklogTreeWidget,
4895
+ BacklogView,
4896
+ ConnectionStatus,
4897
+ ConnectionStatusBanner,
4898
+ ConnectionStatusDot,
4899
+ DashboardGrid,
4900
+ DashboardHeader,
4901
+ DashboardSettings,
4902
+ DetailPanel,
4903
+ EditableSelect,
4904
+ EditableText,
4905
+ EditableTextarea,
4906
+ EvidenceLog,
4907
+ FeatureAdoptionWidget,
4908
+ LabelsSection,
4909
+ LifecycleProgressBar,
4910
+ LinksSection,
4911
+ PriorityIcon,
4912
+ ProgressDistributionWidget,
4913
+ ProjectDashboard,
4914
+ ProjectItem,
4915
+ ProjectStatusWidget,
4916
+ ProjectsSidebar,
4917
+ RecentActivityWidget,
4918
+ ResizeHandle,
4919
+ SessionMetricsWidget,
4920
+ SprintProgressWidget,
4921
+ StaleNotification,
4922
+ StatusActions,
4923
+ StatusBadge,
4924
+ StatusGrid,
4925
+ TaskDetailPanel,
4926
+ TaskDistributionWidget,
4927
+ TaskFilter,
4928
+ TaskList,
4929
+ TasksSidebar,
4930
+ TeamWorkloadWidget,
4931
+ WidgetContextMenu,
4932
+ extractAssignees,
4933
+ mcpCall,
4934
+ predictStatus,
4935
+ useConnectionStatus,
4936
+ useDashboardStore,
4937
+ useFilteredTree,
4938
+ useGridDrag,
4939
+ useIntegrationStore,
4940
+ useOptimisticTaskActions,
4941
+ useProjectTree,
4942
+ useProjects,
4943
+ useTaskActions,
4944
+ useTaskDetail,
4945
+ useTaskDetailStore
4946
+ };