@burdenoff/planmagnet-fe-sdk 2026.704.2 → 2026.704.4

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.d.ts CHANGED
@@ -271,8 +271,9 @@ interface BoardColumn {
271
271
  }
272
272
  /**
273
273
  * The default, ordered board columns. Product-agnostic — no farm semantics.
274
- * TESTING/CANCELLED are omitted from the default board but are valid statuses;
275
- * callers can override via TaskBoardCoreProps.columns.
274
+ * Covers every TaskStatus so a task can never be "orphaned" off the board
275
+ * (a CANCELLED item has a home). Callers can trim/reorder via
276
+ * TaskBoardCoreProps.columns.
276
277
  */
277
278
  declare const DEFAULT_BOARD_COLUMNS: BoardColumn[];
278
279
  /** Priority filter options (UI order). */
@@ -432,6 +433,12 @@ interface TaskBoardCoreProps {
432
433
  columns?: BoardColumn[];
433
434
  /** Read-only mode — disables drag-to-move. */
434
435
  readonly?: boolean;
436
+ /**
437
+ * Ask for confirmation before a drag-drop moves a task to another column.
438
+ * Defaults to `true` — an accidental drag shouldn't silently change a task's
439
+ * status. Set `false` for instant, Linear-style moves.
440
+ */
441
+ confirmMove?: boolean;
435
442
  /** Hide the board's own header (title + count). */
436
443
  hideHeader?: boolean;
437
444
  /**
@@ -483,7 +490,7 @@ interface TaskBoardCoreProps {
483
490
  * (optimistic cache update + `UPDATE_WORK_ITEM`; reverts on error). Filtering
484
491
  * is controlled by the parent toolbar. Farm-free, semantic-token only.
485
492
  */
486
- declare function TaskBoardCore({ projectId, columns, readonly, hideHeader, priority, type, search, assigneeId, hiddenFields, resolveAssignee, linkSourceProduct, renderLinkIcon, onTaskClick, className, workspaceId: workspaceIdProp, }: TaskBoardCoreProps): react.JSX.Element;
493
+ declare function TaskBoardCore({ projectId, columns, readonly, confirmMove, hideHeader, priority, type, search, assigneeId, hiddenFields, resolveAssignee, linkSourceProduct, renderLinkIcon, onTaskClick, className, workspaceId: workspaceIdProp, }: TaskBoardCoreProps): react.JSX.Element;
487
494
 
488
495
  interface TaskBoardProps extends TaskBoardCoreProps {
489
496
  /** Workspace API gateway URL (URL mode). */
@@ -795,6 +802,14 @@ interface TaskDetailCoreProps {
795
802
  resolveAssignee?: ResolveAssignee;
796
803
  /** Optional host-provided member directory → renders a picker instead of a raw id field. */
797
804
  assigneeOptions?: AssigneeOption[];
805
+ /**
806
+ * Product key for cross-product entity links (e.g. "healthybowl"). When set,
807
+ * the read view shows this task's linked records (fields/crops/animals). Opt-in
808
+ * so the SDK stays farm-free.
809
+ */
810
+ linkSourceProduct?: string;
811
+ /** Optional host hook to render an icon per linked-entity type. */
812
+ renderLinkIcon?: RenderLinkIcon;
798
813
  /** Extra classes on the root container. */
799
814
  className?: string;
800
815
  }
@@ -803,7 +818,7 @@ interface TaskDetailCoreProps {
803
818
  * edit. Fetches via `planmagnetWorkItem(id)`, saves via
804
819
  * `planmagnetUpdateWorkItem`. BYO-Apollo, semantic-token only, farm-free.
805
820
  */
806
- declare function TaskDetailCore({ id, readonly, hiddenFields, hideHeader, onUpdated, onDeleted, onError, resolveAssignee, assigneeOptions, className, }: TaskDetailCoreProps): react.JSX.Element;
821
+ declare function TaskDetailCore({ id, readonly, hiddenFields, hideHeader, onUpdated, onDeleted, onError, resolveAssignee, assigneeOptions, linkSourceProduct, renderLinkIcon, className, }: TaskDetailCoreProps): react.JSX.Element;
807
822
 
808
823
  interface TaskDetailProps extends TaskDetailCoreProps {
809
824
  /** Workspace API gateway URL (URL mode). */
@@ -862,6 +877,9 @@ interface TaskLabels {
862
877
  noTasks: string;
863
878
  noTasksFound: string;
864
879
  dropHere: string;
880
+ moveConfirmTitle: string;
881
+ moveConfirmPrompt: string;
882
+ moveConfirm: string;
865
883
  unassigned: string;
866
884
  assigned: string;
867
885
  overdue: string;
@@ -897,6 +915,9 @@ interface TaskLabels {
897
915
  storyPointsLabel: string;
898
916
  labelsLabel: string;
899
917
  labelsPlaceholder: string;
918
+ linkedRecordsLabel: string;
919
+ createdLabel: string;
920
+ updatedLabel: string;
900
921
  create: string;
901
922
  creating: string;
902
923
  cancel: string;
@@ -973,6 +994,11 @@ interface TasksPageCoreProps {
973
994
  views?: TaskView[];
974
995
  /** Initial view. Defaults to "board". */
975
996
  defaultView?: TaskView;
997
+ /**
998
+ * Ask for confirmation before a board drag-drop moves a task. Defaults to
999
+ * `true`; set `false` for instant, Linear-style moves.
1000
+ */
1001
+ confirmMove?: boolean;
976
1002
  /** Optional host hook to resolve an assignee id → display name. */
977
1003
  resolveAssignee?: ResolveAssignee;
978
1004
  /**
@@ -1022,7 +1048,7 @@ interface TasksPageCoreProps {
1022
1048
  * BYO-Apollo: reads the ambient `<ApolloProvider>` and takes `workspaceId` from
1023
1049
  * a prop or the PlanMagnetProvider context. Semantic tokens only, farm-free.
1024
1050
  */
1025
- declare function TasksPageCore({ workspaceId: workspaceIdProp, projectId, boardProjectId, columns, readonly, hiddenFields, views, defaultView, resolveAssignee, linkSourceProduct, renderLinkIcon, assigneeOptions, currentActorId, labels, openItemId, header, onTaskSaved, createSurface, detailSurface, className, }: TasksPageCoreProps): react.JSX.Element;
1051
+ declare function TasksPageCore({ workspaceId: workspaceIdProp, projectId, boardProjectId, columns, readonly, hiddenFields, views, defaultView, confirmMove, resolveAssignee, linkSourceProduct, renderLinkIcon, assigneeOptions, currentActorId, labels, openItemId, header, onTaskSaved, createSurface, detailSurface, className, }: TasksPageCoreProps): react.JSX.Element;
1026
1052
 
1027
1053
  interface TasksPageProps extends TasksPageCoreProps {
1028
1054
  /** Workspace API gateway URL (URL mode). */
package/dist/index.js CHANGED
@@ -521,6 +521,12 @@ function parseDate(iso) {
521
521
  const d = parseISO(iso);
522
522
  return isValid(d) ? d : null;
523
523
  }
524
+ function toIsoDate(value) {
525
+ if (!value) return void 0;
526
+ const iso = /^\d{4}-\d{2}-\d{2}$/.test(value) ? `${value}T00:00:00.000Z` : value;
527
+ const d = new Date(iso);
528
+ return Number.isNaN(d.getTime()) ? void 0 : d.toISOString();
529
+ }
524
530
  function formatDueDate(iso) {
525
531
  const d = parseDate(iso);
526
532
  if (!d) return null;
@@ -530,6 +536,10 @@ function formatDueDate(iso) {
530
536
  d.getFullYear() === now.getFullYear() ? "MMM d" : "MMM d yyyy"
531
537
  );
532
538
  }
539
+ function formatDateFull(iso) {
540
+ const d = parseDate(iso);
541
+ return d ? format(d, "MMM d, yyyy") : null;
542
+ }
533
543
  function isOverdue(iso, status) {
534
544
  const d = parseDate(iso);
535
545
  if (!d) return false;
@@ -626,7 +636,8 @@ var DEFAULT_BOARD_COLUMNS = [
626
636
  { status: "IN_PROGRESS", label: "In Progress" },
627
637
  { status: "IN_REVIEW", label: "In Review" },
628
638
  { status: "TESTING", label: "Testing" },
629
- { status: "DONE", label: "Done" }
639
+ { status: "DONE", label: "Done" },
640
+ { status: "CANCELLED", label: "Cancelled" }
630
641
  ];
631
642
  var TASK_PRIORITIES = [
632
643
  "NONE",
@@ -696,6 +707,9 @@ var DEFAULT_TASK_LABELS = {
696
707
  noTasks: "No tasks",
697
708
  noTasksFound: "No tasks found.",
698
709
  dropHere: "Drop here",
710
+ moveConfirmTitle: "Move task?",
711
+ moveConfirmPrompt: 'Move "{task}" to {status}?',
712
+ moveConfirm: "Move",
699
713
  unassigned: "Unassigned",
700
714
  assigned: "Assigned",
701
715
  overdue: "{n} overdue",
@@ -731,6 +745,9 @@ var DEFAULT_TASK_LABELS = {
731
745
  storyPointsLabel: "Story points",
732
746
  labelsLabel: "Labels",
733
747
  labelsPlaceholder: "comma, separated, labels",
748
+ linkedRecordsLabel: "Linked records",
749
+ createdLabel: "Created",
750
+ updatedLabel: "Updated",
734
751
  create: "Create task",
735
752
  creating: "Creating\u2026",
736
753
  cancel: "Cancel",
@@ -1076,6 +1093,7 @@ function TaskBoardCore({
1076
1093
  projectId,
1077
1094
  columns = DEFAULT_BOARD_COLUMNS,
1078
1095
  readonly,
1096
+ confirmMove = true,
1079
1097
  hideHeader,
1080
1098
  priority = "ALL",
1081
1099
  type = "ALL",
@@ -1098,6 +1116,7 @@ function TaskBoardCore({
1098
1116
  const [draggingId, setDraggingId] = useState(null);
1099
1117
  const [dragOver, setDragOver] = useState(null);
1100
1118
  const [moveError, setMoveError] = useState(null);
1119
+ const [pendingMove, setPendingMove] = useState(null);
1101
1120
  const [updateItem] = useMutation(UPDATE_WORK_ITEM);
1102
1121
  const { data, loading, error } = useQuery(
1103
1122
  LIST_WORK_ITEMS,
@@ -1164,7 +1183,9 @@ function TaskBoardCore({
1164
1183
  const task = tasks.find((t) => t.id === draggingId);
1165
1184
  setDragOver(null);
1166
1185
  setDraggingId(null);
1167
- if (task) moveTask(task, status);
1186
+ if (!task || task.status === status) return;
1187
+ if (confirmMove) setPendingMove({ task, toStatus: status });
1188
+ else moveTask(task, status);
1168
1189
  };
1169
1190
  return /* @__PURE__ */ jsxs("div", { className: cn("space-y-3", className), children: [
1170
1191
  !hideHeader && /* @__PURE__ */ jsxs("div", { className: "flex items-baseline justify-between", children: [
@@ -1224,7 +1245,43 @@ function TaskBoardCore({
1224
1245
  },
1225
1246
  col.status
1226
1247
  );
1227
- }) })
1248
+ }) }),
1249
+ /* @__PURE__ */ jsx(
1250
+ Modal,
1251
+ {
1252
+ open: pendingMove != null,
1253
+ onClose: () => setPendingMove(null),
1254
+ ariaLabel: L.moveConfirmTitle,
1255
+ className: "max-w-sm",
1256
+ children: pendingMove && /* @__PURE__ */ jsxs("div", { className: "space-y-4 p-6", children: [
1257
+ /* @__PURE__ */ jsx("h3", { className: "text-base font-semibold text-foreground", children: L.moveConfirmTitle }),
1258
+ /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: L.moveConfirmPrompt.replace("{task}", pendingMove.task.title).replace("{status}", L.status[pendingMove.toStatus]) }),
1259
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-end gap-2", children: [
1260
+ /* @__PURE__ */ jsx(
1261
+ "button",
1262
+ {
1263
+ type: "button",
1264
+ onClick: () => setPendingMove(null),
1265
+ className: "rounded-md bg-muted px-4 py-2 text-sm font-medium text-muted-foreground hover:bg-muted/70",
1266
+ children: L.cancel
1267
+ }
1268
+ ),
1269
+ /* @__PURE__ */ jsx(
1270
+ "button",
1271
+ {
1272
+ type: "button",
1273
+ onClick: () => {
1274
+ moveTask(pendingMove.task, pendingMove.toStatus);
1275
+ setPendingMove(null);
1276
+ },
1277
+ className: "rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground hover:brightness-110",
1278
+ children: L.moveConfirm
1279
+ }
1280
+ )
1281
+ ] })
1282
+ ] })
1283
+ }
1284
+ )
1228
1285
  ] });
1229
1286
  }
1230
1287
  function TaskBoard({
@@ -1317,7 +1374,7 @@ function CreateTaskCore({
1317
1374
  ...hidden.has("priority") ? {} : { priority },
1318
1375
  ...hidden.has("description") || !description.trim() ? {} : { description: description.trim() },
1319
1376
  ...hidden.has("assignee") || !assigneeId.trim() ? {} : { assigneeId: assigneeId.trim() },
1320
- ...hidden.has("dueDate") || !dueDate.trim() ? {} : { dueDate },
1377
+ ...hidden.has("dueDate") || !dueDate.trim() ? {} : { dueDate: toIsoDate(dueDate) },
1321
1378
  ...hidden.has("labels") || parsedLabels.length === 0 ? {} : { labels: parsedLabels },
1322
1379
  ...hidden.has("storyPoints") || parsedPoints == null || Number.isNaN(parsedPoints) ? {} : { storyPoints: parsedPoints }
1323
1380
  };
@@ -2409,6 +2466,8 @@ function TaskDetailCore({
2409
2466
  onError: onError2,
2410
2467
  resolveAssignee,
2411
2468
  assigneeOptions,
2469
+ linkSourceProduct,
2470
+ renderLinkIcon,
2412
2471
  className
2413
2472
  }) {
2414
2473
  const client = useApolloClient();
@@ -2422,6 +2481,11 @@ function TaskDetailCore({
2422
2481
  skip: !id
2423
2482
  });
2424
2483
  const item = data?.planmagnetWorkItem ?? null;
2484
+ const { linksByItem } = useWorkItemLinks(
2485
+ item?.workspaceId,
2486
+ linkSourceProduct
2487
+ );
2488
+ const links = item ? linksByItem.get(item.id) ?? [] : [];
2425
2489
  const [editing, setEditing] = useState(false);
2426
2490
  const [confirmingDelete, setConfirmingDelete] = useState(false);
2427
2491
  const [title, setTitle] = useState("");
@@ -2474,7 +2538,7 @@ function TaskDetailCore({
2474
2538
  ...hidden.has("status") ? {} : { status },
2475
2539
  ...hidden.has("priority") ? {} : { priority },
2476
2540
  ...hidden.has("assignee") ? {} : { assigneeId: assigneeId.trim() },
2477
- ...hidden.has("dueDate") ? {} : { dueDate: dueDate || void 0 },
2541
+ ...hidden.has("dueDate") ? {} : { dueDate: toIsoDate(dueDate) },
2478
2542
  ...hidden.has("labels") ? {} : { labels: parsedLabels },
2479
2543
  ...hidden.has("storyPoints") || parsedPoints == null || Number.isNaN(parsedPoints) ? {} : { storyPoints: parsedPoints }
2480
2544
  };
@@ -2589,7 +2653,7 @@ function TaskDetailCore({
2589
2653
  ),
2590
2654
  /* @__PURE__ */ jsx("span", { className: "text-foreground", children: resolveAssignee?.(item.assigneeId)?.name ?? L.assigned })
2591
2655
  ] }) : /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: L.unassigned }) }),
2592
- !hidden.has("dueDate") && /* @__PURE__ */ jsx(Field, { label: L.dueDateLabel, children: /* @__PURE__ */ jsx("span", { className: "text-foreground", children: item.dueDate ?? "\u2014" }) }),
2656
+ !hidden.has("dueDate") && /* @__PURE__ */ jsx(Field, { label: L.dueDateLabel, children: /* @__PURE__ */ jsx("span", { className: "text-foreground", children: formatDateFull(item.dueDate) ?? "\u2014" }) }),
2593
2657
  !hidden.has("storyPoints") && /* @__PURE__ */ jsx(Field, { label: L.storyPointsLabel, children: /* @__PURE__ */ jsx("span", { className: "text-foreground", children: item.storyPoints ?? 0 }) }),
2594
2658
  !hidden.has("description") && /* @__PURE__ */ jsxs("div", { className: "sm:col-span-2", children: [
2595
2659
  /* @__PURE__ */ jsx("dt", { className: labelClass2, children: L.description }),
@@ -2605,6 +2669,29 @@ function TaskDetailCore({
2605
2669
  },
2606
2670
  l
2607
2671
  )) })
2672
+ ] }),
2673
+ links.length > 0 && /* @__PURE__ */ jsxs("div", { className: "sm:col-span-2", children: [
2674
+ /* @__PURE__ */ jsx("dt", { className: labelClass2, children: L.linkedRecordsLabel }),
2675
+ /* @__PURE__ */ jsx("dd", { className: "mt-1 flex flex-wrap gap-1.5", children: links.map((link) => /* @__PURE__ */ jsx(
2676
+ LinkChip,
2677
+ {
2678
+ link,
2679
+ renderIcon: renderLinkIcon
2680
+ },
2681
+ link.id
2682
+ )) })
2683
+ ] }),
2684
+ (item.createdAt || item.updatedAt) && /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap gap-x-6 gap-y-1 border-t border-border pt-3 text-xs text-muted-foreground sm:col-span-2", children: [
2685
+ item.createdAt && /* @__PURE__ */ jsxs("span", { children: [
2686
+ L.createdLabel,
2687
+ " ",
2688
+ formatDateFull(item.createdAt)
2689
+ ] }),
2690
+ item.updatedAt && /* @__PURE__ */ jsxs("span", { children: [
2691
+ L.updatedLabel,
2692
+ " ",
2693
+ formatDateFull(item.updatedAt)
2694
+ ] })
2608
2695
  ] })
2609
2696
  ] }) : /* @__PURE__ */ jsxs("form", { onSubmit: handleSave, className: "space-y-4", children: [
2610
2697
  /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
@@ -2812,6 +2899,7 @@ function TasksPageCore({
2812
2899
  hiddenFields,
2813
2900
  views,
2814
2901
  defaultView = "board",
2902
+ confirmMove,
2815
2903
  resolveAssignee,
2816
2904
  linkSourceProduct,
2817
2905
  renderLinkIcon,
@@ -2890,6 +2978,8 @@ function TasksPageCore({
2890
2978
  hiddenFields,
2891
2979
  resolveAssignee,
2892
2980
  assigneeOptions,
2981
+ linkSourceProduct,
2982
+ renderLinkIcon,
2893
2983
  onUpdated: handleUpdated,
2894
2984
  onDeleted: handleDeleted
2895
2985
  }
@@ -2933,6 +3023,7 @@ function TasksPageCore({
2933
3023
  projectId: boardProjectId,
2934
3024
  columns,
2935
3025
  readonly,
3026
+ confirmMove,
2936
3027
  hideHeader: true,
2937
3028
  priority,
2938
3029
  type,