@cadraos/sdk 0.3.8 → 0.3.10

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.
@@ -16,6 +16,10 @@ function mapHandler(h) {
16
16
  type: h.type,
17
17
  agentUuid: h.agent_uuid,
18
18
  teamUuid: h.team_uuid,
19
+ // FINDING-6: carry the remote_agent binding through the wire parse. The
20
+ // engine's remote_agent dispatch reads `remoteAgentUuid`; dropping it here
21
+ // made every published remote_agent lane dispatch-dead.
22
+ remoteAgentUuid: h.remote_agent_uuid,
19
23
  agentRef: h.agent_ref,
20
24
  webhook: h.webhook ? {
21
25
  url: h.webhook.url,
@@ -294,7 +298,7 @@ function useKanbanBoard(options) {
294
298
  try {
295
299
  const full = await adapter.getCard(cardId);
296
300
  setSelectedCard(
297
- (prev) => prev && prev.id === cardId ? full : prev
301
+ (prev) => prev == null || prev.id === cardId ? full : prev
298
302
  );
299
303
  } catch (err) {
300
304
  if (!known) fail(err, "Failed to open card");
@@ -403,6 +407,27 @@ function useKanbanBoard(options) {
403
407
  },
404
408
  [refresh]
405
409
  );
410
+ const deleteCard = useCallback(
411
+ async (cardId) => {
412
+ if (!adapter.deleteCard) return false;
413
+ setError(null);
414
+ const prevCards = cards;
415
+ const prevSelected = selectedCard;
416
+ setCards((prev) => prev.filter((c) => c.id !== cardId));
417
+ setSelectedCard((prev) => prev && prev.id === cardId ? null : prev);
418
+ try {
419
+ await adapter.deleteCard(cardId);
420
+ void silentRefresh();
421
+ return true;
422
+ } catch (err) {
423
+ setCards(prevCards);
424
+ setSelectedCard(prevSelected);
425
+ fail(err, "Failed to delete card");
426
+ return false;
427
+ }
428
+ },
429
+ [adapter, cards, selectedCard, silentRefresh, fail]
430
+ );
406
431
  return {
407
432
  board,
408
433
  lanes: board?.lanes ?? [],
@@ -418,6 +443,7 @@ function useKanbanBoard(options) {
418
443
  createCard,
419
444
  updateCard,
420
445
  transitionCard,
446
+ deleteCard,
421
447
  handleRunTerminal
422
448
  };
423
449
  }
@@ -2293,35 +2319,40 @@ function useCardRun(options) {
2293
2319
  function cn9(...classes) {
2294
2320
  return classes.filter(Boolean).join(" ");
2295
2321
  }
2296
- function runStatusTone(status) {
2297
- switch (status) {
2298
- case "running":
2299
- return {
2300
- label: "Running",
2301
- className: "bg-blue-50 text-blue-700 dark:bg-blue-950/40 dark:text-blue-300"
2302
- };
2303
- case "completed":
2304
- return {
2305
- label: "Completed",
2306
- className: "bg-green-50 text-green-700 dark:bg-green-950/40 dark:text-green-300"
2307
- };
2308
- case "failed":
2309
- return {
2310
- label: "Failed",
2311
- className: "bg-red-50 text-red-700 dark:bg-red-950/40 dark:text-red-300"
2312
- };
2313
- case "cancelled":
2314
- return {
2315
- label: "Cancelled",
2316
- className: "bg-gray-100 text-gray-600 dark:bg-gray-800 dark:text-gray-400"
2317
- };
2322
+ function badgeVariantClass(variant) {
2323
+ switch (variant) {
2324
+ case "default":
2325
+ return "bg-primary text-primary-foreground";
2326
+ case "secondary":
2327
+ return "border border-border bg-secondary text-secondary-foreground";
2328
+ case "outline":
2329
+ return "border border-border text-foreground";
2330
+ case "info":
2331
+ return "border border-info/30 bg-info/15 text-info";
2332
+ case "success":
2333
+ return "border border-success/30 bg-success/15 text-success";
2334
+ case "warning":
2335
+ return "border border-warning/40 bg-warning/15 text-warning";
2336
+ case "destructive":
2337
+ return "border border-destructive/40 bg-destructive/15 text-destructive";
2338
+ case "neutral":
2318
2339
  default:
2319
- return {
2320
- label: "Pending",
2321
- className: "bg-amber-50 text-amber-700 dark:bg-amber-950/40 dark:text-amber-300"
2322
- };
2340
+ return "border border-border bg-secondary text-muted-foreground";
2323
2341
  }
2324
2342
  }
2343
+ function runStatusTone(status) {
2344
+ const map = {
2345
+ running: { label: "Running", variant: "info" },
2346
+ completed: { label: "Completed", variant: "success" },
2347
+ failed: { label: "Failed", variant: "destructive" },
2348
+ cancelled: { label: "Cancelled", variant: "neutral" }
2349
+ };
2350
+ const { label, variant } = map[status ?? ""] ?? {
2351
+ label: "Pending",
2352
+ variant: "neutral"
2353
+ };
2354
+ return { label, variant, className: badgeVariantClass(variant) };
2355
+ }
2325
2356
  function CardDrawer({
2326
2357
  card,
2327
2358
  adapter,
@@ -2440,11 +2471,11 @@ function CardDrawer({
2440
2471
  role: "dialog",
2441
2472
  "aria-label": `Card: ${card.title}`,
2442
2473
  className: cn9(
2443
- "flex h-full w-full sm:max-w-sm flex-col bg-white dark:bg-gray-950 border-l border-gray-200 dark:border-gray-800 shadow-xl",
2474
+ "flex h-full w-full sm:max-w-sm flex-col bg-card border-l border-border shadow-xl",
2444
2475
  className
2445
2476
  ),
2446
2477
  children: [
2447
- /* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between gap-3 border-b border-gray-200 dark:border-gray-800 px-4 py-3", children: [
2478
+ /* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between gap-3 border-b border-border px-4 py-3", children: [
2448
2479
  /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
2449
2480
  isEditing ? /* @__PURE__ */ jsx(
2450
2481
  "input",
@@ -2454,14 +2485,14 @@ function CardDrawer({
2454
2485
  onChange: (e) => setEditTitle(e.target.value),
2455
2486
  "aria-label": "Card title",
2456
2487
  placeholder: "Card title",
2457
- className: "w-full rounded-md border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-900 px-2 py-1 text-base font-semibold text-gray-900 dark:text-gray-100 focus:outline-none focus:ring-2 focus:ring-blue-500/40"
2488
+ className: "w-full rounded-md border border-input bg-background px-2 py-1 text-base font-semibold text-foreground focus:outline-none focus:ring-2 focus:ring-ring/50"
2458
2489
  }
2459
2490
  ) : /* @__PURE__ */ jsx(
2460
2491
  "h2",
2461
2492
  {
2462
2493
  className: cn9(
2463
- "text-base font-semibold text-gray-900 dark:text-gray-100 truncate",
2464
- onUpdate && "cursor-text rounded hover:bg-gray-100 dark:hover:bg-gray-800/60 -mx-1 px-1"
2494
+ "text-base font-semibold text-foreground truncate",
2495
+ onUpdate && "cursor-text rounded hover:bg-muted/60 -mx-1 px-1"
2465
2496
  ),
2466
2497
  onClick: onUpdate ? () => setIsEditing(true) : void 0,
2467
2498
  title: onUpdate ? "Click to edit" : void 0,
@@ -2469,7 +2500,7 @@ function CardDrawer({
2469
2500
  }
2470
2501
  ),
2471
2502
  /* @__PURE__ */ jsxs("div", { className: "mt-1 flex items-center gap-2", children: [
2472
- /* @__PURE__ */ jsx("span", { className: "text-xs text-gray-500 dark:text-gray-400", children: card.lane }),
2503
+ /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: card.lane }),
2473
2504
  /* @__PURE__ */ jsx(
2474
2505
  "span",
2475
2506
  {
@@ -2489,7 +2520,7 @@ function CardDrawer({
2489
2520
  type: "button",
2490
2521
  onClick: () => setIsEditing(true),
2491
2522
  "aria-label": "Edit card",
2492
- className: "rounded p-1 text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-800 hover:text-gray-700 dark:hover:text-gray-200",
2523
+ className: "rounded p-1 text-muted-foreground hover:bg-muted hover:text-foreground",
2493
2524
  children: /* @__PURE__ */ jsxs(
2494
2525
  "svg",
2495
2526
  {
@@ -2515,7 +2546,7 @@ function CardDrawer({
2515
2546
  type: "button",
2516
2547
  onClick: onClose,
2517
2548
  "aria-label": "Close",
2518
- className: "rounded p-1 text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-800 hover:text-gray-700 dark:hover:text-gray-200",
2549
+ className: "rounded p-1 text-muted-foreground hover:bg-muted hover:text-foreground",
2519
2550
  children: /* @__PURE__ */ jsxs(
2520
2551
  "svg",
2521
2552
  {
@@ -2537,8 +2568,8 @@ function CardDrawer({
2537
2568
  )
2538
2569
  ] })
2539
2570
  ] }),
2540
- isEditing ? /* @__PURE__ */ jsxs("div", { className: "border-b border-gray-200 dark:border-gray-800 px-4 py-3", children: [
2541
- /* @__PURE__ */ jsx("label", { className: "mb-1 block text-xs font-medium text-gray-500 dark:text-gray-400", children: "Description" }),
2571
+ isEditing ? /* @__PURE__ */ jsxs("div", { className: "border-b border-border px-4 py-3", children: [
2572
+ /* @__PURE__ */ jsx("label", { className: "mb-1 block text-xs font-medium text-muted-foreground", children: "Description" }),
2542
2573
  /* @__PURE__ */ jsx(
2543
2574
  "textarea",
2544
2575
  {
@@ -2547,7 +2578,7 @@ function CardDrawer({
2547
2578
  "aria-label": "Card description",
2548
2579
  rows: 4,
2549
2580
  placeholder: "Add a description\u2026",
2550
- className: "w-full resize-y rounded-md border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-900 px-2 py-1.5 text-sm text-gray-700 dark:text-gray-200 focus:outline-none focus:ring-2 focus:ring-blue-500/40"
2581
+ className: "w-full resize-y rounded-md border border-input bg-background px-2 py-1.5 text-sm text-foreground focus:outline-none focus:ring-2 focus:ring-ring/50"
2551
2582
  }
2552
2583
  ),
2553
2584
  /* @__PURE__ */ jsxs("div", { className: "mt-3 flex items-center justify-end gap-2", children: [
@@ -2557,7 +2588,7 @@ function CardDrawer({
2557
2588
  type: "button",
2558
2589
  onClick: handleCancel,
2559
2590
  disabled: isSaving,
2560
- className: "rounded-md px-3 py-1.5 text-sm font-medium text-gray-600 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-800 disabled:opacity-50",
2591
+ className: "rounded-md px-3 py-1.5 text-sm font-medium text-muted-foreground hover:bg-muted disabled:opacity-50",
2561
2592
  children: "Cancel"
2562
2593
  }
2563
2594
  ),
@@ -2567,26 +2598,27 @@ function CardDrawer({
2567
2598
  type: "button",
2568
2599
  onClick: () => void handleSave(),
2569
2600
  disabled: isSaving,
2570
- className: "rounded-md bg-blue-600 px-3 py-1.5 text-sm font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500/40 disabled:opacity-50",
2601
+ className: "rounded-md bg-primary px-3 py-1.5 text-sm font-medium text-primary-foreground hover:bg-primary/90 focus:outline-none focus:ring-2 focus:ring-ring/50 disabled:opacity-50",
2571
2602
  children: isSaving ? "Saving\u2026" : "Save"
2572
2603
  }
2573
2604
  )
2574
2605
  ] })
2575
- ] }) : (card.description || onUpdate) && /* @__PURE__ */ jsx("div", { className: "border-b border-gray-200 dark:border-gray-800 px-4 py-3", children: /* @__PURE__ */ jsx(
2606
+ ] }) : (card.description || onUpdate) && /* @__PURE__ */ jsx("div", { className: "border-b border-border px-4 py-3", children: /* @__PURE__ */ jsx(
2576
2607
  "p",
2577
2608
  {
2578
2609
  className: cn9(
2579
2610
  "text-sm whitespace-pre-wrap",
2580
- card.description ? "text-gray-600 dark:text-gray-300" : "italic text-gray-400 dark:text-gray-600",
2581
- onUpdate && "cursor-text rounded hover:bg-gray-100 dark:hover:bg-gray-800/60 -mx-1 px-1"
2611
+ card.description ? "text-muted-foreground" : "italic text-muted-foreground/70",
2612
+ onUpdate && "cursor-text rounded hover:bg-muted/60 -mx-1 px-1"
2582
2613
  ),
2583
2614
  onClick: onUpdate ? () => setIsEditing(true) : void 0,
2584
2615
  title: onUpdate ? "Click to edit" : void 0,
2585
2616
  children: card.description || "Add a description\u2026"
2586
2617
  }
2587
2618
  ) }),
2588
- showTransitionButtons ? /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center justify-end gap-2 border-b border-gray-200 dark:border-gray-800 px-4 py-2", children: [
2589
- isHumanGate && /* @__PURE__ */ jsx("span", { className: "mr-auto text-xs font-medium text-amber-600 dark:text-amber-400", children: "Awaiting your approval" }),
2619
+ showTransitionButtons ? /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center justify-end gap-2 border-b border-border px-4 py-2", children: [
2620
+ isHumanGate && // Human gate = needs-you → warning (amber is reserved for this, F3).
2621
+ /* @__PURE__ */ jsx("span", { className: "mr-auto text-xs font-medium text-warning", children: "Awaiting your approval" }),
2590
2622
  manualEdges.map((t, i) => {
2591
2623
  const toLabel = laneByKey.get(t.toLane)?.label ?? t.toLane;
2592
2624
  const isPrimary = isHumanGate ? i === 0 : false;
@@ -2599,26 +2631,27 @@ function CardDrawer({
2599
2631
  onClick: () => void handleTransition(t.toLane),
2600
2632
  disabled: pendingLane !== null,
2601
2633
  className: cn9(
2602
- "rounded-md px-3 py-1.5 text-sm font-medium focus:outline-none focus:ring-2 disabled:opacity-50",
2603
- isPrimary ? "bg-emerald-600 text-white hover:bg-emerald-700 focus:ring-emerald-500/40" : "border border-gray-300 dark:border-gray-700 text-gray-700 dark:text-gray-200 hover:bg-gray-100 dark:hover:bg-gray-800 focus:ring-blue-500/40"
2634
+ // ≥44px touch (M12/F5) — density kept via padding, not height.
2635
+ "inline-flex min-h-11 items-center justify-center rounded-md px-3 py-1.5 text-sm font-medium focus:outline-none focus:ring-2 focus:ring-ring/50 disabled:opacity-50",
2636
+ isPrimary ? "border border-border bg-secondary text-secondary-foreground hover:bg-muted" : "border border-border text-foreground hover:bg-muted"
2604
2637
  ),
2605
2638
  children: busy ? "Moving\u2026" : label
2606
2639
  },
2607
2640
  t.id
2608
2641
  );
2609
2642
  })
2610
- ] }) : onAdvance && canAdvance && !isEditing && /* @__PURE__ */ jsx("div", { className: "flex items-center justify-end border-b border-gray-200 dark:border-gray-800 px-4 py-2", children: /* @__PURE__ */ jsx(
2643
+ ] }) : onAdvance && canAdvance && !isEditing && /* @__PURE__ */ jsx("div", { className: "flex items-center justify-end border-b border-border px-4 py-2", children: /* @__PURE__ */ jsx(
2611
2644
  "button",
2612
2645
  {
2613
2646
  type: "button",
2614
2647
  onClick: () => void handleAdvance(),
2615
2648
  disabled: isAdvancing,
2616
- className: "rounded-md border border-gray-300 dark:border-gray-700 px-3 py-1.5 text-sm font-medium text-gray-700 dark:text-gray-200 hover:bg-gray-100 dark:hover:bg-gray-800 focus:outline-none focus:ring-2 focus:ring-blue-500/40 disabled:opacity-50",
2649
+ className: "inline-flex min-h-11 items-center justify-center rounded-md border border-border px-3 py-1.5 text-sm font-medium text-foreground hover:bg-muted focus:outline-none focus:ring-2 focus:ring-ring/50 disabled:opacity-50",
2617
2650
  children: isAdvancing ? "Advancing\u2026" : "Advance \u2192"
2618
2651
  }
2619
2652
  ) }),
2620
2653
  /* @__PURE__ */ jsxs("div", { className: "flex flex-1 flex-col overflow-hidden", children: [
2621
- !hasExecution ? /* @__PURE__ */ jsx("div", { className: "flex flex-1 items-center justify-center p-6 text-center", children: /* @__PURE__ */ jsx("p", { className: "text-sm text-gray-400 dark:text-gray-600", children: "No run yet for this card." }) }) : /* @__PURE__ */ jsx(
2654
+ !hasExecution ? /* @__PURE__ */ jsx("div", { className: "flex flex-1 items-center justify-center p-6 text-center", children: /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: "No run yet for this card." }) }) : /* @__PURE__ */ jsx(
2622
2655
  MessageList,
2623
2656
  {
2624
2657
  messages: run.messages,
@@ -2631,14 +2664,14 @@ function CardDrawer({
2631
2664
  className: "flex-1"
2632
2665
  }
2633
2666
  ),
2634
- failed && /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-3 border-t border-red-200 dark:border-red-900 bg-red-50 dark:bg-red-950/30 px-4 py-3", children: [
2635
- /* @__PURE__ */ jsx("span", { className: "text-sm text-red-700 dark:text-red-300", children: run.error ?? "Run failed." }),
2667
+ failed && /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-3 border-t border-destructive/40 bg-destructive/10 px-4 py-3", children: [
2668
+ /* @__PURE__ */ jsx("span", { className: "text-sm text-destructive", children: run.error ?? "Run failed." }),
2636
2669
  onRetry && /* @__PURE__ */ jsx(
2637
2670
  "button",
2638
2671
  {
2639
2672
  type: "button",
2640
2673
  onClick: () => onRetry(card),
2641
- className: "shrink-0 rounded-md bg-red-600 px-3 py-1.5 text-sm font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-red-500/40",
2674
+ className: "inline-flex min-h-11 shrink-0 items-center justify-center rounded-md border border-border bg-card px-3 py-1.5 text-sm font-medium text-foreground hover:bg-muted focus:outline-none focus:ring-2 focus:ring-ring/50",
2642
2675
  children: "Retry"
2643
2676
  }
2644
2677
  )
@@ -2680,10 +2713,54 @@ function Spinner({ className }) {
2680
2713
  }
2681
2714
  );
2682
2715
  }
2683
- function KanbanCard({ card, onClick, selected, className }) {
2716
+ function ClockIcon2() {
2717
+ return /* @__PURE__ */ jsxs(
2718
+ "svg",
2719
+ {
2720
+ viewBox: "0 0 24 24",
2721
+ fill: "none",
2722
+ stroke: "currentColor",
2723
+ strokeWidth: "2",
2724
+ strokeLinecap: "round",
2725
+ strokeLinejoin: "round",
2726
+ "aria-hidden": true,
2727
+ className: "h-2.5 w-2.5",
2728
+ children: [
2729
+ /* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "9" }),
2730
+ /* @__PURE__ */ jsx("path", { d: "M12 7v5l3 2" })
2731
+ ]
2732
+ }
2733
+ );
2734
+ }
2735
+ function KanbanCard({
2736
+ card,
2737
+ onClick,
2738
+ selected,
2739
+ dueState,
2740
+ draggable,
2741
+ onDragStart,
2742
+ onDragEnd,
2743
+ className
2744
+ }) {
2684
2745
  const runStatus = card.run?.status;
2685
- const tone = runStatus ? runStatusTone(runStatus) : null;
2686
- const isWorking = runStatus === "running" || runStatus === "pending";
2746
+ const isWorking = runStatus === "running";
2747
+ const failed = runStatus === "failed";
2748
+ let chip = null;
2749
+ if (failed) {
2750
+ chip = { label: "Failed", variant: "destructive" };
2751
+ } else if (dueState === "ready") {
2752
+ chip = { label: "Ready", variant: "success" };
2753
+ } else if (runStatus === "completed") {
2754
+ const t = runStatusTone(runStatus);
2755
+ chip = { label: t.label, variant: t.variant };
2756
+ } else if (dueState === "due") {
2757
+ chip = { label: "Due", variant: "neutral", icon: "clock" };
2758
+ } else if (dueState === "waiting") {
2759
+ chip = { label: "Waiting", variant: "neutral" };
2760
+ } else if (runStatus) {
2761
+ const t = runStatusTone(runStatus);
2762
+ chip = { label: t.label, variant: t.variant };
2763
+ }
2687
2764
  return /* @__PURE__ */ jsxs(
2688
2765
  "button",
2689
2766
  {
@@ -2691,47 +2768,438 @@ function KanbanCard({ card, onClick, selected, className }) {
2691
2768
  onClick: () => onClick?.(card),
2692
2769
  "aria-pressed": selected,
2693
2770
  "aria-busy": isWorking,
2771
+ draggable,
2772
+ onDragStart: draggable ? (e) => {
2773
+ e.dataTransfer.effectAllowed = "move";
2774
+ e.dataTransfer.setData("text/plain", card.id);
2775
+ onDragStart?.(card);
2776
+ } : void 0,
2777
+ onDragEnd: draggable ? () => onDragEnd?.() : void 0,
2694
2778
  className: cn9(
2695
- "relative w-full text-left rounded-lg border bg-white dark:bg-gray-900 p-3 shadow-sm transition",
2696
- "hover:shadow-md hover:border-gray-300 dark:hover:border-gray-600",
2697
- "focus:outline-none focus:ring-2 focus:ring-blue-500/40",
2698
- isWorking ? "border-amber-400 dark:border-amber-500 ring-2 ring-amber-400/50 animate-pulse" : selected ? "border-blue-400 dark:border-blue-500 ring-2 ring-blue-500/30" : "border-gray-200 dark:border-gray-700",
2779
+ // Solid card: fill + border + a faint token top-sheen (adapts to theme),
2780
+ // matching the host's cockpit cards (work-shell v2 visual pass).
2781
+ "relative w-full text-left rounded-xl border bg-card bg-gradient-to-b from-foreground/[0.03] to-transparent p-3 shadow-sm motion-safe:transition-shadow",
2782
+ "hover:shadow-md hover:border-primary/40",
2783
+ "focus:outline-none focus:ring-2 focus:ring-ring/50",
2784
+ draggable && "cursor-grab active:cursor-grabbing",
2785
+ // The working card carries the SINGLE focal motion on the board (F11).
2786
+ isWorking ? "border-info/60 ring-2 ring-info/40 animate-pulse" : selected ? "border-primary/60 ring-2 ring-primary/30" : "border-border",
2699
2787
  className
2700
2788
  ),
2701
2789
  children: [
2702
- isWorking && /* @__PURE__ */ jsxs(
2703
- "span",
2704
- {
2705
- "aria-hidden": true,
2706
- className: "pointer-events-none absolute -top-1 -right-1 flex h-3 w-3",
2707
- children: [
2708
- /* @__PURE__ */ jsx("span", { className: "absolute inline-flex h-full w-full animate-ping rounded-full bg-amber-400 opacity-75" }),
2709
- /* @__PURE__ */ jsx("span", { className: "relative inline-flex h-3 w-3 rounded-full bg-amber-500" })
2710
- ]
2711
- }
2712
- ),
2713
2790
  /* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between gap-2", children: [
2714
- /* @__PURE__ */ jsx("span", { className: "text-sm font-medium text-gray-900 dark:text-gray-100 line-clamp-2", children: card.title }),
2715
- isWorking ? /* @__PURE__ */ jsxs("span", { className: "shrink-0 inline-flex items-center gap-1 rounded-full bg-amber-100 dark:bg-amber-900/40 px-2 py-0.5 text-[10px] font-semibold uppercase tracking-wide text-amber-700 dark:text-amber-300", children: [
2716
- /* @__PURE__ */ jsx(Spinner, {}),
2717
- "Working"
2718
- ] }) : tone && /* @__PURE__ */ jsx(
2791
+ /* @__PURE__ */ jsx("span", { className: "text-sm font-medium text-foreground line-clamp-2", children: card.title }),
2792
+ isWorking ? /* @__PURE__ */ jsxs(
2719
2793
  "span",
2720
2794
  {
2721
2795
  className: cn9(
2722
- "shrink-0 rounded-full px-2 py-0.5 text-[10px] font-semibold uppercase tracking-wide",
2723
- tone.className
2796
+ "shrink-0 inline-flex items-center gap-1 rounded-full px-2 py-0.5 text-[10px] font-semibold uppercase tracking-wide",
2797
+ badgeVariantClass("info")
2724
2798
  ),
2725
- children: tone.label
2799
+ children: [
2800
+ /* @__PURE__ */ jsx(Spinner, {}),
2801
+ "Working"
2802
+ ]
2803
+ }
2804
+ ) : chip && /* @__PURE__ */ jsxs(
2805
+ "span",
2806
+ {
2807
+ className: cn9(
2808
+ "shrink-0 inline-flex items-center gap-1 rounded-full px-2 py-0.5 text-[10px] font-semibold uppercase tracking-wide",
2809
+ badgeVariantClass(chip.variant)
2810
+ ),
2811
+ children: [
2812
+ chip.icon === "clock" && /* @__PURE__ */ jsx(ClockIcon2, {}),
2813
+ chip.label
2814
+ ]
2726
2815
  }
2727
2816
  )
2728
2817
  ] }),
2729
- card.description && /* @__PURE__ */ jsx("p", { className: "mt-1.5 text-xs text-gray-500 dark:text-gray-400 line-clamp-2", children: card.description }),
2730
- card.type && /* @__PURE__ */ jsx("div", { className: "mt-2", children: /* @__PURE__ */ jsx("span", { className: "inline-block rounded bg-gray-100 dark:bg-gray-800 px-1.5 py-0.5 text-[10px] font-medium text-gray-600 dark:text-gray-400", children: card.type }) })
2818
+ card.description && /* @__PURE__ */ jsx("p", { className: "mt-1.5 text-xs text-muted-foreground line-clamp-2", children: card.description }),
2819
+ card.type && /* @__PURE__ */ jsx("div", { className: "mt-2", children: /* @__PURE__ */ jsx("span", { className: "inline-block rounded bg-muted px-1.5 py-0.5 text-[10px] font-medium text-muted-foreground", children: card.type }) })
2731
2820
  ]
2732
2821
  }
2733
2822
  );
2734
2823
  }
2824
+ function CapMeter({ count, cap, className }) {
2825
+ const pct = cap > 0 ? Math.min(100, Math.round(count / cap * 100)) : 0;
2826
+ return /* @__PURE__ */ jsxs(
2827
+ "div",
2828
+ {
2829
+ className: cn9(
2830
+ "mt-2 flex items-center gap-2 text-[11px] text-muted-foreground",
2831
+ className
2832
+ ),
2833
+ children: [
2834
+ /* @__PURE__ */ jsxs("span", { className: "tabular-nums font-medium", children: [
2835
+ count,
2836
+ "/",
2837
+ cap
2838
+ ] }),
2839
+ /* @__PURE__ */ jsx(
2840
+ "span",
2841
+ {
2842
+ role: "progressbar",
2843
+ "aria-valuemin": 0,
2844
+ "aria-valuemax": cap,
2845
+ "aria-valuenow": count,
2846
+ "aria-label": `${count} of ${cap} used`,
2847
+ className: "h-1 flex-1 overflow-hidden rounded-full bg-secondary",
2848
+ children: /* @__PURE__ */ jsx(
2849
+ "span",
2850
+ {
2851
+ className: "block h-full rounded-full bg-primary",
2852
+ style: { width: `${pct}%` }
2853
+ }
2854
+ )
2855
+ }
2856
+ )
2857
+ ]
2858
+ }
2859
+ );
2860
+ }
2861
+ var AGENT_GROUPS = [
2862
+ { kind: "cadra", label: "Cadra agents" },
2863
+ { kind: "remote", label: "Your own agents" },
2864
+ { kind: "human", label: "Teammates" }
2865
+ ];
2866
+ function AgentKindIcon({ kind }) {
2867
+ const common = {
2868
+ viewBox: "0 0 24 24",
2869
+ fill: "none",
2870
+ stroke: "currentColor",
2871
+ strokeWidth: 2,
2872
+ strokeLinecap: "round",
2873
+ strokeLinejoin: "round",
2874
+ className: "h-3.5 w-3.5",
2875
+ "aria-hidden": true
2876
+ };
2877
+ if (kind === "remote") {
2878
+ return /* @__PURE__ */ jsxs("svg", { ...common, children: [
2879
+ /* @__PURE__ */ jsx("rect", { x: "2", y: "3", width: "20", height: "14", rx: "2" }),
2880
+ /* @__PURE__ */ jsx("path", { d: "M8 21h8M12 17v4" })
2881
+ ] });
2882
+ }
2883
+ if (kind === "human") {
2884
+ return /* @__PURE__ */ jsxs("svg", { ...common, children: [
2885
+ /* @__PURE__ */ jsx("path", { d: "M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2" }),
2886
+ /* @__PURE__ */ jsx("circle", { cx: "9", cy: "7", r: "4" })
2887
+ ] });
2888
+ }
2889
+ return /* @__PURE__ */ jsxs("svg", { ...common, children: [
2890
+ /* @__PURE__ */ jsx("rect", { x: "3", y: "11", width: "18", height: "10", rx: "2" }),
2891
+ /* @__PURE__ */ jsx("circle", { cx: "12", cy: "5", r: "2" }),
2892
+ /* @__PURE__ */ jsx("path", { d: "M12 7v4M8 16h.01M16 16h.01" })
2893
+ ] });
2894
+ }
2895
+ function OnlineDot({ online }) {
2896
+ if (online === void 0) return null;
2897
+ return /* @__PURE__ */ jsx(
2898
+ "span",
2899
+ {
2900
+ "aria-label": online ? "connected" : "offline",
2901
+ className: cn9(
2902
+ "h-1.5 w-1.5 shrink-0 rounded-full",
2903
+ online ? "bg-success" : "bg-muted-foreground"
2904
+ )
2905
+ }
2906
+ );
2907
+ }
2908
+ function RemoteBadge() {
2909
+ return /* @__PURE__ */ jsx("span", { className: "shrink-0 rounded-full border border-accent/40 bg-accent/15 px-1.5 py-px text-[10px] font-semibold leading-4 text-accent", children: "Remote" });
2910
+ }
2911
+ function LaneAgentPicker({
2912
+ selected,
2913
+ options,
2914
+ onSelect,
2915
+ canEdit = true,
2916
+ lockedReason,
2917
+ onEditAgent,
2918
+ className
2919
+ }) {
2920
+ const [open, setOpen] = useState(false);
2921
+ const [placement, setPlacement] = useState(null);
2922
+ const rootRef = useRef(null);
2923
+ const triggerRef = useRef(null);
2924
+ const editable = canEdit && !!onSelect && (options?.length ?? 0) > 0;
2925
+ const openMenu = () => {
2926
+ const el = triggerRef.current;
2927
+ if (el && typeof window !== "undefined") {
2928
+ const rect = el.getBoundingClientRect();
2929
+ const vw = window.innerWidth || 0;
2930
+ const vh = window.innerHeight || 0;
2931
+ const margin = 8;
2932
+ const gap = 4;
2933
+ const desired = 288;
2934
+ const width = Math.max(
2935
+ Math.min(rect.width || 224, Math.max(vw - margin * 2, 160)),
2936
+ 160
2937
+ );
2938
+ const left = Math.min(
2939
+ Math.max(rect.left, margin),
2940
+ Math.max(vw - width - margin, margin)
2941
+ );
2942
+ const spaceBelow = vh - rect.bottom - gap - margin;
2943
+ const spaceAbove = rect.top - gap - margin;
2944
+ const flipUp = spaceBelow < desired && spaceAbove > spaceBelow;
2945
+ const maxHeight = Math.max(
2946
+ Math.min(desired, flipUp ? spaceAbove : spaceBelow),
2947
+ 120
2948
+ );
2949
+ setPlacement(
2950
+ flipUp ? { bottom: Math.max(vh - rect.top + gap, margin), left, width, maxHeight } : { top: rect.bottom + gap, left, width, maxHeight }
2951
+ );
2952
+ } else {
2953
+ setPlacement(null);
2954
+ }
2955
+ setOpen(true);
2956
+ };
2957
+ useEffect(() => {
2958
+ if (!open) return;
2959
+ const onDocDown = (e) => {
2960
+ if (rootRef.current && !rootRef.current.contains(e.target)) {
2961
+ setOpen(false);
2962
+ }
2963
+ };
2964
+ const onKey = (e) => {
2965
+ if (e.key === "Escape") setOpen(false);
2966
+ };
2967
+ document.addEventListener("mousedown", onDocDown);
2968
+ document.addEventListener("touchstart", onDocDown);
2969
+ document.addEventListener("keydown", onKey);
2970
+ return () => {
2971
+ document.removeEventListener("mousedown", onDocDown);
2972
+ document.removeEventListener("touchstart", onDocDown);
2973
+ document.removeEventListener("keydown", onKey);
2974
+ };
2975
+ }, [open]);
2976
+ const kind = selected?.kind ?? "cadra";
2977
+ const displayLabel = selected?.label ?? "Unassigned";
2978
+ const face = /* @__PURE__ */ jsxs(Fragment, { children: [
2979
+ /* @__PURE__ */ jsx(
2980
+ "span",
2981
+ {
2982
+ className: cn9(
2983
+ "grid h-5 w-5 shrink-0 place-items-center rounded-full",
2984
+ kind === "cadra" ? "bg-primary/15 text-primary" : "bg-secondary text-foreground"
2985
+ ),
2986
+ children: /* @__PURE__ */ jsx(AgentKindIcon, { kind })
2987
+ }
2988
+ ),
2989
+ /* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate text-left", children: displayLabel }),
2990
+ kind === "remote" && /* @__PURE__ */ jsx(RemoteBadge, {}),
2991
+ /* @__PURE__ */ jsx(OnlineDot, { online: selected?.online })
2992
+ ] });
2993
+ const grouped = AGENT_GROUPS.map((group) => ({
2994
+ ...group,
2995
+ items: (options ?? []).filter((opt) => (opt.kind ?? "cadra") === group.kind)
2996
+ })).filter((group) => group.items.length > 0);
2997
+ const menuStyle = placement ? {
2998
+ position: "fixed",
2999
+ top: placement.top,
3000
+ bottom: placement.bottom,
3001
+ left: placement.left,
3002
+ width: placement.width,
3003
+ maxHeight: placement.maxHeight
3004
+ } : void 0;
3005
+ return /* @__PURE__ */ jsxs("div", { ref: rootRef, className: cn9("relative mt-2 w-full", className), children: [
3006
+ editable ? /* @__PURE__ */ jsxs(
3007
+ "button",
3008
+ {
3009
+ ref: triggerRef,
3010
+ type: "button",
3011
+ onClick: () => open ? setOpen(false) : openMenu(),
3012
+ "aria-haspopup": "listbox",
3013
+ "aria-expanded": open,
3014
+ "aria-label": `Assigned agent: ${displayLabel}. Change`,
3015
+ className: cn9(
3016
+ "flex min-h-11 w-full items-center gap-1.5 rounded-lg border border-border bg-card px-2",
3017
+ "text-xs font-semibold text-foreground",
3018
+ "hover:border-primary/50 focus:outline-none focus:ring-2 focus:ring-ring/50"
3019
+ ),
3020
+ children: [
3021
+ face,
3022
+ /* @__PURE__ */ jsx(
3023
+ "svg",
3024
+ {
3025
+ viewBox: "0 0 24 24",
3026
+ fill: "none",
3027
+ stroke: "currentColor",
3028
+ strokeWidth: 2,
3029
+ strokeLinecap: "round",
3030
+ strokeLinejoin: "round",
3031
+ "aria-hidden": true,
3032
+ className: "h-3.5 w-3.5 shrink-0 text-muted-foreground",
3033
+ children: /* @__PURE__ */ jsx("path", { d: "M6 9l6 6 6-6" })
3034
+ }
3035
+ )
3036
+ ]
3037
+ }
3038
+ ) : /* @__PURE__ */ jsxs(
3039
+ "div",
3040
+ {
3041
+ role: "group",
3042
+ "aria-label": `Assigned agent: ${displayLabel}`,
3043
+ className: cn9(
3044
+ "flex min-h-11 w-full items-center gap-1.5 rounded-lg border border-border bg-muted/60 px-2",
3045
+ "text-xs font-semibold text-muted-foreground"
3046
+ ),
3047
+ children: [
3048
+ face,
3049
+ /* @__PURE__ */ jsxs(
3050
+ "svg",
3051
+ {
3052
+ viewBox: "0 0 24 24",
3053
+ fill: "none",
3054
+ stroke: "currentColor",
3055
+ strokeWidth: 2,
3056
+ strokeLinecap: "round",
3057
+ strokeLinejoin: "round",
3058
+ "aria-hidden": true,
3059
+ className: "h-3.5 w-3.5 shrink-0",
3060
+ children: [
3061
+ /* @__PURE__ */ jsx("rect", { x: "5", y: "11", width: "14", height: "10", rx: "2" }),
3062
+ /* @__PURE__ */ jsx("path", { d: "M8 11V7a4 4 0 0 1 8 0v4" })
3063
+ ]
3064
+ }
3065
+ )
3066
+ ]
3067
+ }
3068
+ ),
3069
+ !editable && lockedReason && /* @__PURE__ */ jsxs("div", { className: "mt-1.5 flex items-center gap-1 text-[11px] text-muted-foreground", children: [
3070
+ /* @__PURE__ */ jsxs(
3071
+ "svg",
3072
+ {
3073
+ viewBox: "0 0 24 24",
3074
+ fill: "none",
3075
+ stroke: "currentColor",
3076
+ strokeWidth: 2,
3077
+ strokeLinecap: "round",
3078
+ strokeLinejoin: "round",
3079
+ "aria-hidden": true,
3080
+ className: "h-3 w-3 shrink-0",
3081
+ children: [
3082
+ /* @__PURE__ */ jsx("rect", { x: "5", y: "11", width: "14", height: "10", rx: "2" }),
3083
+ /* @__PURE__ */ jsx("path", { d: "M8 11V7a4 4 0 0 1 8 0v4" })
3084
+ ]
3085
+ }
3086
+ ),
3087
+ /* @__PURE__ */ jsx("span", { children: lockedReason })
3088
+ ] }),
3089
+ editable && open && /* @__PURE__ */ jsxs(
3090
+ "div",
3091
+ {
3092
+ style: menuStyle,
3093
+ className: cn9(
3094
+ "flex flex-col overflow-hidden rounded-lg border border-border bg-popover shadow-lg",
3095
+ menuStyle ? "z-50" : "absolute z-20 mt-1 max-h-64 w-full"
3096
+ ),
3097
+ children: [
3098
+ /* @__PURE__ */ jsx(
3099
+ "ul",
3100
+ {
3101
+ role: "listbox",
3102
+ "aria-label": "Assign an agent",
3103
+ className: "min-h-0 flex-1 overflow-y-auto p-1",
3104
+ children: grouped.map((group) => /* @__PURE__ */ jsxs("li", { role: "presentation", children: [
3105
+ /* @__PURE__ */ jsx("div", { className: "px-2 pb-1 pt-2 text-[10px] font-semibold uppercase tracking-wide text-muted-foreground", children: group.label }),
3106
+ /* @__PURE__ */ jsx("ul", { role: "group", "aria-label": group.label, children: group.items.map((opt) => {
3107
+ const isSelected = opt.id === selected?.id;
3108
+ const optKind = opt.kind ?? "cadra";
3109
+ return /* @__PURE__ */ jsx("li", { role: "option", "aria-selected": isSelected, children: /* @__PURE__ */ jsxs(
3110
+ "button",
3111
+ {
3112
+ type: "button",
3113
+ onClick: () => {
3114
+ onSelect(opt.id);
3115
+ setOpen(false);
3116
+ },
3117
+ className: cn9(
3118
+ "flex min-h-11 w-full items-center gap-1.5 rounded-md px-2 text-left text-xs font-medium",
3119
+ "hover:bg-muted focus:outline-none focus:ring-2 focus:ring-ring/50",
3120
+ isSelected ? "text-primary" : "text-foreground"
3121
+ ),
3122
+ children: [
3123
+ /* @__PURE__ */ jsx(
3124
+ "span",
3125
+ {
3126
+ className: cn9(
3127
+ "grid h-5 w-5 shrink-0 place-items-center rounded-full",
3128
+ optKind === "cadra" ? "bg-primary/15 text-primary" : "bg-secondary text-foreground"
3129
+ ),
3130
+ children: /* @__PURE__ */ jsx(AgentKindIcon, { kind: optKind })
3131
+ }
3132
+ ),
3133
+ /* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate", children: opt.label }),
3134
+ optKind === "remote" && /* @__PURE__ */ jsx(RemoteBadge, {}),
3135
+ /* @__PURE__ */ jsx(OnlineDot, { online: opt.online }),
3136
+ isSelected && /* @__PURE__ */ jsx(
3137
+ "svg",
3138
+ {
3139
+ viewBox: "0 0 24 24",
3140
+ fill: "none",
3141
+ stroke: "currentColor",
3142
+ strokeWidth: 2,
3143
+ strokeLinecap: "round",
3144
+ strokeLinejoin: "round",
3145
+ "aria-hidden": true,
3146
+ className: "h-3.5 w-3.5 shrink-0",
3147
+ children: /* @__PURE__ */ jsx("path", { d: "M20 6L9 17l-5-5" })
3148
+ }
3149
+ )
3150
+ ]
3151
+ }
3152
+ ) }, opt.id);
3153
+ }) })
3154
+ ] }, group.kind))
3155
+ }
3156
+ ),
3157
+ onEditAgent && selected && /* @__PURE__ */ jsx("div", { className: "border-t border-border p-1", children: /* @__PURE__ */ jsxs(
3158
+ "button",
3159
+ {
3160
+ type: "button",
3161
+ onClick: () => {
3162
+ onEditAgent(selected.id);
3163
+ setOpen(false);
3164
+ },
3165
+ className: cn9(
3166
+ "flex min-h-11 w-full items-center gap-1.5 rounded-md px-2 text-left text-xs font-medium text-foreground",
3167
+ "hover:bg-muted focus:outline-none focus:ring-2 focus:ring-ring/50"
3168
+ ),
3169
+ children: [
3170
+ /* @__PURE__ */ jsx(
3171
+ "svg",
3172
+ {
3173
+ viewBox: "0 0 24 24",
3174
+ fill: "none",
3175
+ stroke: "currentColor",
3176
+ strokeWidth: 2,
3177
+ strokeLinecap: "round",
3178
+ strokeLinejoin: "round",
3179
+ "aria-hidden": true,
3180
+ className: "h-3.5 w-3.5 shrink-0 text-muted-foreground",
3181
+ children: /* @__PURE__ */ jsx("path", { d: "M17 3a2.85 2.83 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5Z" })
3182
+ }
3183
+ ),
3184
+ "Edit this agent"
3185
+ ]
3186
+ }
3187
+ ) })
3188
+ ]
3189
+ }
3190
+ )
3191
+ ] });
3192
+ }
3193
+ function dueStateFor(card, lane, now) {
3194
+ const rs = card.run?.status;
3195
+ if (rs === "completed" && !lane.terminal) return "ready";
3196
+ if (card.nextDueAt) {
3197
+ const t = Date.parse(card.nextDueAt);
3198
+ if (!Number.isNaN(t) && t <= now) return "due";
3199
+ }
3200
+ if (lane.trigger === "scheduled" && !rs) return "waiting";
3201
+ return void 0;
3202
+ }
2735
3203
  function KanbanColumn({
2736
3204
  lane,
2737
3205
  cards,
@@ -2739,29 +3207,100 @@ function KanbanColumn({
2739
3207
  selectedCardId,
2740
3208
  onCreateCard,
2741
3209
  showCreate,
3210
+ count,
3211
+ agentPicker,
3212
+ canEditHandlers,
3213
+ draggableCards,
3214
+ dragActive,
3215
+ dropAllowed,
3216
+ onCardDragStart,
3217
+ onCardDragEnd,
3218
+ onLaneDrop,
2742
3219
  className
2743
3220
  }) {
3221
+ const headerCount = count ?? cards.length;
3222
+ const isScheduled = lane.trigger === "scheduled";
3223
+ const showCapMeter = count !== void 0 && typeof lane.cap === "number" && lane.cap > 0;
3224
+ const showAgentBadge = lane.handler?.type === "cadra_agent" && !agentPicker;
3225
+ const now = Date.now();
2744
3226
  return /* @__PURE__ */ jsxs(
2745
3227
  "div",
2746
3228
  {
3229
+ onDragOver: dropAllowed ? (e) => {
3230
+ e.preventDefault();
3231
+ e.dataTransfer.dropEffect = "move";
3232
+ } : void 0,
3233
+ onDrop: dropAllowed && onLaneDrop ? (e) => {
3234
+ e.preventDefault();
3235
+ onLaneDrop(lane);
3236
+ } : void 0,
2747
3237
  className: cn9(
2748
- "flex w-[85vw] max-w-sm snap-start shrink-0 flex-col rounded-xl bg-gray-50 dark:bg-gray-900/50 border border-gray-200 dark:border-gray-800 sm:w-72 sm:max-w-none",
3238
+ "flex w-[85vw] max-w-sm snap-start shrink-0 flex-col rounded-xl bg-muted/40 border border-border transition sm:w-72 sm:max-w-none",
3239
+ isScheduled && "border-border",
3240
+ // MV4 drag affordance: allowed targets glow; other lanes dim so the
3241
+ // valid drop zones read at a glance.
3242
+ dragActive && dropAllowed && "border-primary/60 ring-2 ring-primary/40 bg-primary/5",
3243
+ dragActive && !dropAllowed && "opacity-55",
2749
3244
  className
2750
3245
  ),
2751
3246
  children: [
2752
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-2 px-3 py-2.5 border-b border-gray-200 dark:border-gray-800", children: [
2753
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 min-w-0", children: [
2754
- /* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold text-gray-700 dark:text-gray-200 truncate", children: lane.label }),
2755
- lane.handler?.type === "cadra_agent" && /* @__PURE__ */ jsx(
2756
- "span",
2757
- {
2758
- title: lane.handler.agentRef ? `Agent: ${lane.handler.agentRef}` : "Agent lane",
2759
- className: "rounded bg-violet-100 dark:bg-violet-950/40 px-1.5 py-0.5 text-[10px] font-medium text-violet-700 dark:text-violet-300",
2760
- children: "agent"
2761
- }
2762
- )
3247
+ /* @__PURE__ */ jsxs("div", { className: "border-b border-border px-3 py-2.5", children: [
3248
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-2", children: [
3249
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 min-w-0", children: [
3250
+ /* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold text-foreground truncate", children: lane.label }),
3251
+ showAgentBadge && /* @__PURE__ */ jsx(
3252
+ "span",
3253
+ {
3254
+ title: lane.handler?.agentRef ? `Agent: ${lane.handler.agentRef}` : "Agent lane",
3255
+ className: cn9(
3256
+ // A static lane marker — NOT a run state. Uses neutral tokens,
3257
+ // never `info` (which means "running" in the F3 vocabulary).
3258
+ "rounded px-1.5 py-0.5 text-[10px] font-medium",
3259
+ badgeVariantClass("neutral")
3260
+ ),
3261
+ children: "agent"
3262
+ }
3263
+ )
3264
+ ] }),
3265
+ /* @__PURE__ */ jsx("span", { className: "shrink-0 rounded-full bg-secondary px-2 py-0.5 text-[11px] font-medium text-muted-foreground tabular-nums", children: headerCount })
2763
3266
  ] }),
2764
- /* @__PURE__ */ jsx("span", { className: "shrink-0 rounded-full bg-gray-200 dark:bg-gray-800 px-2 py-0.5 text-[11px] font-medium text-gray-600 dark:text-gray-400", children: cards.length })
3267
+ isScheduled && /* @__PURE__ */ jsx("div", { className: "mt-2 flex items-center gap-2 flex-wrap", children: /* @__PURE__ */ jsxs(
3268
+ "span",
3269
+ {
3270
+ className: cn9(
3271
+ "inline-flex items-center gap-1 rounded-full px-2 py-0.5 text-[11px] font-semibold",
3272
+ badgeVariantClass("neutral")
3273
+ ),
3274
+ children: [
3275
+ /* @__PURE__ */ jsxs(
3276
+ "svg",
3277
+ {
3278
+ viewBox: "0 0 24 24",
3279
+ fill: "none",
3280
+ stroke: "currentColor",
3281
+ strokeWidth: "2",
3282
+ strokeLinecap: "round",
3283
+ strokeLinejoin: "round",
3284
+ "aria-hidden": true,
3285
+ className: "h-3 w-3",
3286
+ children: [
3287
+ /* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "9" }),
3288
+ /* @__PURE__ */ jsx("path", { d: "M12 7v5l3 2" })
3289
+ ]
3290
+ }
3291
+ ),
3292
+ "runs on schedule"
3293
+ ]
3294
+ }
3295
+ ) }),
3296
+ showCapMeter && /* @__PURE__ */ jsx(CapMeter, { count: headerCount, cap: lane.cap }),
3297
+ agentPicker && /* @__PURE__ */ jsx(
3298
+ LaneAgentPicker,
3299
+ {
3300
+ ...agentPicker,
3301
+ canEdit: agentPicker.canEdit ?? canEditHandlers
3302
+ }
3303
+ )
2765
3304
  ] }),
2766
3305
  /* @__PURE__ */ jsxs("div", { className: "flex flex-1 flex-col gap-2 overflow-y-auto p-2", children: [
2767
3306
  cards.map((card) => /* @__PURE__ */ jsx(
@@ -2769,21 +3308,26 @@ function KanbanColumn({
2769
3308
  {
2770
3309
  card,
2771
3310
  onClick: onCardClick,
2772
- selected: card.id === selectedCardId
3311
+ selected: card.id === selectedCardId,
3312
+ dueState: dueStateFor(card, lane, now),
3313
+ draggable: draggableCards,
3314
+ onDragStart: onCardDragStart,
3315
+ onDragEnd: onCardDragEnd
2773
3316
  },
2774
3317
  card.id
2775
3318
  )),
2776
- cards.length === 0 && /* @__PURE__ */ jsx("p", { className: "px-1 py-4 text-center text-xs text-gray-400 dark:text-gray-600", children: "No cards" }),
3319
+ cards.length === 0 && /* @__PURE__ */ jsx("p", { className: "px-1 py-4 text-center text-xs text-muted-foreground", children: "No cards" }),
2777
3320
  showCreate && onCreateCard && /* @__PURE__ */ jsx(
2778
3321
  "button",
2779
3322
  {
2780
3323
  type: "button",
2781
3324
  onClick: () => onCreateCard(lane),
2782
3325
  className: cn9(
2783
- "mt-1 w-full rounded-lg border border-dashed border-gray-300 dark:border-gray-700 px-3 py-2",
2784
- "text-sm font-medium text-gray-500 dark:text-gray-400",
2785
- "hover:border-gray-400 hover:text-gray-700 dark:hover:text-gray-200 transition",
2786
- "focus:outline-none focus:ring-2 focus:ring-blue-500/40"
3326
+ // ≥44px touch (M12/F5) — density kept via padding, not height.
3327
+ "mt-1 inline-flex min-h-11 w-full items-center justify-center rounded-lg border border-dashed border-border px-3 py-2",
3328
+ "text-sm font-medium text-muted-foreground",
3329
+ "hover:border-primary/50 hover:text-foreground transition",
3330
+ "focus:outline-none focus:ring-2 focus:ring-ring/40"
2787
3331
  ),
2788
3332
  children: "+ New card"
2789
3333
  }
@@ -2798,14 +3342,14 @@ function SkeletonCard() {
2798
3342
  "div",
2799
3343
  {
2800
3344
  className: cn9(
2801
- "w-full rounded-lg border border-gray-200 dark:border-gray-700",
2802
- "bg-white dark:bg-gray-900 p-3 shadow-sm"
3345
+ "w-full rounded-lg border border-border",
3346
+ "bg-card p-3 shadow-sm"
2803
3347
  ),
2804
3348
  "aria-hidden": "true",
2805
3349
  children: [
2806
- /* @__PURE__ */ jsx("div", { className: "h-3.5 w-3/4 rounded bg-gray-200 dark:bg-gray-800" }),
2807
- /* @__PURE__ */ jsx("div", { className: "mt-2 h-2.5 w-1/2 rounded bg-gray-200 dark:bg-gray-800" }),
2808
- /* @__PURE__ */ jsx("div", { className: "mt-3 h-4 w-1/3 rounded bg-gray-200 dark:bg-gray-800" })
3350
+ /* @__PURE__ */ jsx("div", { className: "h-3.5 w-3/4 rounded bg-muted" }),
3351
+ /* @__PURE__ */ jsx("div", { className: "mt-2 h-2.5 w-1/2 rounded bg-muted" }),
3352
+ /* @__PURE__ */ jsx("div", { className: "mt-3 h-4 w-1/3 rounded bg-muted" })
2809
3353
  ]
2810
3354
  }
2811
3355
  );
@@ -2816,14 +3360,14 @@ function SkeletonColumn({ cardCount }) {
2816
3360
  {
2817
3361
  className: cn9(
2818
3362
  "flex w-72 shrink-0 flex-col rounded-xl",
2819
- "bg-gray-50 dark:bg-gray-900/50",
2820
- "border border-gray-200 dark:border-gray-800"
3363
+ "bg-muted/40",
3364
+ "border border-border"
2821
3365
  ),
2822
3366
  "aria-hidden": "true",
2823
3367
  children: [
2824
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-2 px-3 py-2.5 border-b border-gray-200 dark:border-gray-800", children: [
2825
- /* @__PURE__ */ jsx("div", { className: "h-3.5 w-24 rounded bg-gray-200 dark:bg-gray-800" }),
2826
- /* @__PURE__ */ jsx("div", { className: "h-4 w-6 rounded-full bg-gray-200 dark:bg-gray-800" })
3368
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-2 px-3 py-2.5 border-b border-border", children: [
3369
+ /* @__PURE__ */ jsx("div", { className: "h-3.5 w-24 rounded bg-muted" }),
3370
+ /* @__PURE__ */ jsx("div", { className: "h-4 w-6 rounded-full bg-muted" })
2827
3371
  ] }),
2828
3372
  /* @__PURE__ */ jsx("div", { className: "flex flex-1 flex-col gap-2 p-2", children: Array.from({ length: cardCount }).map((_, i) => (
2829
3373
  // eslint-disable-next-line react/no-array-index-key
@@ -2852,6 +3396,67 @@ function KanbanBoardSkeleton({
2852
3396
  }
2853
3397
  );
2854
3398
  }
3399
+ function formatCountdown(nextTickAt) {
3400
+ if (!nextTickAt) return null;
3401
+ const target = Date.parse(nextTickAt);
3402
+ if (Number.isNaN(target)) return null;
3403
+ const ms = target - Date.now();
3404
+ if (ms <= 0) return "any moment";
3405
+ const mins = Math.round(ms / 6e4);
3406
+ if (mins < 1) return "under a minute";
3407
+ if (mins < 60) return `${mins}m`;
3408
+ const hrs = Math.floor(mins / 60);
3409
+ const rem = mins % 60;
3410
+ return rem > 0 ? `${hrs}h ${rem}m` : `${hrs}h`;
3411
+ }
3412
+ var DOT_TONE = {
3413
+ "on-schedule": "bg-success",
3414
+ due: "bg-info",
3415
+ paused: "bg-muted-foreground"
3416
+ };
3417
+ var DEFAULT_LABEL = {
3418
+ "on-schedule": "On schedule",
3419
+ due: "Running now",
3420
+ paused: "Paused"
3421
+ };
3422
+ function ScheduleStatus({
3423
+ nextTickAt,
3424
+ dueByLane,
3425
+ label,
3426
+ tone = "on-schedule",
3427
+ className
3428
+ }) {
3429
+ const countdown = formatCountdown(nextTickAt);
3430
+ const totalDue = dueByLane ? Object.values(dueByLane).reduce((a, b) => a + b, 0) : 0;
3431
+ return /* @__PURE__ */ jsxs(
3432
+ "div",
3433
+ {
3434
+ className: cn9(
3435
+ "flex items-center gap-2 text-xs text-muted-foreground",
3436
+ className
3437
+ ),
3438
+ children: [
3439
+ /* @__PURE__ */ jsx(
3440
+ "span",
3441
+ {
3442
+ "aria-hidden": true,
3443
+ className: cn9("h-2 w-2 shrink-0 rounded-full", DOT_TONE[tone])
3444
+ }
3445
+ ),
3446
+ /* @__PURE__ */ jsx("span", { className: "font-medium text-foreground", children: label ?? DEFAULT_LABEL[tone] }),
3447
+ countdown && /* @__PURE__ */ jsxs("span", { className: "tabular-nums", children: [
3448
+ "\xB7 next run in ",
3449
+ countdown
3450
+ ] }),
3451
+ totalDue > 0 && /* @__PURE__ */ jsxs("span", { className: "tabular-nums", children: [
3452
+ "\xB7 ",
3453
+ totalDue,
3454
+ " due"
3455
+ ] })
3456
+ ]
3457
+ }
3458
+ );
3459
+ }
2855
3460
  function KanbanBoard({
2856
3461
  adapter,
2857
3462
  onCreateCard,
@@ -2862,6 +3467,13 @@ function KanbanBoard({
2862
3467
  enableEdit = true,
2863
3468
  enableAdvance = true,
2864
3469
  pollIntervalMs,
3470
+ laneCounts,
3471
+ canEditHandlers,
3472
+ laneAgentPicker,
3473
+ scheduleStatus,
3474
+ focusCardId,
3475
+ renderCardDrawer,
3476
+ enableDrag,
2865
3477
  className
2866
3478
  }) {
2867
3479
  const {
@@ -2876,8 +3488,19 @@ function KanbanBoard({
2876
3488
  createCard,
2877
3489
  updateCard,
2878
3490
  transitionCard,
3491
+ deleteCard,
2879
3492
  handleRunTerminal
2880
3493
  } = useKanbanBoard({ adapter, onError, onCardCreated, pollIntervalMs });
3494
+ const lastFocusedRef = useRef(void 0);
3495
+ useEffect(() => {
3496
+ if (!focusCardId) {
3497
+ lastFocusedRef.current = void 0;
3498
+ return;
3499
+ }
3500
+ if (lastFocusedRef.current === focusCardId) return;
3501
+ lastFocusedRef.current = focusCardId;
3502
+ void openCard(focusCardId);
3503
+ }, [focusCardId]);
2881
3504
  const handleCardClick = useCallback(
2882
3505
  (card) => {
2883
3506
  void openCard(card.id);
@@ -2910,14 +3533,53 @@ function KanbanBoard({
2910
3533
  (cardId, toLane) => transitionCard({ cardId, targetLane: toLane }),
2911
3534
  [transitionCard]
2912
3535
  );
3536
+ const [dragging, setDragging] = useState(null);
3537
+ const draggingRef = useRef(null);
3538
+ const dropInFlightRef = useRef(null);
3539
+ const allowedDropLanes = useMemo(() => {
3540
+ if (!dragging || !board?.transitions) return /* @__PURE__ */ new Set();
3541
+ return new Set(
3542
+ board.transitions.filter((t) => t.fromLane === dragging.lane && t.manualAllowed).map((t) => t.toLane)
3543
+ );
3544
+ }, [dragging, board?.transitions]);
3545
+ const handleCardDragStart = useCallback((card) => {
3546
+ const d = { id: card.id, lane: card.lane };
3547
+ draggingRef.current = d;
3548
+ setDragging(d);
3549
+ }, []);
3550
+ const handleCardDragEnd = useCallback(() => {
3551
+ draggingRef.current = null;
3552
+ setDragging(null);
3553
+ }, []);
3554
+ const handleLaneDrop = useCallback(
3555
+ (lane) => {
3556
+ const cur = draggingRef.current;
3557
+ draggingRef.current = null;
3558
+ setDragging(null);
3559
+ if (!cur || lane.key === cur.lane) return;
3560
+ if (dropInFlightRef.current === cur.id) return;
3561
+ const allowed = (board?.transitions ?? []).some(
3562
+ (t) => t.fromLane === cur.lane && t.toLane === lane.key && t.manualAllowed
3563
+ );
3564
+ if (!allowed) return;
3565
+ dropInFlightRef.current = cur.id;
3566
+ void Promise.resolve(transitionCard({ cardId: cur.id, targetLane: lane.key })).finally(
3567
+ () => {
3568
+ if (dropInFlightRef.current === cur.id) dropInFlightRef.current = null;
3569
+ }
3570
+ );
3571
+ },
3572
+ [board?.transitions, transitionCard]
3573
+ );
2913
3574
  const initialLaneKey = lanes.find((l) => !l.terminal)?.key ?? lanes[0]?.key;
2914
3575
  const selectedLane = selectedCard ? lanes.find((l) => l.key === selectedCard.lane) : void 0;
2915
3576
  const canAdvanceSelected = !!selectedLane && !selectedLane.terminal;
2916
3577
  return /* @__PURE__ */ jsxs("div", { className: cn9("flex h-full w-full flex-col", className), children: [
2917
- error && /* @__PURE__ */ jsx("div", { className: "m-3 rounded-md border border-red-200 dark:border-red-900 bg-red-50 dark:bg-red-950/30 px-3 py-2 text-sm text-red-700 dark:text-red-300", children: error }),
3578
+ error && /* @__PURE__ */ jsx("div", { className: "m-3 rounded-md border border-destructive/40 bg-destructive/10 px-3 py-2 text-sm text-destructive", children: error }),
3579
+ scheduleStatus && /* @__PURE__ */ jsx("div", { className: "shrink-0 border-b border-border px-4 py-2", children: /* @__PURE__ */ jsx(ScheduleStatus, { ...scheduleStatus }) }),
2918
3580
  /* @__PURE__ */ jsxs("div", { className: "flex flex-1 overflow-hidden", children: [
2919
3581
  lanes.length === 0 && isLoading ? /* @__PURE__ */ jsx(KanbanBoardSkeleton, { className: "flex-1 overflow-x-auto p-4" }) : /* @__PURE__ */ jsxs("div", { className: "flex flex-1 items-start gap-4 overflow-x-auto p-4 snap-x snap-mandatory no-scrollbar sm:snap-none", children: [
2920
- lanes.length === 0 && /* @__PURE__ */ jsx("p", { className: "text-sm text-gray-400 dark:text-gray-600", children: "No lanes configured." }),
3582
+ lanes.length === 0 && /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: "No lanes configured." }),
2921
3583
  lanes.map((lane) => /* @__PURE__ */ jsx(
2922
3584
  KanbanColumn,
2923
3585
  {
@@ -2926,12 +3588,35 @@ function KanbanBoard({
2926
3588
  onCardClick: handleCardClick,
2927
3589
  selectedCardId: selectedCard?.id,
2928
3590
  onCreateCard: handleCreate,
2929
- showCreate: enableCreate && lane.key === initialLaneKey
3591
+ showCreate: enableCreate && lane.key === initialLaneKey,
3592
+ count: laneCounts?.[lane.key],
3593
+ agentPicker: laneAgentPicker?.(lane),
3594
+ canEditHandlers,
3595
+ draggableCards: enableDrag && enableAdvance,
3596
+ dragActive: !!dragging,
3597
+ dropAllowed: !!dragging && dragging.lane !== lane.key && allowedDropLanes.has(lane.key),
3598
+ onCardDragStart: handleCardDragStart,
3599
+ onCardDragEnd: handleCardDragEnd,
3600
+ onLaneDrop: handleLaneDrop
2930
3601
  },
2931
3602
  lane.id ?? lane.key
2932
3603
  ))
2933
3604
  ] }),
2934
- selectedCard && /* @__PURE__ */ jsx(
3605
+ selectedCard && (renderCardDrawer ? renderCardDrawer({
3606
+ card: selectedCard,
3607
+ adapter,
3608
+ lanes,
3609
+ transitions: enableAdvance ? board?.transitions : void 0,
3610
+ canAdvance: canAdvanceSelected,
3611
+ onClose: closeCard,
3612
+ onRetry: onRetryCard ? handleRetry : void 0,
3613
+ onUpdate: enableEdit ? handleUpdate : void 0,
3614
+ onAdvance: enableAdvance ? handleAdvance : void 0,
3615
+ onTransition: enableAdvance ? handleTransition : void 0,
3616
+ onDelete: adapter.deleteCard ? deleteCard : void 0,
3617
+ onRunTerminal: handleRunTerminal,
3618
+ onError
3619
+ }) : /* @__PURE__ */ jsx(
2935
3620
  CardDrawer,
2936
3621
  {
2937
3622
  card: selectedCard,
@@ -2947,7 +3632,7 @@ function KanbanBoard({
2947
3632
  onRunTerminal: handleRunTerminal,
2948
3633
  onError
2949
3634
  }
2950
- )
3635
+ ))
2951
3636
  ] })
2952
3637
  ] });
2953
3638
  }
@@ -2972,6 +3657,7 @@ function composeAdapter(source, boardConfigUuid, version, cardSource) {
2972
3657
  transitionCard(input) {
2973
3658
  return cardSource.transitionCard(input);
2974
3659
  },
3660
+ ...cardSource.deleteCard ? { deleteCard: (id) => cardSource.deleteCard(id) } : {},
2975
3661
  ...cardSource.createRunStream ? { createRunStream: (id) => cardSource.createRunStream(id) } : {}
2976
3662
  };
2977
3663
  }
@@ -3668,15 +4354,6 @@ var ExecutionStreamControllerImpl = class {
3668
4354
  sseClient = null;
3669
4355
  status = "connecting";
3670
4356
  reconnectAttempts = 0;
3671
- // CAD-39 / webchat dual-plane create-connect race. `hasConnected` flips true on
3672
- // the first successful SSE open; `notFoundRetries` bounds retries of a 404
3673
- // encountered BEFORE we ever connect (the run row is persisted slightly AFTER
3674
- // the client opens the stream — the norm on the webchat path where msg-api owns
3675
- // the executionId and cadra persists the agent_executions row via an async
3676
- // worker callback). See handleConnectionError.
3677
- hasConnected = false;
3678
- notFoundRetries = 0;
3679
- maxConnectRaceRetries = 3;
3680
4357
  seenEventIds = /* @__PURE__ */ new Set();
3681
4358
  _lastEventId = null;
3682
4359
  activityTimeoutId = null;
@@ -3816,8 +4493,6 @@ var ExecutionStreamControllerImpl = class {
3816
4493
  if (connected) {
3817
4494
  this.status = "connected";
3818
4495
  this.reconnectAttempts = 0;
3819
- this.hasConnected = true;
3820
- this.notFoundRetries = 0;
3821
4496
  this.resetActivityTimeout();
3822
4497
  }
3823
4498
  this.emit("connectionChange", { connected, usingFallback: false });
@@ -4107,38 +4782,6 @@ var ExecutionStreamControllerImpl = class {
4107
4782
  return;
4108
4783
  }
4109
4784
  const msg = error instanceof Error ? error.message : String(error);
4110
- const isNotFound = /\b404\b/.test(msg);
4111
- const isAuthFailure = /\b40[13]\b/.test(msg) || /INVALID_API_KEY|INVALID_REQUEST/.test(msg);
4112
- if (isNotFound && !isAuthFailure && !this.hasConnected) {
4113
- if (this.notFoundRetries < this.maxConnectRaceRetries) {
4114
- this.notFoundRetries++;
4115
- this.status = "reconnecting";
4116
- const delay = Math.min(
4117
- this.config.reconnectBaseDelayMs * this.notFoundRetries,
4118
- this.config.reconnectMaxDelayMs
4119
- );
4120
- await new Promise((resolve) => setTimeout(resolve, delay));
4121
- if (this.status === "closed") {
4122
- return;
4123
- }
4124
- try {
4125
- await this.connectSSE();
4126
- } catch (retryError) {
4127
- await this.handleConnectionError(retryError);
4128
- }
4129
- return;
4130
- }
4131
- if (this.pollingEnabled) {
4132
- if (this.sseClient) {
4133
- this.sseClient.disconnect();
4134
- this.sseClient = null;
4135
- }
4136
- this.status = "fallback_polling";
4137
- this.emit("connectionChange", { connected: false, usingFallback: true });
4138
- this.startPolling();
4139
- return;
4140
- }
4141
- }
4142
4785
  if (/\b4\d\d\b/.test(msg) || /INVALID_API_KEY|INVALID_REQUEST/.test(msg)) {
4143
4786
  if (this.sseClient) {
4144
4787
  this.sseClient.disconnect();
@@ -6186,6 +6829,6 @@ function createDefaultKanbanAdapter(config) {
6186
6829
  };
6187
6830
  }
6188
6831
 
6189
- export { AgentBoard, BoardConfigClient, CadraLifecycleSource, CardDrawer, KanbanBoard, KanbanCard as KanbanCardTile, KanbanColumn, createDefaultKanbanAdapter, mapDefinitionToBoard, toResolvedLifecycle, useCardRun, useKanbanBoard };
6832
+ export { AgentBoard, BoardConfigClient, CadraLifecycleSource, CapMeter, CardDrawer, KanbanBoard, KanbanCard as KanbanCardTile, KanbanColumn, LaneAgentPicker, ScheduleStatus, badgeVariantClass, cn9 as cn, createDefaultKanbanAdapter, mapDefinitionToBoard, runStatusTone, toResolvedLifecycle, useCardRun, useKanbanBoard };
6190
6833
  //# sourceMappingURL=index.js.map
6191
6834
  //# sourceMappingURL=index.js.map