@burdenoff/planmagnet-fe-sdk 2026.706.5 → 2026.706.6

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
@@ -740,6 +740,19 @@ interface TaskTimelineCoreProps {
740
740
  resolveAssignee?: ResolveAssignee;
741
741
  /** Fetch + flag project milestones on the timeline (default true). */
742
742
  showMilestones?: boolean;
743
+ /**
744
+ * Allow creating milestones from the timeline (an inline name + due-date
745
+ * form in the milestones strip). Requires a create project (see
746
+ * `milestoneProjectId`). Gate on the host's manage permission. Default false.
747
+ */
748
+ canCreateMilestone?: boolean;
749
+ /**
750
+ * Project new milestones are created under. Defaults to `projectId`. Set this
751
+ * when the timeline's task-filter project differs from the project milestones
752
+ * should belong to — e.g. an unscoped "all tasks" board that still has a
753
+ * default create-project.
754
+ */
755
+ milestoneProjectId?: string;
743
756
  className?: string;
744
757
  workspaceId?: string;
745
758
  }
@@ -752,7 +765,7 @@ interface TaskTimelineCoreProps {
752
765
  * honest to the data. Overdue and undated work is surfaced explicitly so nothing
753
766
  * hides. BYO-Apollo, semantic-token only, farm-free.
754
767
  */
755
- declare function TaskTimelineCore({ projectId, priority, type, search, assigneeId, defaultMode, onSelect, resolveAssignee, showMilestones, className, workspaceId: workspaceIdProp, }: TaskTimelineCoreProps): react.JSX.Element;
768
+ declare function TaskTimelineCore({ projectId, priority, type, search, assigneeId, defaultMode, onSelect, resolveAssignee, showMilestones, canCreateMilestone, milestoneProjectId, className, workspaceId: workspaceIdProp, }: TaskTimelineCoreProps): react.JSX.Element;
756
769
 
757
770
  /** The three task views the toolbar can switch between. */
758
771
  type TaskView = "board" | "list" | "timeline";
@@ -946,6 +959,9 @@ interface TaskLabels {
946
959
  overdue: string;
947
960
  noDueDate: string;
948
961
  milestones: string;
962
+ addMilestone: string;
963
+ milestoneNamePlaceholder: string;
964
+ failedToCreateMilestone: string;
949
965
  today: string;
950
966
  week: string;
951
967
  month: string;
@@ -1061,6 +1077,11 @@ interface TasksPageCoreProps {
1061
1077
  columns?: BoardColumn[];
1062
1078
  /** Read-only — hides the "New task" button, disables drag + detail editing. */
1063
1079
  readonly?: boolean;
1080
+ /**
1081
+ * Allow creating milestones from the Timeline view (inline form in the
1082
+ * milestones strip). Requires a `boardProjectId`. Defaults to `!readonly`.
1083
+ */
1084
+ canCreateMilestone?: boolean;
1064
1085
  /**
1065
1086
  * Field names to hide across the board cards, create form, and detail view.
1066
1087
  * A product-agnostic hook so hosts can suppress fields that don't apply.
@@ -1137,7 +1158,7 @@ interface TasksPageCoreProps {
1137
1158
  * BYO-Apollo: reads the ambient `<ApolloProvider>` and takes `workspaceId` from
1138
1159
  * a prop or the PlanMagnetProvider context. Semantic tokens only, farm-free.
1139
1160
  */
1140
- declare function TasksPageCore({ workspaceId: workspaceIdProp, projectId, boardProjectId, columns, readonly, hiddenFields, views, defaultView, confirmMove, resolveAssignee, linkSourceProduct, renderLinkIcon, assigneeOptions, currentActorId, commentsEntityType, onMention, labels, openItemId, header, onTaskSaved, createSurface, detailSurface, className, }: TasksPageCoreProps): react.JSX.Element;
1161
+ declare function TasksPageCore({ workspaceId: workspaceIdProp, projectId, boardProjectId, columns, readonly, canCreateMilestone, hiddenFields, views, defaultView, confirmMove, resolveAssignee, linkSourceProduct, renderLinkIcon, assigneeOptions, currentActorId, commentsEntityType, onMention, labels, openItemId, header, onTaskSaved, createSurface, detailSurface, className, }: TasksPageCoreProps): react.JSX.Element;
1141
1162
 
1142
1163
  interface TasksPageProps extends TasksPageCoreProps {
1143
1164
  /** Workspace API gateway URL (URL mode). */
@@ -1255,11 +1276,28 @@ interface UseWorkItemLinksResult {
1255
1276
  */
1256
1277
  declare function useWorkItemLinks(workspaceId: string | undefined, sourceProduct: string | undefined, limit?: number): UseWorkItemLinksResult;
1257
1278
 
1279
+ /** Args for creating a milestone (subset of PlanmagnetCreateMilestoneInput). */
1280
+ interface CreateMilestoneArgs {
1281
+ projectId: string;
1282
+ name: string;
1283
+ /** A `yyyy-MM-dd` day; stored on the DateTime column at local noon. */
1284
+ dueDate?: string | null;
1285
+ status?: MilestoneStatus;
1286
+ description?: string | null;
1287
+ }
1258
1288
  interface UseMilestonesResult {
1259
1289
  milestones: Milestone[];
1260
1290
  /** Milestones grouped by their due day (yyyy-MM-dd), for timeline flags. */
1261
1291
  byDay: Map<string, Milestone[]>;
1262
1292
  loading: boolean;
1293
+ /** True while a create mutation is in flight. */
1294
+ creating: boolean;
1295
+ /**
1296
+ * Create a milestone and refetch the list. Returns the created milestone, or
1297
+ * throws so the caller can surface a toast. A `yyyy-MM-dd` dueDate is stored
1298
+ * at local noon so it lands on the intended day regardless of timezone.
1299
+ */
1300
+ createMilestone: (args: CreateMilestoneArgs) => Promise<Milestone | null>;
1263
1301
  }
1264
1302
  /**
1265
1303
  * useMilestones — fetch a project's milestones (e.g. a harvest window) for the
package/dist/index.js CHANGED
@@ -6,7 +6,7 @@ import { onError } from '@apollo/client/link/error';
6
6
  import { CombinedGraphQLErrors } from '@apollo/client/errors';
7
7
  import { jsx, jsxs } from 'react/jsx-runtime';
8
8
  import { gql } from '@apollo/client';
9
- import { CircleX, CircleCheckBig, CircleDotDashed, CircleDot, Circle, CircleDashed, User, Link2, CalendarDays, ChevronUp, ChevronDown, ChevronLeft, ChevronRight, Flag, CalendarRange, List, LayoutGrid, Search, Plus, Trash2 } from 'lucide-react';
9
+ import { CircleX, CircleCheckBig, CircleDotDashed, CircleDot, Circle, CircleDashed, User, Link2, CalendarDays, ChevronUp, ChevronDown, ChevronLeft, ChevronRight, Flag, Plus, CalendarRange, List, LayoutGrid, Search, Trash2 } from 'lucide-react';
10
10
  import { clsx } from 'clsx';
11
11
  import { twMerge } from 'tailwind-merge';
12
12
  import { addWeeks, startOfWeek, endOfWeek, eachDayOfInterval, addMonths, startOfMonth, endOfMonth, format, isToday, isSameMonth, startOfDay, parseISO, isValid } from 'date-fns';
@@ -507,6 +507,14 @@ var LIST_MILESTONES = gql`
507
507
  }
508
508
  ${MILESTONE_FIELDS}
509
509
  `;
510
+ var CREATE_MILESTONE = gql`
511
+ mutation CreateMilestone($input: PlanmagnetCreateMilestoneInput!) {
512
+ planmagnetCreateMilestone(input: $input) {
513
+ ...MilestoneFields
514
+ }
515
+ }
516
+ ${MILESTONE_FIELDS}
517
+ `;
510
518
  var DELETE_WORK_ITEM = gql`
511
519
  mutation DeleteWorkItem($id: ID!) {
512
520
  planmagnetDeleteWorkItem(id: $id)
@@ -772,6 +780,9 @@ var DEFAULT_TASK_LABELS = {
772
780
  overdue: "{n} overdue",
773
781
  noDueDate: "No due date ({n})",
774
782
  milestones: "Milestones",
783
+ addMilestone: "Add milestone",
784
+ milestoneNamePlaceholder: "Milestone name",
785
+ failedToCreateMilestone: "Failed to create milestone",
775
786
  today: "Today",
776
787
  week: "Week",
777
788
  month: "Month",
@@ -1996,17 +2007,52 @@ function TaskList({
1996
2007
  }
1997
2008
  );
1998
2009
  }
2010
+ function dayToLocalNoonIso(day) {
2011
+ const m = /^(\d{4})-(\d{2})-(\d{2})/.exec(day);
2012
+ if (!m) return void 0;
2013
+ return new Date(
2014
+ Number(m[1]),
2015
+ Number(m[2]) - 1,
2016
+ Number(m[3]),
2017
+ 12,
2018
+ 0,
2019
+ 0
2020
+ ).toISOString();
2021
+ }
1999
2022
  function useMilestones(workspaceId, opts = {}) {
2000
2023
  const { projectId, enabled = true, limit = 100 } = opts;
2001
- const { data, loading } = useQuery(LIST_MILESTONES, {
2002
- variables: {
2003
- workspaceId,
2004
- ...projectId ? { filter: { projectId } } : {},
2005
- pagination: { first: limit }
2024
+ const { data, loading, refetch } = useQuery(
2025
+ LIST_MILESTONES,
2026
+ {
2027
+ variables: {
2028
+ workspaceId,
2029
+ ...projectId ? { filter: { projectId } } : {},
2030
+ pagination: { first: limit }
2031
+ },
2032
+ skip: !workspaceId || !enabled,
2033
+ errorPolicy: "all"
2034
+ }
2035
+ );
2036
+ const [createMutation, { loading: creating }] = useMutation(CREATE_MILESTONE);
2037
+ const createMilestone = useCallback(
2038
+ async (args) => {
2039
+ const iso = args.dueDate ? dayToLocalNoonIso(args.dueDate) : void 0;
2040
+ const res = await createMutation({
2041
+ variables: {
2042
+ input: {
2043
+ projectId: args.projectId,
2044
+ name: args.name,
2045
+ ...iso ? { dueDate: iso } : {},
2046
+ ...args.status ? { status: args.status } : {},
2047
+ ...args.description ? { description: args.description } : {}
2048
+ }
2049
+ }
2050
+ });
2051
+ await refetch();
2052
+ return res.data?.planmagnetCreateMilestone ?? null;
2006
2053
  },
2007
- skip: !workspaceId || !enabled,
2008
- errorPolicy: "all"
2009
- });
2054
+ [createMutation, refetch]
2055
+ );
2010
2056
  const milestones = useMemo(
2011
2057
  () => data?.planmagnetMilestones?.items ?? [],
2012
2058
  [data]
@@ -2023,7 +2069,7 @@ function useMilestones(workspaceId, opts = {}) {
2023
2069
  }
2024
2070
  return map;
2025
2071
  }, [milestones]);
2026
- return { milestones, byDay, loading };
2072
+ return { milestones, byDay, loading, creating, createMilestone };
2027
2073
  }
2028
2074
  var WEEK_OPTS = { weekStartsOn: 1 };
2029
2075
  function dayKey(d) {
@@ -2039,18 +2085,49 @@ function TaskTimelineCore({
2039
2085
  onSelect,
2040
2086
  resolveAssignee,
2041
2087
  showMilestones = true,
2088
+ canCreateMilestone = false,
2089
+ milestoneProjectId,
2042
2090
  className,
2043
2091
  workspaceId: workspaceIdProp
2044
2092
  }) {
2045
2093
  const ctx = usePlanMagnetContextSafe();
2046
2094
  const workspaceId = workspaceIdProp ?? ctx?.workspaceId;
2047
2095
  const L = useTaskLabels();
2048
- const { milestones, byDay: milestonesByDay } = useMilestones(workspaceId, {
2096
+ const {
2097
+ milestones,
2098
+ byDay: milestonesByDay,
2099
+ creating: creatingMilestone,
2100
+ createMilestone
2101
+ } = useMilestones(workspaceId, {
2049
2102
  projectId,
2050
2103
  enabled: showMilestones
2051
2104
  });
2052
2105
  const [mode, setMode] = useState(defaultMode);
2053
2106
  const [offset, setOffset] = useState(0);
2107
+ const milestoneProject = milestoneProjectId ?? projectId;
2108
+ const canAddMilestone = canCreateMilestone && Boolean(milestoneProject);
2109
+ const [showMilestoneForm, setShowMilestoneForm] = useState(false);
2110
+ const [milestoneName, setMilestoneName] = useState("");
2111
+ const [milestoneDue, setMilestoneDue] = useState("");
2112
+ const [milestoneError, setMilestoneError] = useState(null);
2113
+ const submitMilestone = async (e) => {
2114
+ e.preventDefault();
2115
+ const name = milestoneName.trim();
2116
+ if (!name || !milestoneProject) return;
2117
+ setMilestoneError(null);
2118
+ try {
2119
+ await createMilestone({
2120
+ projectId: milestoneProject,
2121
+ name,
2122
+ dueDate: milestoneDue || void 0
2123
+ });
2124
+ setMilestoneName("");
2125
+ setMilestoneDue("");
2126
+ setShowMilestoneForm(false);
2127
+ } catch {
2128
+ setMilestoneError(L.failedToCreateMilestone);
2129
+ }
2130
+ };
2054
2131
  const { data, loading, error } = useQuery(
2055
2132
  LIST_WORK_ITEMS,
2056
2133
  {
@@ -2198,12 +2275,82 @@ function TaskTimelineCore({
2198
2275
  t.id
2199
2276
  )) })
2200
2277
  ] }),
2201
- milestones.length > 0 && /* @__PURE__ */ jsxs("div", { className: "rounded-md border border-primary/30 bg-primary/5 px-3 py-2", children: [
2202
- /* @__PURE__ */ jsxs("p", { className: "mb-1 flex items-center gap-1 text-xs font-semibold text-primary", children: [
2203
- /* @__PURE__ */ jsx(Flag, { className: "h-3.5 w-3.5", "aria-hidden": "true" }),
2204
- L.milestones
2278
+ (milestones.length > 0 || canAddMilestone) && /* @__PURE__ */ jsxs("div", { className: "rounded-md border border-primary/30 bg-primary/5 px-3 py-2", children: [
2279
+ /* @__PURE__ */ jsxs("div", { className: "mb-1 flex items-center justify-between gap-2", children: [
2280
+ /* @__PURE__ */ jsxs("p", { className: "flex items-center gap-1 text-xs font-semibold text-primary", children: [
2281
+ /* @__PURE__ */ jsx(Flag, { className: "h-3.5 w-3.5", "aria-hidden": "true" }),
2282
+ L.milestones
2283
+ ] }),
2284
+ canAddMilestone && /* @__PURE__ */ jsx(
2285
+ "button",
2286
+ {
2287
+ type: "button",
2288
+ onClick: () => {
2289
+ setShowMilestoneForm((v) => !v);
2290
+ setMilestoneError(null);
2291
+ },
2292
+ "aria-label": L.addMilestone,
2293
+ "aria-expanded": showMilestoneForm,
2294
+ title: L.addMilestone,
2295
+ className: "flex h-6 w-6 items-center justify-center rounded-md border border-primary/40 text-primary hover:bg-primary/10",
2296
+ children: /* @__PURE__ */ jsx(Plus, { className: "h-3.5 w-3.5", "aria-hidden": "true" })
2297
+ }
2298
+ )
2205
2299
  ] }),
2206
- /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-1.5", children: milestones.map((m) => /* @__PURE__ */ jsx(MilestoneFlag, { milestone: m }, m.id)) })
2300
+ showMilestoneForm && canAddMilestone && /* @__PURE__ */ jsxs(
2301
+ "form",
2302
+ {
2303
+ onSubmit: submitMilestone,
2304
+ className: "mb-2 flex flex-wrap items-center gap-2",
2305
+ children: [
2306
+ /* @__PURE__ */ jsx(
2307
+ "input",
2308
+ {
2309
+ type: "text",
2310
+ value: milestoneName,
2311
+ onChange: (e) => setMilestoneName(e.target.value),
2312
+ placeholder: L.milestoneNamePlaceholder,
2313
+ "aria-label": L.milestoneNamePlaceholder,
2314
+ autoFocus: true,
2315
+ className: "h-8 min-w-40 flex-1 rounded-md border border-border bg-background px-2 text-xs text-foreground"
2316
+ }
2317
+ ),
2318
+ /* @__PURE__ */ jsx(
2319
+ "input",
2320
+ {
2321
+ type: "date",
2322
+ value: milestoneDue,
2323
+ onChange: (e) => setMilestoneDue(e.target.value),
2324
+ "aria-label": L.dueDateLabel,
2325
+ className: "h-8 rounded-md border border-border bg-background px-2 text-xs text-foreground"
2326
+ }
2327
+ ),
2328
+ /* @__PURE__ */ jsx(
2329
+ "button",
2330
+ {
2331
+ type: "submit",
2332
+ disabled: !milestoneName.trim() || creatingMilestone,
2333
+ className: "h-8 rounded-md bg-primary px-3 text-xs font-medium text-primary-foreground hover:bg-primary/90 disabled:opacity-50",
2334
+ children: creatingMilestone ? L.creating : L.addMilestone
2335
+ }
2336
+ ),
2337
+ /* @__PURE__ */ jsx(
2338
+ "button",
2339
+ {
2340
+ type: "button",
2341
+ onClick: () => {
2342
+ setShowMilestoneForm(false);
2343
+ setMilestoneError(null);
2344
+ },
2345
+ className: "h-8 rounded-md border border-border px-3 text-xs font-medium text-foreground hover:bg-muted/50",
2346
+ children: L.cancel
2347
+ }
2348
+ )
2349
+ ]
2350
+ }
2351
+ ),
2352
+ milestoneError && /* @__PURE__ */ jsx("p", { className: "mb-1 text-[11px] text-destructive", children: milestoneError }),
2353
+ milestones.length > 0 && /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-1.5", children: milestones.map((m) => /* @__PURE__ */ jsx(MilestoneFlag, { milestone: m }, m.id)) })
2207
2354
  ] }),
2208
2355
  mode === "week" ? /* @__PURE__ */ jsx("div", { className: "grid grid-cols-7 gap-2", children: days.map((day) => {
2209
2356
  const items = byDay.get(dayKey(day)) ?? [];
@@ -3342,6 +3489,7 @@ function TasksPageCore({
3342
3489
  boardProjectId,
3343
3490
  columns,
3344
3491
  readonly,
3492
+ canCreateMilestone,
3345
3493
  hiddenFields,
3346
3494
  views,
3347
3495
  defaultView = "board",
@@ -3539,7 +3687,9 @@ function TasksPageCore({
3539
3687
  search,
3540
3688
  assigneeId: mineAssignee,
3541
3689
  resolveAssignee,
3542
- onSelect: handleSelect
3690
+ onSelect: handleSelect,
3691
+ canCreateMilestone: canCreateMilestone ?? !readonly,
3692
+ milestoneProjectId: boardProjectId ?? projectId
3543
3693
  }
3544
3694
  ),
3545
3695
  showInlineDetail && /* @__PURE__ */ jsx("div", { className: "pb-6", children: detailView }),