@burdenoff/planmagnet-fe-sdk 2026.704.3 → 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
@@ -536,6 +536,10 @@ function formatDueDate(iso) {
536
536
  d.getFullYear() === now.getFullYear() ? "MMM d" : "MMM d yyyy"
537
537
  );
538
538
  }
539
+ function formatDateFull(iso) {
540
+ const d = parseDate(iso);
541
+ return d ? format(d, "MMM d, yyyy") : null;
542
+ }
539
543
  function isOverdue(iso, status) {
540
544
  const d = parseDate(iso);
541
545
  if (!d) return false;
@@ -632,7 +636,8 @@ var DEFAULT_BOARD_COLUMNS = [
632
636
  { status: "IN_PROGRESS", label: "In Progress" },
633
637
  { status: "IN_REVIEW", label: "In Review" },
634
638
  { status: "TESTING", label: "Testing" },
635
- { status: "DONE", label: "Done" }
639
+ { status: "DONE", label: "Done" },
640
+ { status: "CANCELLED", label: "Cancelled" }
636
641
  ];
637
642
  var TASK_PRIORITIES = [
638
643
  "NONE",
@@ -702,6 +707,9 @@ var DEFAULT_TASK_LABELS = {
702
707
  noTasks: "No tasks",
703
708
  noTasksFound: "No tasks found.",
704
709
  dropHere: "Drop here",
710
+ moveConfirmTitle: "Move task?",
711
+ moveConfirmPrompt: 'Move "{task}" to {status}?',
712
+ moveConfirm: "Move",
705
713
  unassigned: "Unassigned",
706
714
  assigned: "Assigned",
707
715
  overdue: "{n} overdue",
@@ -737,6 +745,9 @@ var DEFAULT_TASK_LABELS = {
737
745
  storyPointsLabel: "Story points",
738
746
  labelsLabel: "Labels",
739
747
  labelsPlaceholder: "comma, separated, labels",
748
+ linkedRecordsLabel: "Linked records",
749
+ createdLabel: "Created",
750
+ updatedLabel: "Updated",
740
751
  create: "Create task",
741
752
  creating: "Creating\u2026",
742
753
  cancel: "Cancel",
@@ -1082,6 +1093,7 @@ function TaskBoardCore({
1082
1093
  projectId,
1083
1094
  columns = DEFAULT_BOARD_COLUMNS,
1084
1095
  readonly,
1096
+ confirmMove = true,
1085
1097
  hideHeader,
1086
1098
  priority = "ALL",
1087
1099
  type = "ALL",
@@ -1104,6 +1116,7 @@ function TaskBoardCore({
1104
1116
  const [draggingId, setDraggingId] = useState(null);
1105
1117
  const [dragOver, setDragOver] = useState(null);
1106
1118
  const [moveError, setMoveError] = useState(null);
1119
+ const [pendingMove, setPendingMove] = useState(null);
1107
1120
  const [updateItem] = useMutation(UPDATE_WORK_ITEM);
1108
1121
  const { data, loading, error } = useQuery(
1109
1122
  LIST_WORK_ITEMS,
@@ -1170,7 +1183,9 @@ function TaskBoardCore({
1170
1183
  const task = tasks.find((t) => t.id === draggingId);
1171
1184
  setDragOver(null);
1172
1185
  setDraggingId(null);
1173
- if (task) moveTask(task, status);
1186
+ if (!task || task.status === status) return;
1187
+ if (confirmMove) setPendingMove({ task, toStatus: status });
1188
+ else moveTask(task, status);
1174
1189
  };
1175
1190
  return /* @__PURE__ */ jsxs("div", { className: cn("space-y-3", className), children: [
1176
1191
  !hideHeader && /* @__PURE__ */ jsxs("div", { className: "flex items-baseline justify-between", children: [
@@ -1230,7 +1245,43 @@ function TaskBoardCore({
1230
1245
  },
1231
1246
  col.status
1232
1247
  );
1233
- }) })
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
+ )
1234
1285
  ] });
1235
1286
  }
1236
1287
  function TaskBoard({
@@ -2415,6 +2466,8 @@ function TaskDetailCore({
2415
2466
  onError: onError2,
2416
2467
  resolveAssignee,
2417
2468
  assigneeOptions,
2469
+ linkSourceProduct,
2470
+ renderLinkIcon,
2418
2471
  className
2419
2472
  }) {
2420
2473
  const client = useApolloClient();
@@ -2428,6 +2481,11 @@ function TaskDetailCore({
2428
2481
  skip: !id
2429
2482
  });
2430
2483
  const item = data?.planmagnetWorkItem ?? null;
2484
+ const { linksByItem } = useWorkItemLinks(
2485
+ item?.workspaceId,
2486
+ linkSourceProduct
2487
+ );
2488
+ const links = item ? linksByItem.get(item.id) ?? [] : [];
2431
2489
  const [editing, setEditing] = useState(false);
2432
2490
  const [confirmingDelete, setConfirmingDelete] = useState(false);
2433
2491
  const [title, setTitle] = useState("");
@@ -2595,7 +2653,7 @@ function TaskDetailCore({
2595
2653
  ),
2596
2654
  /* @__PURE__ */ jsx("span", { className: "text-foreground", children: resolveAssignee?.(item.assigneeId)?.name ?? L.assigned })
2597
2655
  ] }) : /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: L.unassigned }) }),
2598
- !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" }) }),
2599
2657
  !hidden.has("storyPoints") && /* @__PURE__ */ jsx(Field, { label: L.storyPointsLabel, children: /* @__PURE__ */ jsx("span", { className: "text-foreground", children: item.storyPoints ?? 0 }) }),
2600
2658
  !hidden.has("description") && /* @__PURE__ */ jsxs("div", { className: "sm:col-span-2", children: [
2601
2659
  /* @__PURE__ */ jsx("dt", { className: labelClass2, children: L.description }),
@@ -2611,6 +2669,29 @@ function TaskDetailCore({
2611
2669
  },
2612
2670
  l
2613
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
+ ] })
2614
2695
  ] })
2615
2696
  ] }) : /* @__PURE__ */ jsxs("form", { onSubmit: handleSave, className: "space-y-4", children: [
2616
2697
  /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
@@ -2818,6 +2899,7 @@ function TasksPageCore({
2818
2899
  hiddenFields,
2819
2900
  views,
2820
2901
  defaultView = "board",
2902
+ confirmMove,
2821
2903
  resolveAssignee,
2822
2904
  linkSourceProduct,
2823
2905
  renderLinkIcon,
@@ -2896,6 +2978,8 @@ function TasksPageCore({
2896
2978
  hiddenFields,
2897
2979
  resolveAssignee,
2898
2980
  assigneeOptions,
2981
+ linkSourceProduct,
2982
+ renderLinkIcon,
2899
2983
  onUpdated: handleUpdated,
2900
2984
  onDeleted: handleDeleted
2901
2985
  }
@@ -2939,6 +3023,7 @@ function TasksPageCore({
2939
3023
  projectId: boardProjectId,
2940
3024
  columns,
2941
3025
  readonly,
3026
+ confirmMove,
2942
3027
  hideHeader: true,
2943
3028
  priority,
2944
3029
  type,