@cadraos/sdk 0.3.14-p46.0 → 0.3.14-p46.1

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.
@@ -266,9 +266,12 @@ function useKanbanBoard(options) {
266
266
  ]);
267
267
  setBoard(nextBoard);
268
268
  setCards(cardList.cards);
269
- setSelectedCard(
270
- (prev) => prev ? cardList.cards.find((c) => c.id === prev.id) ?? prev : prev
271
- );
269
+ setSelectedCard((prev) => {
270
+ if (!prev) return prev;
271
+ const next = cardList.cards.find((c) => c.id === prev.id);
272
+ if (!next) return prev;
273
+ return next.run ? next : { ...next, run: prev.run };
274
+ });
272
275
  } catch (err) {
273
276
  fail(err, "Failed to load board");
274
277
  } finally {
@@ -283,9 +286,12 @@ function useKanbanBoard(options) {
283
286
  ]);
284
287
  setBoard(nextBoard);
285
288
  setCards(cardList.cards);
286
- setSelectedCard(
287
- (prev) => prev ? cardList.cards.find((c) => c.id === prev.id) ?? prev : prev
288
- );
289
+ setSelectedCard((prev) => {
290
+ if (!prev) return prev;
291
+ const next = cardList.cards.find((c) => c.id === prev.id);
292
+ if (!next) return prev;
293
+ return next.run ? next : { ...next, run: prev.run };
294
+ });
289
295
  } catch {
290
296
  }
291
297
  }, [adapter]);
@@ -306,7 +312,7 @@ function useKanbanBoard(options) {
306
312
  try {
307
313
  const full = await adapter.getCard(cardId);
308
314
  setSelectedCard(
309
- (prev) => prev && prev.id === cardId ? full : prev
315
+ (prev) => prev == null || prev.id === cardId ? full : prev
310
316
  );
311
317
  } catch (err) {
312
318
  if (!known) fail(err, "Failed to open card");
@@ -415,6 +421,27 @@ function useKanbanBoard(options) {
415
421
  },
416
422
  [refresh]
417
423
  );
424
+ const deleteCard = react.useCallback(
425
+ async (cardId) => {
426
+ if (!adapter.deleteCard) return false;
427
+ setError(null);
428
+ const prevCards = cards;
429
+ const prevSelected = selectedCard;
430
+ setCards((prev) => prev.filter((c) => c.id !== cardId));
431
+ setSelectedCard((prev) => prev && prev.id === cardId ? null : prev);
432
+ try {
433
+ await adapter.deleteCard(cardId);
434
+ void silentRefresh();
435
+ return true;
436
+ } catch (err) {
437
+ setCards(prevCards);
438
+ setSelectedCard(prevSelected);
439
+ fail(err, "Failed to delete card");
440
+ return false;
441
+ }
442
+ },
443
+ [adapter, cards, selectedCard, silentRefresh, fail]
444
+ );
418
445
  return {
419
446
  board,
420
447
  lanes: board?.lanes ?? [],
@@ -430,6 +457,7 @@ function useKanbanBoard(options) {
430
457
  createCard,
431
458
  updateCard,
432
459
  transitionCard,
460
+ deleteCard,
433
461
  handleRunTerminal
434
462
  };
435
463
  }
@@ -2723,6 +2751,9 @@ function KanbanCard({
2723
2751
  onClick,
2724
2752
  selected,
2725
2753
  dueState,
2754
+ draggable,
2755
+ onDragStart,
2756
+ onDragEnd,
2726
2757
  className
2727
2758
  }) {
2728
2759
  const runStatus = card.run?.status;
@@ -2751,10 +2782,20 @@ function KanbanCard({
2751
2782
  onClick: () => onClick?.(card),
2752
2783
  "aria-pressed": selected,
2753
2784
  "aria-busy": isWorking,
2785
+ draggable,
2786
+ onDragStart: draggable ? (e) => {
2787
+ e.dataTransfer.effectAllowed = "move";
2788
+ e.dataTransfer.setData("text/plain", card.id);
2789
+ onDragStart?.(card);
2790
+ } : void 0,
2791
+ onDragEnd: draggable ? () => onDragEnd?.() : void 0,
2754
2792
  className: cn9(
2755
- "relative w-full text-left rounded-lg border bg-card p-3 shadow-sm transition",
2793
+ // Solid card: fill + border + a faint token top-sheen (adapts to theme),
2794
+ // matching the host's cockpit cards (work-shell v2 visual pass).
2795
+ "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",
2756
2796
  "hover:shadow-md hover:border-primary/40",
2757
2797
  "focus:outline-none focus:ring-2 focus:ring-ring/50",
2798
+ draggable && "cursor-grab active:cursor-grabbing",
2758
2799
  // The working card carries the SINGLE focal motion on the board (F11).
2759
2800
  isWorking ? "border-info/60 ring-2 ring-info/40 animate-pulse" : selected ? "border-primary/60 ring-2 ring-primary/30" : "border-border",
2760
2801
  className
@@ -3047,6 +3088,16 @@ function LaneAgentPicker({
3047
3088
  )
3048
3089
  ] });
3049
3090
  }
3091
+ function dueStateFor(card, lane, now) {
3092
+ const rs = card.run?.status;
3093
+ if (rs === "completed" && !lane.terminal) return "ready";
3094
+ if (card.nextDueAt) {
3095
+ const t = Date.parse(card.nextDueAt);
3096
+ if (!Number.isNaN(t) && t <= now) return "due";
3097
+ }
3098
+ if (lane.trigger === "scheduled" && !rs) return "waiting";
3099
+ return void 0;
3100
+ }
3050
3101
  function LaneHealthDot({ health }) {
3051
3102
  if (!health || health.state !== "down" && health.state !== "degraded") return null;
3052
3103
  const down = health.state === "down";
@@ -3067,16 +3118,6 @@ function LaneHealthDot({ health }) {
3067
3118
  }
3068
3119
  );
3069
3120
  }
3070
- function dueStateFor(card, lane, now) {
3071
- const rs = card.run?.status;
3072
- if (rs === "completed" && !lane.terminal) return "ready";
3073
- if (card.nextDueAt) {
3074
- const t = Date.parse(card.nextDueAt);
3075
- if (!Number.isNaN(t) && t <= now) return "due";
3076
- }
3077
- if (lane.trigger === "scheduled" && !rs) return "waiting";
3078
- return void 0;
3079
- }
3080
3121
  function KanbanColumn({
3081
3122
  lane,
3082
3123
  cards,
@@ -3087,6 +3128,12 @@ function KanbanColumn({
3087
3128
  count,
3088
3129
  agentPicker,
3089
3130
  canEditHandlers,
3131
+ draggableCards,
3132
+ dragActive,
3133
+ dropAllowed,
3134
+ onCardDragStart,
3135
+ onCardDragEnd,
3136
+ onLaneDrop,
3090
3137
  health,
3091
3138
  className
3092
3139
  }) {
@@ -3098,9 +3145,21 @@ function KanbanColumn({
3098
3145
  return /* @__PURE__ */ jsxRuntime.jsxs(
3099
3146
  "div",
3100
3147
  {
3148
+ onDragOver: dropAllowed ? (e) => {
3149
+ e.preventDefault();
3150
+ e.dataTransfer.dropEffect = "move";
3151
+ } : void 0,
3152
+ onDrop: dropAllowed && onLaneDrop ? (e) => {
3153
+ e.preventDefault();
3154
+ onLaneDrop(lane);
3155
+ } : void 0,
3101
3156
  className: cn9(
3102
- "flex w-[85vw] max-w-sm snap-start shrink-0 flex-col rounded-xl bg-muted/40 border border-border sm:w-72 sm:max-w-none",
3157
+ "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",
3103
3158
  isScheduled && "border-border",
3159
+ // MV4 drag affordance: allowed targets glow; other lanes dim so the
3160
+ // valid drop zones read at a glance.
3161
+ dragActive && dropAllowed && "border-primary/60 ring-2 ring-primary/40 bg-primary/5",
3162
+ dragActive && !dropAllowed && "opacity-55",
3104
3163
  className
3105
3164
  ),
3106
3165
  children: [
@@ -3170,7 +3229,10 @@ function KanbanColumn({
3170
3229
  card,
3171
3230
  onClick: onCardClick,
3172
3231
  selected: card.id === selectedCardId,
3173
- dueState: dueStateFor(card, lane, now)
3232
+ dueState: dueStateFor(card, lane, now),
3233
+ draggable: draggableCards,
3234
+ onDragStart: onCardDragStart,
3235
+ onDragEnd: onCardDragEnd
3174
3236
  },
3175
3237
  card.id
3176
3238
  )),
@@ -3331,6 +3393,8 @@ function KanbanBoard({
3331
3393
  laneAgentPicker,
3332
3394
  scheduleStatus,
3333
3395
  focusCardId,
3396
+ renderCardDrawer,
3397
+ enableDrag,
3334
3398
  className
3335
3399
  }) {
3336
3400
  const {
@@ -3345,6 +3409,7 @@ function KanbanBoard({
3345
3409
  createCard,
3346
3410
  updateCard,
3347
3411
  transitionCard,
3412
+ deleteCard,
3348
3413
  handleRunTerminal
3349
3414
  } = useKanbanBoard({ adapter, onError, onCardCreated, pollIntervalMs });
3350
3415
  const lastFocusedRef = react.useRef(void 0);
@@ -3389,6 +3454,44 @@ function KanbanBoard({
3389
3454
  (cardId, toLane) => transitionCard({ cardId, targetLane: toLane }),
3390
3455
  [transitionCard]
3391
3456
  );
3457
+ const [dragging, setDragging] = react.useState(null);
3458
+ const draggingRef = react.useRef(null);
3459
+ const dropInFlightRef = react.useRef(null);
3460
+ const allowedDropLanes = react.useMemo(() => {
3461
+ if (!dragging || !board?.transitions) return /* @__PURE__ */ new Set();
3462
+ return new Set(
3463
+ board.transitions.filter((t) => t.fromLane === dragging.lane && t.manualAllowed).map((t) => t.toLane)
3464
+ );
3465
+ }, [dragging, board?.transitions]);
3466
+ const handleCardDragStart = react.useCallback((card) => {
3467
+ const d = { id: card.id, lane: card.lane };
3468
+ draggingRef.current = d;
3469
+ setDragging(d);
3470
+ }, []);
3471
+ const handleCardDragEnd = react.useCallback(() => {
3472
+ draggingRef.current = null;
3473
+ setDragging(null);
3474
+ }, []);
3475
+ const handleLaneDrop = react.useCallback(
3476
+ (lane) => {
3477
+ const cur = draggingRef.current;
3478
+ draggingRef.current = null;
3479
+ setDragging(null);
3480
+ if (!cur || lane.key === cur.lane) return;
3481
+ if (dropInFlightRef.current === cur.id) return;
3482
+ const allowed = (board?.transitions ?? []).some(
3483
+ (t) => t.fromLane === cur.lane && t.toLane === lane.key && t.manualAllowed
3484
+ );
3485
+ if (!allowed) return;
3486
+ dropInFlightRef.current = cur.id;
3487
+ void Promise.resolve(transitionCard({ cardId: cur.id, targetLane: lane.key })).finally(
3488
+ () => {
3489
+ if (dropInFlightRef.current === cur.id) dropInFlightRef.current = null;
3490
+ }
3491
+ );
3492
+ },
3493
+ [board?.transitions, transitionCard]
3494
+ );
3392
3495
  const initialLaneKey = lanes.find((l) => !l.terminal)?.key ?? lanes[0]?.key;
3393
3496
  const selectedLane = selectedCard ? lanes.find((l) => l.key === selectedCard.lane) : void 0;
3394
3497
  const canAdvanceSelected = !!selectedLane && !selectedLane.terminal;
@@ -3410,12 +3513,32 @@ function KanbanBoard({
3410
3513
  count: laneCounts?.[lane.key],
3411
3514
  health: laneHealth?.[lane.key],
3412
3515
  agentPicker: laneAgentPicker?.(lane),
3413
- canEditHandlers
3516
+ canEditHandlers,
3517
+ draggableCards: enableDrag && enableAdvance,
3518
+ dragActive: !!dragging,
3519
+ dropAllowed: !!dragging && dragging.lane !== lane.key && allowedDropLanes.has(lane.key),
3520
+ onCardDragStart: handleCardDragStart,
3521
+ onCardDragEnd: handleCardDragEnd,
3522
+ onLaneDrop: handleLaneDrop
3414
3523
  },
3415
3524
  lane.id ?? lane.key
3416
3525
  ))
3417
3526
  ] }),
3418
- selectedCard && /* @__PURE__ */ jsxRuntime.jsx(
3527
+ selectedCard && (renderCardDrawer ? renderCardDrawer({
3528
+ card: selectedCard,
3529
+ adapter,
3530
+ lanes,
3531
+ transitions: enableAdvance ? board?.transitions : void 0,
3532
+ canAdvance: canAdvanceSelected,
3533
+ onClose: closeCard,
3534
+ onRetry: onRetryCard ? handleRetry : void 0,
3535
+ onUpdate: enableEdit ? handleUpdate : void 0,
3536
+ onAdvance: enableAdvance ? handleAdvance : void 0,
3537
+ onTransition: enableAdvance ? handleTransition : void 0,
3538
+ onDelete: adapter.deleteCard ? deleteCard : void 0,
3539
+ onRunTerminal: handleRunTerminal,
3540
+ onError
3541
+ }) : /* @__PURE__ */ jsxRuntime.jsx(
3419
3542
  CardDrawer,
3420
3543
  {
3421
3544
  card: selectedCard,
@@ -3431,7 +3554,7 @@ function KanbanBoard({
3431
3554
  onRunTerminal: handleRunTerminal,
3432
3555
  onError
3433
3556
  }
3434
- )
3557
+ ))
3435
3558
  ] })
3436
3559
  ] });
3437
3560
  }
@@ -3456,6 +3579,7 @@ function composeAdapter(source, boardConfigUuid, version, cardSource) {
3456
3579
  transitionCard(input) {
3457
3580
  return cardSource.transitionCard(input);
3458
3581
  },
3582
+ ...cardSource.deleteCard ? { deleteCard: (id) => cardSource.deleteCard(id) } : {},
3459
3583
  ...cardSource.createRunStream ? { createRunStream: (id) => cardSource.createRunStream(id) } : {}
3460
3584
  };
3461
3585
  }