@burdenoff/microfe-planmagnet 2026.1001.6 → 2026.1001.8
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/planmagnet/PlanMagnetRoutes.d.ts.map +1 -1
- package/dist/planmagnet/PlanMagnetRoutes.js +10 -6
- package/dist/planmagnet/PlanMagnetRoutes.js.map +1 -1
- package/dist/planmagnet/automation/automationRuleTypes.d.ts +31 -19
- package/dist/planmagnet/automation/automationRuleTypes.d.ts.map +1 -1
- package/dist/planmagnet/automation/automationRuleTypes.js +4 -8
- package/dist/planmagnet/automation/automationRuleTypes.js.map +1 -1
- package/dist/planmagnet/controls/FeatureControls.d.ts +18 -1
- package/dist/planmagnet/controls/FeatureControls.d.ts.map +1 -1
- package/dist/planmagnet/controls/FeatureControls.js +259 -202
- package/dist/planmagnet/controls/FeatureControls.js.map +1 -1
- package/dist/planmagnet/controls/FeedbackControls.d.ts +70 -0
- package/dist/planmagnet/controls/FeedbackControls.d.ts.map +1 -0
- package/dist/planmagnet/controls/FeedbackControls.js +153 -0
- package/dist/planmagnet/controls/FeedbackControls.js.map +1 -0
- package/dist/planmagnet/controls/SyncControls.d.ts.map +1 -1
- package/dist/planmagnet/controls/SyncControls.js +1 -0
- package/dist/planmagnet/controls/SyncControls.js.map +1 -1
- package/dist/planmagnet/controls/WebhookSubscriptionControls.d.ts +47 -0
- package/dist/planmagnet/controls/WebhookSubscriptionControls.d.ts.map +1 -0
- package/dist/planmagnet/controls/WebhookSubscriptionControls.js +542 -0
- package/dist/planmagnet/controls/WebhookSubscriptionControls.js.map +1 -0
- package/dist/planmagnet/dashboards/DashboardUI.js +7 -7
- package/dist/planmagnet/dashboards/cards/RiskSeverityCard.js +4 -4
- package/dist/planmagnet/forecast/DeliveryForecastCard.js +8 -8
- package/dist/planmagnet/forecast/ProjectOutlookCard.js +10 -10
- package/dist/planmagnet/forecast/ProjectRiskSummaryCard.js +9 -9
- package/dist/planmagnet/forecast/ResourceForecastCard.js +6 -6
- package/dist/planmagnet/hooks/useLiveRefresh.d.ts +33 -2
- package/dist/planmagnet/hooks/useLiveRefresh.d.ts.map +1 -1
- package/dist/planmagnet/hooks/useLiveRefresh.js +71 -42
- package/dist/planmagnet/hooks/useLiveRefresh.js.map +1 -1
- package/dist/planmagnet/integrations/syncTypes.d.ts +2 -1
- package/dist/planmagnet/integrations/syncTypes.d.ts.map +1 -1
- package/dist/planmagnet/integrations/syncTypes.js +19 -9
- package/dist/planmagnet/integrations/syncTypes.js.map +1 -1
- package/dist/planmagnet/pages/AnalyticsAnalyticsPagePage.js +8 -8
- package/dist/planmagnet/pages/DashboardDashboardPagePage.js +23 -23
- package/dist/planmagnet/pages/DocsDocDetailPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/DocsDocDetailPagePage.js +340 -248
- package/dist/planmagnet/pages/DocsDocDetailPagePage.js.map +1 -1
- package/dist/planmagnet/pages/IntegrationsSyncDetailPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/IntegrationsSyncDetailPagePage.js +172 -167
- package/dist/planmagnet/pages/IntegrationsSyncDetailPagePage.js.map +1 -1
- package/dist/planmagnet/pages/IntegrationsSyncsPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/IntegrationsSyncsPagePage.js +92 -83
- package/dist/planmagnet/pages/IntegrationsSyncsPagePage.js.map +1 -1
- package/dist/planmagnet/pages/IntegrationsWebhooksPagePage.d.ts +2 -0
- package/dist/planmagnet/pages/IntegrationsWebhooksPagePage.d.ts.map +1 -0
- package/dist/planmagnet/pages/IntegrationsWebhooksPagePage.js +175 -0
- package/dist/planmagnet/pages/IntegrationsWebhooksPagePage.js.map +1 -0
- package/dist/planmagnet/pages/ProductsFeatureDetailPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/ProductsFeatureDetailPagePage.js +151 -126
- package/dist/planmagnet/pages/ProductsFeatureDetailPagePage.js.map +1 -1
- package/dist/planmagnet/pages/ProductsFeaturesPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/ProductsFeaturesPagePage.js +136 -129
- package/dist/planmagnet/pages/ProductsFeaturesPagePage.js.map +1 -1
- package/dist/planmagnet/pages/ProductsNewFeaturePagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/ProductsNewFeaturePagePage.js +149 -119
- package/dist/planmagnet/pages/ProductsNewFeaturePagePage.js.map +1 -1
- package/dist/planmagnet/pages/ProductsProductsPagePage.js +13 -13
- package/dist/planmagnet/pages/ProductsRoadmapAnalyticsPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/ProductsRoadmapAnalyticsPagePage.js +153 -117
- package/dist/planmagnet/pages/ProductsRoadmapAnalyticsPagePage.js.map +1 -1
- package/dist/planmagnet/pages/ProductsRoadmapDetailPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/ProductsRoadmapDetailPagePage.js +173 -138
- package/dist/planmagnet/pages/ProductsRoadmapDetailPagePage.js.map +1 -1
- package/dist/planmagnet/pages/ProjectsAtRiskPagePage.js +9 -9
- package/dist/planmagnet/pages/ProjectsBudgetsPagePage.js +8 -8
- package/dist/planmagnet/pages/ProjectsProjectsPagePage.js +12 -12
- package/dist/planmagnet/pages/ReportsReportsPagePage.js +23 -23
- package/dist/planmagnet/pages/TasksMyDashboardPagePage.js +18 -18
- package/dist/planmagnet/pages/TasksMyWorkPagePage.js +10 -10
- package/dist/planmagnet/pages/TasksTimeEntriesPagePage.js +5 -5
- package/dist/planmagnet/pages/analytics/DeliveryHealthCards.js +8 -8
- package/dist/planmagnet/pages/analytics/DeliveryKpisCard.js +6 -6
- package/dist/planmagnet/pages/analytics/MetricsCard.js +6 -6
- package/dist/planmagnet/pages/analytics/QualityMetricsCard.js +6 -6
- package/dist/planmagnet/pages/analytics/SprintDeliveryCards.js +8 -8
- package/dist/planmagnet/pages/analytics/StatusFlowCard.js +7 -7
- package/dist/planmagnet/pages/analytics/ThroughputCard.js +7 -7
- package/dist/planmagnet/pages/analytics/WorkBreakdownCard.js +7 -7
- package/dist/planmagnet/people/People.d.ts +14 -0
- package/dist/planmagnet/people/People.d.ts.map +1 -1
- package/dist/planmagnet/people/People.js +36 -22
- package/dist/planmagnet/people/People.js.map +1 -1
- package/dist/planmagnet/roadmaps/RoadmapNowNextLater.d.ts.map +1 -1
- package/dist/planmagnet/roadmaps/RoadmapNowNextLater.js +171 -153
- package/dist/planmagnet/roadmaps/RoadmapNowNextLater.js.map +1 -1
- package/dist/planmagnet/webhooks/webhookTypes.d.ts +54 -0
- package/dist/planmagnet/webhooks/webhookTypes.d.ts.map +1 -0
- package/dist/planmagnet/webhooks/webhookTypes.js +17 -0
- package/dist/planmagnet/webhooks/webhookTypes.js.map +1 -0
- package/package.json +1 -1
|
@@ -3,8 +3,8 @@ import { ShieldAlert as t } from "lucide-react";
|
|
|
3
3
|
import { gql as n } from "@apollo/client";
|
|
4
4
|
import { useQuery as r } from "@apollo/client/react";
|
|
5
5
|
import { jsx as i, jsxs as a } from "react/jsx-runtime";
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
6
|
+
import { Badge as o } from "@burdenoff/fe-libs/ui";
|
|
7
|
+
import { useI18n as s } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
|
|
8
8
|
//#region src/planmagnet/dashboards/cards/RiskSeverityCard.tsx
|
|
9
9
|
var c = n`
|
|
10
10
|
query DashboardProjectRisks($workspaceId: ID!, $projectId: String!) {
|
|
@@ -39,7 +39,7 @@ var c = n`
|
|
|
39
39
|
NEGLIGIBLE: "secondary"
|
|
40
40
|
};
|
|
41
41
|
function f({ workspaceId: n, projectId: f, onDrillDown: p }) {
|
|
42
|
-
let { t: m } =
|
|
42
|
+
let { t: m } = s(), { data: h, loading: g, error: _ } = r(c, {
|
|
43
43
|
variables: {
|
|
44
44
|
workspaceId: n,
|
|
45
45
|
projectId: f
|
|
@@ -72,7 +72,7 @@ function f({ workspaceId: n, projectId: f, onDrillDown: p }) {
|
|
|
72
72
|
children: m("planmagnet.dashboards.pm.risks.noneOpen", "No open risks for this project.")
|
|
73
73
|
}) : /* @__PURE__ */ i("div", {
|
|
74
74
|
className: "flex flex-wrap gap-2",
|
|
75
|
-
children: u.filter((e) => x.has(e)).map((e) => /* @__PURE__ */ a(
|
|
75
|
+
children: u.filter((e) => x.has(e)).map((e) => /* @__PURE__ */ a(o, {
|
|
76
76
|
variant: d[e],
|
|
77
77
|
children: [
|
|
78
78
|
e.toLowerCase(),
|
|
@@ -4,8 +4,8 @@ import { CalendarRange as n } from "lucide-react";
|
|
|
4
4
|
import { gql as r } from "@apollo/client";
|
|
5
5
|
import { useQuery as i } from "@apollo/client/react";
|
|
6
6
|
import { jsx as a, jsxs as o } from "react/jsx-runtime";
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
7
|
+
import { Card as s, CardContent as c, CardHeader as l, CardTitle as u, Checkbox as d } from "@burdenoff/fe-libs/ui";
|
|
8
|
+
import { useI18n as f } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
|
|
9
9
|
//#region src/planmagnet/forecast/DeliveryForecastCard.tsx
|
|
10
10
|
var p = r`
|
|
11
11
|
query PlanmagnetDeliveryForecast(
|
|
@@ -49,7 +49,7 @@ function m(e) {
|
|
|
49
49
|
});
|
|
50
50
|
}
|
|
51
51
|
function h({ workspaceId: r, projectId: h, sprintId: g, sprintName: _ }) {
|
|
52
|
-
let { t: v } =
|
|
52
|
+
let { t: v } = f(), [y, b] = t(!1), x = y && !!g, { data: S, loading: C, error: w } = i(p, {
|
|
53
53
|
variables: {
|
|
54
54
|
workspaceId: r,
|
|
55
55
|
projectId: h,
|
|
@@ -57,10 +57,10 @@ function h({ workspaceId: r, projectId: h, sprintId: g, sprintName: _ }) {
|
|
|
57
57
|
},
|
|
58
58
|
skip: !r || !h
|
|
59
59
|
}), T = S?.planmagnetForecastDelivery;
|
|
60
|
-
return /* @__PURE__ */ o(
|
|
60
|
+
return /* @__PURE__ */ o(s, {
|
|
61
61
|
"data-testid": "forecast-card",
|
|
62
|
-
children: [/* @__PURE__ */ o(
|
|
63
|
-
/* @__PURE__ */ o(
|
|
62
|
+
children: [/* @__PURE__ */ o(l, { children: [
|
|
63
|
+
/* @__PURE__ */ o(u, {
|
|
64
64
|
className: "flex items-center gap-2",
|
|
65
65
|
children: [/* @__PURE__ */ a(n, { className: "h-4 w-4" }), v("planmagnet.forecast.delivery.title", "Delivery forecast")]
|
|
66
66
|
}),
|
|
@@ -71,13 +71,13 @@ function h({ workspaceId: r, projectId: h, sprintId: g, sprintName: _ }) {
|
|
|
71
71
|
g && /* @__PURE__ */ o("label", {
|
|
72
72
|
htmlFor: `forecast-scope-sprint-${h}`,
|
|
73
73
|
className: "flex items-center gap-2 pt-1 text-xs font-normal text-muted-foreground",
|
|
74
|
-
children: [/* @__PURE__ */ a(
|
|
74
|
+
children: [/* @__PURE__ */ a(d, {
|
|
75
75
|
id: `forecast-scope-sprint-${h}`,
|
|
76
76
|
checked: y,
|
|
77
77
|
onCheckedChange: (e) => b(e === !0)
|
|
78
78
|
}), v("planmagnet.forecast.delivery.scopeToggle", "Only this sprint's items ({{sprint}})", { sprint: _ ?? v("planmagnet.forecast.delivery.thisSprint", "this sprint") })]
|
|
79
79
|
})
|
|
80
|
-
] }), /* @__PURE__ */ a(
|
|
80
|
+
] }), /* @__PURE__ */ a(c, { children: h ? C && !S ? /* @__PURE__ */ a("div", {
|
|
81
81
|
className: "flex justify-center py-8",
|
|
82
82
|
children: /* @__PURE__ */ a("div", { className: "animate-spin h-6 w-6 border-2 border-primary border-t-transparent rounded-full" })
|
|
83
83
|
}) : w ? /* @__PURE__ */ a("p", {
|
|
@@ -5,8 +5,8 @@ import { Compass as r } from "lucide-react";
|
|
|
5
5
|
import { gql as i } from "@apollo/client";
|
|
6
6
|
import { useQuery as a } from "@apollo/client/react";
|
|
7
7
|
import { jsx as o, jsxs as s } from "react/jsx-runtime";
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
8
|
+
import { Badge as c, Card as l, CardContent as u, CardHeader as d, CardTitle as f } from "@burdenoff/fe-libs/ui";
|
|
9
|
+
import { useI18n as p } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
|
|
10
10
|
//#region src/planmagnet/forecast/ProjectOutlookCard.tsx
|
|
11
11
|
var m = i`
|
|
12
12
|
query PlanmagnetProjectOutlook($workspaceId: ID!, $projectId: ID!) {
|
|
@@ -57,28 +57,28 @@ function _(e) {
|
|
|
57
57
|
return e === null ? "—" : `${Math.round(e * 100)}%`;
|
|
58
58
|
}
|
|
59
59
|
function v({ workspaceId: i, projectId: v }) {
|
|
60
|
-
let { t: y } =
|
|
60
|
+
let { t: y } = p(), b = e(), { data: x, loading: S, error: C } = a(m, {
|
|
61
61
|
variables: {
|
|
62
62
|
workspaceId: i,
|
|
63
63
|
projectId: v
|
|
64
64
|
},
|
|
65
65
|
skip: !i || !v
|
|
66
66
|
}), w = x?.planmagnetProjectOutlook;
|
|
67
|
-
return /* @__PURE__ */ s(
|
|
67
|
+
return /* @__PURE__ */ s(l, {
|
|
68
68
|
"data-testid": "outlook-card",
|
|
69
|
-
children: [/* @__PURE__ */ s(
|
|
69
|
+
children: [/* @__PURE__ */ s(d, {
|
|
70
70
|
className: "flex flex-row items-start justify-between space-y-0",
|
|
71
|
-
children: [/* @__PURE__ */ s("div", { children: [/* @__PURE__ */ s(
|
|
71
|
+
children: [/* @__PURE__ */ s("div", { children: [/* @__PURE__ */ s(f, {
|
|
72
72
|
className: "flex items-center gap-2",
|
|
73
73
|
children: [/* @__PURE__ */ o(r, { className: "h-4 w-4" }), y("planmagnet.forecast.outlook.title", "Project outlook")]
|
|
74
74
|
}), /* @__PURE__ */ o("p", {
|
|
75
75
|
className: "text-xs text-muted-foreground",
|
|
76
76
|
children: y("planmagnet.forecast.outlook.subtitle", "Explicit rule-based heuristics over real data — never AI.")
|
|
77
|
-
})] }), /* @__PURE__ */ o(
|
|
77
|
+
})] }), /* @__PURE__ */ o(c, {
|
|
78
78
|
variant: "outline",
|
|
79
79
|
children: y("planmagnet.forecast.outlook.method", "HEURISTIC")
|
|
80
80
|
})]
|
|
81
|
-
}), /* @__PURE__ */ o(
|
|
81
|
+
}), /* @__PURE__ */ o(u, { children: S && !x ? /* @__PURE__ */ o("div", {
|
|
82
82
|
className: "flex justify-center py-8",
|
|
83
83
|
children: /* @__PURE__ */ o("div", { className: "animate-spin h-6 w-6 border-2 border-primary border-t-transparent rounded-full" })
|
|
84
84
|
}) : C ? /* @__PURE__ */ o("p", {
|
|
@@ -89,7 +89,7 @@ function v({ workspaceId: i, projectId: v }) {
|
|
|
89
89
|
children: [
|
|
90
90
|
/* @__PURE__ */ s("div", {
|
|
91
91
|
className: "flex flex-wrap items-center gap-2",
|
|
92
|
-
children: [/* @__PURE__ */ o(
|
|
92
|
+
children: [/* @__PURE__ */ o(c, {
|
|
93
93
|
variant: h[w.successLikelihood],
|
|
94
94
|
children: y("planmagnet.forecast.outlook.successLikelihood", "Success likelihood: {{level}}", { level: w.successLikelihood })
|
|
95
95
|
}), /* @__PURE__ */ o("span", {
|
|
@@ -107,7 +107,7 @@ function v({ workspaceId: i, projectId: v }) {
|
|
|
107
107
|
className: "rounded-md border p-3",
|
|
108
108
|
children: [/* @__PURE__ */ s("h3", {
|
|
109
109
|
className: "mb-1 flex items-center gap-2 text-sm font-medium",
|
|
110
|
-
children: [y("planmagnet.forecast.outlook.quality", "Quality trend"), /* @__PURE__ */ o(
|
|
110
|
+
children: [y("planmagnet.forecast.outlook.quality", "Quality trend"), /* @__PURE__ */ o(c, {
|
|
111
111
|
variant: g[w.qualityTrend.trend],
|
|
112
112
|
children: w.qualityTrend.trend
|
|
113
113
|
})]
|
|
@@ -6,8 +6,8 @@ import { ShieldAlert as s } from "lucide-react";
|
|
|
6
6
|
import { gql as c } from "@apollo/client";
|
|
7
7
|
import { useQuery as l } from "@apollo/client/react";
|
|
8
8
|
import { jsx as u, jsxs as d } from "react/jsx-runtime";
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
9
|
+
import { Badge as f, Card as p, CardContent as m, CardHeader as h, CardTitle as g } from "@burdenoff/fe-libs/ui";
|
|
10
|
+
import { useI18n as _ } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
|
|
11
11
|
//#region src/planmagnet/forecast/ProjectRiskSummaryCard.tsx
|
|
12
12
|
var v = c`
|
|
13
13
|
query PlanmagnetProjectRiskSummary($workspaceId: ID!, $projectId: ID!) {
|
|
@@ -26,28 +26,28 @@ var v = c`
|
|
|
26
26
|
}
|
|
27
27
|
`;
|
|
28
28
|
function y({ workspaceId: c, projectId: y }) {
|
|
29
|
-
let { t: b } =
|
|
29
|
+
let { t: b } = _(), x = e(), { data: S, loading: C, error: w } = l(v, {
|
|
30
30
|
variables: {
|
|
31
31
|
workspaceId: c,
|
|
32
32
|
projectId: y
|
|
33
33
|
},
|
|
34
34
|
skip: !c || !y
|
|
35
35
|
}), T = S?.planmagnetProjectRiskSummary, E = new Map(T?.byKind.map((e) => [e.kind, e.count]) ?? []), D = new Map(T?.bySeverity.map((e) => [e.severity, e.count]) ?? []);
|
|
36
|
-
return /* @__PURE__ */ d(
|
|
36
|
+
return /* @__PURE__ */ d(p, {
|
|
37
37
|
"data-testid": "risk-summary",
|
|
38
|
-
children: [/* @__PURE__ */ d(
|
|
38
|
+
children: [/* @__PURE__ */ d(h, {
|
|
39
39
|
className: "flex flex-row items-start justify-between space-y-0",
|
|
40
|
-
children: [/* @__PURE__ */ d("div", { children: [/* @__PURE__ */ d(
|
|
40
|
+
children: [/* @__PURE__ */ d("div", { children: [/* @__PURE__ */ d(g, {
|
|
41
41
|
className: "flex items-center gap-2",
|
|
42
42
|
children: [/* @__PURE__ */ u(s, { className: "h-4 w-4" }), b("planmagnet.forecast.riskSummary.title", "At-risk work")]
|
|
43
43
|
}), /* @__PURE__ */ u("p", {
|
|
44
44
|
className: "text-xs text-muted-foreground",
|
|
45
45
|
children: b("planmagnet.forecast.riskSummary.subtitle", "Explicit threshold rules — never AI.")
|
|
46
|
-
})] }), /* @__PURE__ */ u(
|
|
46
|
+
})] }), /* @__PURE__ */ u(f, {
|
|
47
47
|
variant: "outline",
|
|
48
48
|
children: b("planmagnet.forecast.riskSummary.method", "Rule-based")
|
|
49
49
|
})]
|
|
50
|
-
}), /* @__PURE__ */ u(
|
|
50
|
+
}), /* @__PURE__ */ u(m, { children: C && !S ? /* @__PURE__ */ u("p", {
|
|
51
51
|
className: "text-sm text-muted-foreground",
|
|
52
52
|
children: b("planmagnet.forecast.loading", "Loading…")
|
|
53
53
|
}) : w ? /* @__PURE__ */ u("p", {
|
|
@@ -61,7 +61,7 @@ function y({ workspaceId: c, projectId: y }) {
|
|
|
61
61
|
children: [
|
|
62
62
|
/* @__PURE__ */ u("div", {
|
|
63
63
|
className: "flex flex-wrap items-center gap-2",
|
|
64
|
-
children: i.filter((e) => (D.get(e) ?? 0) > 0).map((e) => /* @__PURE__ */ d(
|
|
64
|
+
children: i.filter((e) => (D.get(e) ?? 0) > 0).map((e) => /* @__PURE__ */ d(f, {
|
|
65
65
|
variant: o(e),
|
|
66
66
|
children: [
|
|
67
67
|
n(e),
|
|
@@ -4,8 +4,8 @@ import { Gauge as n } from "lucide-react";
|
|
|
4
4
|
import { gql as r } from "@apollo/client";
|
|
5
5
|
import { useQuery as i } from "@apollo/client/react";
|
|
6
6
|
import { jsx as a, jsxs as o } from "react/jsx-runtime";
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
7
|
+
import { Card as s, CardContent as c, CardHeader as l, CardTitle as u } from "@burdenoff/fe-libs/ui";
|
|
8
|
+
import { useI18n as d } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
|
|
9
9
|
//#region src/planmagnet/forecast/ResourceForecastCard.tsx
|
|
10
10
|
var f = r`
|
|
11
11
|
query PlanmagnetResourceForecast(
|
|
@@ -39,7 +39,7 @@ var f = r`
|
|
|
39
39
|
}
|
|
40
40
|
`;
|
|
41
41
|
function p({ workspaceId: r, teamId: p, projectId: m, horizonSprints: h = 3 }) {
|
|
42
|
-
let { t: g } =
|
|
42
|
+
let { t: g } = d(), { data: _, loading: v, error: y } = i(f, {
|
|
43
43
|
variables: {
|
|
44
44
|
workspaceId: r,
|
|
45
45
|
teamId: p,
|
|
@@ -48,15 +48,15 @@ function p({ workspaceId: r, teamId: p, projectId: m, horizonSprints: h = 3 }) {
|
|
|
48
48
|
},
|
|
49
49
|
skip: !r || !p && !m
|
|
50
50
|
}), b = _?.planmagnetResourceForecast, x = Math.max(1, ...b?.capacityGaps.map((e) => Math.abs(e.gapPoints)) ?? [1]);
|
|
51
|
-
return /* @__PURE__ */ o(
|
|
51
|
+
return /* @__PURE__ */ o(s, {
|
|
52
52
|
"data-testid": "resource-forecast",
|
|
53
|
-
children: [/* @__PURE__ */ o(
|
|
53
|
+
children: [/* @__PURE__ */ o(l, { children: [/* @__PURE__ */ o(u, {
|
|
54
54
|
className: "flex items-center gap-2",
|
|
55
55
|
children: [/* @__PURE__ */ a(n, { className: "h-4 w-4" }), g("planmagnet.forecast.resource.title", "Resource forecast")]
|
|
56
56
|
}), /* @__PURE__ */ a("p", {
|
|
57
57
|
className: "text-xs text-muted-foreground",
|
|
58
58
|
children: g("planmagnet.forecast.resource.subtitle", "Open backlog vs. historical velocity, over the next {{count}} sprint(s).", { count: h })
|
|
59
|
-
})] }), /* @__PURE__ */ a(
|
|
59
|
+
})] }), /* @__PURE__ */ a(c, { children: !p && !m ? null : v && !_ ? /* @__PURE__ */ a("div", {
|
|
60
60
|
className: "flex justify-center py-8",
|
|
61
61
|
children: /* @__PURE__ */ a("div", { className: "animate-spin h-6 w-6 border-2 border-primary border-t-transparent rounded-full" })
|
|
62
62
|
}) : y ? /* @__PURE__ */ a("p", {
|
|
@@ -26,8 +26,20 @@
|
|
|
26
26
|
* configured — see `PlanMagnetProvider`) always take this path; that is expected, not a defect, and is why a
|
|
27
27
|
* subscription error here is never surfaced as a page error.
|
|
28
28
|
*/
|
|
29
|
-
export type LiveRefreshKind = 'workItem' | 'project' | 'sprint';
|
|
29
|
+
export type LiveRefreshKind = 'workItem' | 'project' | 'sprint' | 'roadmap';
|
|
30
30
|
export type LiveRefreshMode = 'connecting' | 'live' | 'polling';
|
|
31
|
+
export interface LiveEvent {
|
|
32
|
+
kind: string;
|
|
33
|
+
id: string;
|
|
34
|
+
workspaceId: string;
|
|
35
|
+
projectId?: string | null;
|
|
36
|
+
/** `roadmap`-kind events only (`PlanmagnetRoadmapEvent`) — the roadmap this change belongs to. Equal to `id`
|
|
37
|
+
* when the change is to the roadmap itself (as opposed to one of its items). */
|
|
38
|
+
roadmapId?: string | null;
|
|
39
|
+
changedFields: string[];
|
|
40
|
+
actorId: string;
|
|
41
|
+
at: string;
|
|
42
|
+
}
|
|
31
43
|
export interface UseLiveRefreshOptions {
|
|
32
44
|
/** Which event stream to subscribe to — see `PlanmagnetOn{Project,Sprint,WorkItem}Updated` in the schema. */
|
|
33
45
|
kind: LiveRefreshKind;
|
|
@@ -41,6 +53,9 @@ export interface UseLiveRefreshOptions {
|
|
|
41
53
|
/** Narrows a `workItem`-kind stream to one item. Unused by every current caller (the board/dashboards want
|
|
42
54
|
* every item's events for their scope) but supported for a future single-item page. */
|
|
43
55
|
workItemId?: string | null;
|
|
56
|
+
/** Required for `kind: 'roadmap'` — `planmagnetOnRoadmapUpdated` is always scoped to exactly one roadmap
|
|
57
|
+
* (PRM-11), there is no workspace-wide roadmap stream. */
|
|
58
|
+
roadmapId?: string | null;
|
|
44
59
|
/** Apollo operation names this page's queries were declared with — refetched by name
|
|
45
60
|
* (`client.refetchQueries({ include })`) on a (debounced, optionally throttled) event. Never the literal
|
|
46
61
|
* query document, and never the event's own payload used as data. */
|
|
@@ -60,6 +75,22 @@ export interface UseLiveRefreshOptions {
|
|
|
60
75
|
* team-scoped sprint (no `projectId` at all) rather than falling back to a workspace-wide stream. Default
|
|
61
76
|
* `true` (no extra gate). */
|
|
62
77
|
enabled?: boolean;
|
|
78
|
+
/** Called with the raw event whenever one arrives — ALONGSIDE, never instead of, the debounced `refetch` this
|
|
79
|
+
* hook already schedules. For a page that needs to react to the event itself, not just "something changed,
|
|
80
|
+
* re-read" — e.g. the roadmap detail page (PRM-11) telling a DELETED event on the roadmap itself
|
|
81
|
+
* (`event.id === event.roadmapId`) apart from one on an item, to show "This roadmap was deleted" instead of
|
|
82
|
+
* racing a refetch against a record that is already gone. This never hands the caller anything to render
|
|
83
|
+
* directly from: `event` is still the same thin notification `useLiveRefresh` never applies as data itself —
|
|
84
|
+
* only `kind`/`id`/`roadmapId` are meaningful, the record always still comes from a query. */
|
|
85
|
+
onEvent?: (event: LiveEvent) => void;
|
|
86
|
+
/** Holds back a pending refetch while `true` — for a page with its own uninterruptible local gesture in
|
|
87
|
+
* progress that a refetch-driven re-render would break (PRM-11: the Now/Next/Later board, for exactly as long
|
|
88
|
+
* as one of its cards is mid-drag, so a live-triggered refetch never replaces the list — and the native HTML5
|
|
89
|
+
* drag source with it — out from under an in-flight drag). An event arriving while paused is never dropped:
|
|
90
|
+
* it still runs through the normal debounce/floor; only the moment the resulting refetch is allowed to
|
|
91
|
+
* actually fire is held, and it fires once, immediately (not re-debounced), the moment this goes back to
|
|
92
|
+
* `false`. Default `false`. */
|
|
93
|
+
paused?: boolean;
|
|
63
94
|
}
|
|
64
95
|
export interface UseLiveRefreshResult {
|
|
65
96
|
/** `null` while a required id (e.g. a sprint page before `sprintId` resolves) hasn't loaded — there is
|
|
@@ -72,5 +103,5 @@ export interface UseLiveRefreshResult {
|
|
|
72
103
|
* "Live" while one of its own streams is actually degraded. `null` (nothing subscribed yet) only when every
|
|
73
104
|
* input is `null`. */
|
|
74
105
|
export declare function combineLiveRefreshModes(...modes: ReadonlyArray<LiveRefreshMode | null>): LiveRefreshMode | null;
|
|
75
|
-
export declare function useLiveRefresh({ kind, projectId, sprintId, workItemId, queries, debounceMs, minIntervalMs, connectTimeoutMs, pollIntervalMs, enabled, }: UseLiveRefreshOptions): UseLiveRefreshResult;
|
|
106
|
+
export declare function useLiveRefresh({ kind, projectId, sprintId, workItemId, roadmapId, queries, debounceMs, minIntervalMs, connectTimeoutMs, pollIntervalMs, enabled, onEvent, paused, }: UseLiveRefreshOptions): UseLiveRefreshResult;
|
|
76
107
|
//# sourceMappingURL=useLiveRefresh.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useLiveRefresh.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/hooks/useLiveRefresh.ts"],"names":[],"mappings":"AAMA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH,MAAM,MAAM,eAAe,GAAG,UAAU,GAAG,SAAS,GAAG,QAAQ,CAAC;
|
|
1
|
+
{"version":3,"file":"useLiveRefresh.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/hooks/useLiveRefresh.ts"],"names":[],"mappings":"AAMA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH,MAAM,MAAM,eAAe,GAAG,UAAU,GAAG,SAAS,GAAG,QAAQ,GAAG,SAAS,CAAC;AAC5E,MAAM,MAAM,eAAe,GAAG,YAAY,GAAG,MAAM,GAAG,SAAS,CAAC;AAEhE,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B;oFACgF;IAChF,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,EAAE,EAAE,MAAM,CAAC;CACZ;AAqED,MAAM,WAAW,qBAAqB;IACpC,6GAA6G;IAC7G,IAAI,EAAE,eAAe,CAAC;IACtB;;+EAE2E;IAC3E,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B;kHAC8G;IAC9G,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB;2FACuF;IACvF,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B;8DAC0D;IAC1D,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B;;yEAEqE;IACrE,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IAC3B,sGAAsG;IACtG,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;mHAE+G;IAC/G,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,4GAA4G;IAC5G,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,6FAA6F;IAC7F,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;;iCAG6B;IAC7B,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB;;;;;;kGAM8F;IAC9F,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,SAAS,KAAK,IAAI,CAAC;IACrC;;;;;;mCAM+B;IAC/B,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,oBAAoB;IACnC;8GAC0G;IAC1G,IAAI,EAAE,eAAe,GAAG,IAAI,CAAC;CAC9B;AAED;;;;sBAIsB;AACtB,wBAAgB,uBAAuB,CACrC,GAAG,KAAK,EAAE,aAAa,CAAC,eAAe,GAAG,IAAI,CAAC,GAC9C,eAAe,GAAG,IAAI,CAMxB;AA+DD,wBAAgB,cAAc,CAAC,EAC7B,IAAI,EACJ,SAAS,EACT,QAAQ,EACR,UAAU,EACV,SAAS,EACT,OAAO,EACP,UAAgB,EAChB,aAAa,EACb,gBAAuB,EACvB,cAAsB,EACtB,OAAc,EACd,OAAO,EACP,MAAc,GACf,EAAE,qBAAqB,GAAG,oBAAoB,CA4K9C"}
|
|
@@ -32,6 +32,18 @@ var l = a`
|
|
|
32
32
|
}
|
|
33
33
|
}
|
|
34
34
|
`, d = a`
|
|
35
|
+
subscription PlanmagnetLiveRoadmapUpdated($workspaceId: ID!, $roadmapId: ID!) {
|
|
36
|
+
planmagnetOnRoadmapUpdated(workspaceId: $workspaceId, roadmapId: $roadmapId) {
|
|
37
|
+
kind
|
|
38
|
+
id
|
|
39
|
+
roadmapId
|
|
40
|
+
workspaceId
|
|
41
|
+
changedFields
|
|
42
|
+
actorId
|
|
43
|
+
at
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
`, f = a`
|
|
35
47
|
subscription PlanmagnetLiveWorkItemUpdated($workspaceId: ID!, $projectId: ID, $workItemId: ID) {
|
|
36
48
|
planmagnetOnWorkItemUpdated(
|
|
37
49
|
workspaceId: $workspaceId
|
|
@@ -48,15 +60,15 @@ var l = a`
|
|
|
48
60
|
}
|
|
49
61
|
}
|
|
50
62
|
`;
|
|
51
|
-
function
|
|
63
|
+
function p(...e) {
|
|
52
64
|
let t = e.filter((e) => e !== null);
|
|
53
65
|
return t.length === 0 ? null : t.includes("polling") ? "polling" : t.includes("connecting") ? "connecting" : "live";
|
|
54
66
|
}
|
|
55
|
-
var
|
|
56
|
-
function
|
|
57
|
-
return e === "project" ? l : e === "sprint" ? u : d;
|
|
67
|
+
var m = 5e3, h = 3;
|
|
68
|
+
function g(e) {
|
|
69
|
+
return e === "project" ? l : e === "sprint" ? u : e === "roadmap" ? d : f;
|
|
58
70
|
}
|
|
59
|
-
function
|
|
71
|
+
function _(e, i, a) {
|
|
60
72
|
let o = r(null), s = r(null), c = r(0), l = r(!1), u = t(() => {
|
|
61
73
|
l.current = !1, c.current = Date.now(), e();
|
|
62
74
|
}, [e]), d = t(() => {
|
|
@@ -81,64 +93,81 @@ function g(e, i, a) {
|
|
|
81
93
|
o.current && clearTimeout(o.current), s.current && clearTimeout(s.current);
|
|
82
94
|
}, []), d;
|
|
83
95
|
}
|
|
84
|
-
function
|
|
85
|
-
let { workspaceId:
|
|
86
|
-
c(),
|
|
87
|
-
}, [
|
|
88
|
-
|
|
89
|
-
|
|
96
|
+
function v({ kind: a, projectId: l, sprintId: u, workItemId: d, roadmapId: f, queries: p, debounceMs: v = 500, minIntervalMs: y, connectTimeoutMs: b = 8e3, pollIntervalMs: x = 3e4, enabled: S = !0, onEvent: C, paused: w = !1 }) {
|
|
97
|
+
let { workspaceId: T } = e(), E = o(), D = !T || a === "sprint" && !u && !l || a === "project" && !l || a === "roadmap" && !f || !S, O = D ? null : `${a}:${T}:${l ?? ""}:${u ?? ""}:${d ?? ""}:${f ?? ""}`, k = t(() => {
|
|
98
|
+
c(), E.refetchQueries({ include: [...p] });
|
|
99
|
+
}, [E, p]), A = r(w);
|
|
100
|
+
n(() => {
|
|
101
|
+
A.current = w;
|
|
102
|
+
}, [w]);
|
|
103
|
+
let j = r(!1), M = t(() => {
|
|
104
|
+
if (A.current) {
|
|
105
|
+
j.current = !0;
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
k();
|
|
109
|
+
}, [k]);
|
|
110
|
+
n(() => {
|
|
111
|
+
!w && j.current && (j.current = !1, k());
|
|
112
|
+
}, [w, k]);
|
|
113
|
+
let N = _(M, v, y), [P, F] = i(O ? "connecting" : null), [I, L] = i(O);
|
|
114
|
+
O !== I && (L(O), F(O ? "connecting" : null)), n(() => {
|
|
115
|
+
if (!O) return;
|
|
90
116
|
let e = setTimeout(() => {
|
|
91
|
-
|
|
92
|
-
},
|
|
117
|
+
F((e) => e === "connecting" ? "live" : e);
|
|
118
|
+
}, b);
|
|
93
119
|
return () => clearTimeout(e);
|
|
94
|
-
}, [
|
|
95
|
-
let
|
|
96
|
-
n(() => (
|
|
97
|
-
|
|
98
|
-
}), [
|
|
99
|
-
let
|
|
100
|
-
if (
|
|
101
|
-
|
|
120
|
+
}, [O, b]);
|
|
121
|
+
let R = r(0), z = r(0), B = r(null);
|
|
122
|
+
n(() => (R.current = Date.now(), z.current = 0, () => {
|
|
123
|
+
B.current && clearTimeout(B.current), B.current = null;
|
|
124
|
+
}), [O]);
|
|
125
|
+
let V = r(null), H = t(() => {
|
|
126
|
+
if (z.current = Date.now() - R.current < m ? z.current + 1 : 0, z.current >= h) {
|
|
127
|
+
F("polling");
|
|
102
128
|
return;
|
|
103
129
|
}
|
|
104
|
-
|
|
105
|
-
|
|
130
|
+
B.current = setTimeout(() => {
|
|
131
|
+
B.current = null, R.current = Date.now(), V.current?.();
|
|
106
132
|
}, 0);
|
|
107
|
-
}, []), { restart:
|
|
133
|
+
}, []), { restart: U } = s(g(a), {
|
|
108
134
|
variables: {
|
|
109
|
-
workspaceId:
|
|
135
|
+
workspaceId: T,
|
|
110
136
|
projectId: l ?? void 0,
|
|
111
137
|
sprintId: u ?? void 0,
|
|
112
|
-
workItemId: d ?? void 0
|
|
138
|
+
workItemId: d ?? void 0,
|
|
139
|
+
roadmapId: f ?? void 0
|
|
113
140
|
},
|
|
114
|
-
skip:
|
|
115
|
-
onData: () => {
|
|
116
|
-
|
|
141
|
+
skip: D,
|
|
142
|
+
onData: ({ data: e }) => {
|
|
143
|
+
F((e) => e === "connecting" ? "live" : e);
|
|
144
|
+
let t = e.data?.planmagnetOnProjectUpdated ?? e.data?.planmagnetOnSprintUpdated ?? e.data?.planmagnetOnWorkItemUpdated ?? e.data?.planmagnetOnRoadmapUpdated;
|
|
145
|
+
t && C?.(t), N();
|
|
117
146
|
},
|
|
118
147
|
onError: (e) => {
|
|
119
148
|
console.debug("[useLiveRefresh] subscription unavailable, falling back to polling:", {
|
|
120
149
|
kind: a,
|
|
121
150
|
message: e instanceof Error ? e.message : String(e)
|
|
122
|
-
}),
|
|
151
|
+
}), F("polling");
|
|
123
152
|
},
|
|
124
|
-
onComplete:
|
|
153
|
+
onComplete: H
|
|
125
154
|
});
|
|
126
155
|
return n(() => {
|
|
127
|
-
|
|
128
|
-
}, [
|
|
129
|
-
if (
|
|
156
|
+
V.current = U;
|
|
157
|
+
}, [U]), n(() => {
|
|
158
|
+
if (D || P !== "polling") return;
|
|
130
159
|
let e = setInterval(() => {
|
|
131
|
-
typeof document < "u" && document.visibilityState === "hidden" ||
|
|
132
|
-
},
|
|
160
|
+
typeof document < "u" && document.visibilityState === "hidden" || M();
|
|
161
|
+
}, x);
|
|
133
162
|
return () => clearInterval(e);
|
|
134
163
|
}, [
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
]), { mode:
|
|
164
|
+
D,
|
|
165
|
+
P,
|
|
166
|
+
x,
|
|
167
|
+
M
|
|
168
|
+
]), { mode: D ? null : P };
|
|
140
169
|
}
|
|
141
170
|
//#endregion
|
|
142
|
-
export {
|
|
171
|
+
export { p as combineLiveRefreshModes, v as useLiveRefresh };
|
|
143
172
|
|
|
144
173
|
//# sourceMappingURL=useLiveRefresh.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useLiveRefresh.js","names":[],"sources":["../../../src/planmagnet/hooks/useLiveRefresh.ts"],"sourcesContent":["import { useCallback, useEffect, useRef, useState } from 'react';\nimport { gql } from '@apollo/client';\nimport { useApolloClient, useSubscription } from '@apollo/client/react';\nimport { clearGraphqlFetchResponseCache } from '@burdenoff/fe-libs/shared/graphql';\nimport { usePlanMagnet } from '../providers/PlanMagnetProvider';\n\n/**\n * Live updates (PDM-05/11/21, ANR-23): a page subscribes to a thin, workspace-scoped event stream over the\n * gateway SSE path and re-reads its own named queries when something relevant changed — it never applies the\n * event payload as data (the payload carries identifiers and `changedFields` only, by server design, so a\n * subscriber re-reads under its own permissions instead of trusting something that bypassed RBAC and row-level\n * scoping). See `useDataSinkSubscription.ts` (microfe-bigconsole) for the sibling pattern this generalizes:\n * `skip` when the ids a subscription needs haven't loaded yet, and treat a transport/backend error as expected\n * degradation, not a page-breaking failure.\n *\n * Every event stream requires `workspaceId` server-side — a subscription whose `workspaceId` is not the\n * caller's own workspace is refused by the gateway, so this hook always reads it from `usePlanMagnet()` rather\n * than accepting it as a prop a caller could get wrong.\n *\n * Connection lifecycle. SSE has no connect acknowledgement: a refused subscription (bad variables, another\n * workspace, no auth) comes back at once as an error, while an accepted one simply stays open with nothing to\n * say until something changes. So a stream still open and error-free after `connectTimeoutMs` is treated as\n * live, and the first event proves it earlier. The server closes a stream after 60 s without events\n * (`REALTIME_SUBSCRIPTION_IDLE_TIMEOUT_MS` in the svc), which Apollo reports as completion — that is routine,\n * so the hook resubscribes rather than going quiet. A stream that keeps completing within seconds of opening is\n * not healthy and degrades to polling.\n *\n * Graceful fallback (this is the point of the hook, not an edge case): if the subscription errors, or keeps\n * closing as soon as it opens, `mode` becomes `'polling'` and a 30-second interval takes over, paused while the\n * tab is hidden (`document.visibilityState`). There is no reconnect-to-live attempt within one mount — once\n * degraded, a page stays on polling until it remounts. `standalone` dev/Storybook builds (no SSE link\n * configured — see `PlanMagnetProvider`) always take this path; that is expected, not a defect, and is why a\n * subscription error here is never surfaced as a page error.\n */\n\nexport type LiveRefreshKind = 'workItem' | 'project' | 'sprint';\nexport type LiveRefreshMode = 'connecting' | 'live' | 'polling';\n\ninterface LiveEvent {\n kind: string;\n id: string;\n workspaceId: string;\n projectId?: string | null;\n changedFields: string[];\n actorId: string;\n at: string;\n}\n\nconst ON_PROJECT_UPDATED = gql`\n subscription PlanmagnetLiveProjectUpdated($workspaceId: ID!, $projectId: ID) {\n planmagnetOnProjectUpdated(workspaceId: $workspaceId, projectId: $projectId) {\n kind\n id\n workspaceId\n changedFields\n actorId\n at\n }\n }\n`;\n\nconst ON_SPRINT_UPDATED = gql`\n subscription PlanmagnetLiveSprintUpdated($workspaceId: ID!, $sprintId: ID, $projectId: ID) {\n planmagnetOnSprintUpdated(\n workspaceId: $workspaceId\n sprintId: $sprintId\n projectId: $projectId\n ) {\n kind\n id\n projectId\n workspaceId\n changedFields\n actorId\n at\n }\n }\n`;\n\nconst ON_WORK_ITEM_UPDATED = gql`\n subscription PlanmagnetLiveWorkItemUpdated($workspaceId: ID!, $projectId: ID, $workItemId: ID) {\n planmagnetOnWorkItemUpdated(\n workspaceId: $workspaceId\n projectId: $projectId\n workItemId: $workItemId\n ) {\n kind\n id\n projectId\n workspaceId\n changedFields\n actorId\n at\n }\n }\n`;\n\nexport interface UseLiveRefreshOptions {\n /** Which event stream to subscribe to — see `PlanmagnetOn{Project,Sprint,WorkItem}Updated` in the schema. */\n kind: LiveRefreshKind;\n /** Narrows the stream server-side. Omitted on `workItem`/`project` this is a workspace-wide stream (the\n * Dashboard and role dashboards want every project's events); `sprint` needs `sprintId` to mean anything on a\n * single sprint's page, so the hook skips subscribing until it has one. */\n projectId?: string | null;\n /** For `kind: 'sprint'`, at least one of `sprintId`/`projectId` is required — one sprint's own events, or\n * every sprint in one project's. Neither means \"skip\" (every sprint workspace-wide has no current caller). */\n sprintId?: string | null;\n /** Narrows a `workItem`-kind stream to one item. Unused by every current caller (the board/dashboards want\n * every item's events for their scope) but supported for a future single-item page. */\n workItemId?: string | null;\n /** Apollo operation names this page's queries were declared with — refetched by name\n * (`client.refetchQueries({ include })`) on a (debounced, optionally throttled) event. Never the literal\n * query document, and never the event's own payload used as data. */\n queries: readonly string[];\n /** Coalesces a burst of events (e.g. a bulk update moving 20 cards) into one refetch. Default 500. */\n debounceMs?: number;\n /** A floor between refetches once debounced, for a page whose queries are expensive to re-run on every\n * change (ANR-23: the Dashboard and role dashboards, at most once per 15s). Unset: only the debounce window\n * applies. A pulse arriving inside the floor is never dropped — it runs at the floor's far edge (trailing). */\n minIntervalMs?: number;\n /** How long an open, error-free subscription stays \"Connecting…\" before it counts as live. Default 8000. */\n connectTimeoutMs?: number;\n /** Polling cadence once on the fallback path. Default 30000 (ANR-23/PDM-05: \"every 30s\"). */\n pollIntervalMs?: number;\n /** An extra gate beyond the kind's own required id(s) — e.g. a sprint page's `workItem`-kind stream (no id of\n * its own is required) should still wait for the sprint's `projectId` to resolve, and skip entirely for a\n * team-scoped sprint (no `projectId` at all) rather than falling back to a workspace-wide stream. Default\n * `true` (no extra gate). */\n enabled?: boolean;\n}\n\nexport interface UseLiveRefreshResult {\n /** `null` while a required id (e.g. a sprint page before `sprintId` resolves) hasn't loaded — there is\n * nothing to subscribe to yet, so `LiveStatus` renders nothing rather than a misleading \"Connecting…\". */\n mode: LiveRefreshMode | null;\n}\n\n/** Rolls up more than one `useLiveRefresh` result into the single indicator a page shows (e.g. the sprint\n * detail page subscribes to both sprint events and the sprint's project's work item events). Reports the least\n * favorable non-null mode present — `'polling'` beats `'connecting'` beats `'live'` — so the page never claims\n * \"Live\" while one of its own streams is actually degraded. `null` (nothing subscribed yet) only when every\n * input is `null`. */\nexport function combineLiveRefreshModes(\n ...modes: ReadonlyArray<LiveRefreshMode | null>\n): LiveRefreshMode | null {\n const present = modes.filter((mode): mode is LiveRefreshMode => mode !== null);\n if (present.length === 0) return null;\n if (present.includes('polling')) return 'polling';\n if (present.includes('connecting')) return 'connecting';\n return 'live';\n}\n\nconst RAPID_CLOSE_MS = 5000;\nconst RAPID_CLOSE_LIMIT = 3;\n\nfunction subscriptionDocumentFor(kind: LiveRefreshKind) {\n if (kind === 'project') return ON_PROJECT_UPDATED;\n if (kind === 'sprint') return ON_SPRINT_UPDATED;\n return ON_WORK_ITEM_UPDATED;\n}\n\n/** A debounced, optionally-throttled trigger: bursts inside `debounceMs` coalesce into one call; once debounced,\n * a call inside `minIntervalMs` of the last one is never dropped — it runs at the floor's far edge instead.\n * `run` is expected to be a `useCallback`-stable function (the hook below's `refetch` is, since it only depends\n * on `client` and `queries`) — accepted as a normal dependency rather than stashed in a ref, so it is never\n * read or written outside an effect/callback. */\nfunction useCoalescedTrigger(\n run: () => void,\n debounceMs: number,\n minIntervalMs: number | undefined\n) {\n const debounceTimer = useRef<ReturnType<typeof setTimeout> | null>(null);\n const floorTimer = useRef<ReturnType<typeof setTimeout> | null>(null);\n const lastRunAt = useRef(0);\n const floorPending = useRef(false);\n\n const runNow = useCallback(() => {\n floorPending.current = false;\n lastRunAt.current = Date.now();\n run();\n }, [run]);\n\n const trigger = useCallback(() => {\n if (debounceTimer.current) clearTimeout(debounceTimer.current);\n debounceTimer.current = setTimeout(() => {\n debounceTimer.current = null;\n if (!minIntervalMs) {\n runNow();\n return;\n }\n const elapsed = Date.now() - lastRunAt.current;\n if (elapsed >= minIntervalMs) {\n runNow();\n return;\n }\n if (floorPending.current) return; // a trailing run is already scheduled for this window\n floorPending.current = true;\n floorTimer.current = setTimeout(runNow, minIntervalMs - elapsed);\n }, debounceMs);\n }, [debounceMs, minIntervalMs, runNow]);\n\n useEffect(\n () => () => {\n if (debounceTimer.current) clearTimeout(debounceTimer.current);\n if (floorTimer.current) clearTimeout(floorTimer.current);\n },\n []\n );\n\n return trigger;\n}\n\nexport function useLiveRefresh({\n kind,\n projectId,\n sprintId,\n workItemId,\n queries,\n debounceMs = 500,\n minIntervalMs,\n connectTimeoutMs = 8000,\n pollIntervalMs = 30000,\n enabled = true,\n}: UseLiveRefreshOptions): UseLiveRefreshResult {\n const { workspaceId } = usePlanMagnet();\n const client = useApolloClient();\n\n // 'sprint' means something scoped to either one sprint (a sprint's own page) or every sprint in one project (a\n // project-scoped dashboard) — it needs at least one of the two ids, never neither (that would be every sprint\n // in the whole workspace, which no current caller wants). 'project' always needs its one id.\n const requiredIdMissing =\n (kind === 'sprint' && !sprintId && !projectId) || (kind === 'project' && !projectId);\n const skip = !workspaceId || requiredIdMissing || !enabled;\n\n // Identifies what we'd subscribe to right now — `null` when there's nothing to. `queries` is deliberately not\n // part of this: which QUERIES a page refetches changing (it never does, in practice — every caller passes a\n // module-level const array) shouldn't reset a live connection, only what STREAM we're on should.\n const subscriptionKey = skip\n ? null\n : `${kind}:${workspaceId}:${projectId ?? ''}:${sprintId ?? ''}:${workItemId ?? ''}`;\n\n // fe-libs replays an identical query from its 15 s fetch-layer cache (shared/graphql/fetch.ts), and Apollo's\n // refetchQueries goes through it — so an event arriving within 15 s of the page's last read would re-read the\n // stale copy and the change would not show. Measured in prod: a card created ~12 s after the board loaded never\n // appeared. The cache is dropped first, exactly as a successful mutation does.\n const refetch = useCallback(() => {\n clearGraphqlFetchResponseCache();\n void client.refetchQueries({ include: [...queries] });\n }, [client, queries]);\n\n const trigger = useCoalescedTrigger(refetch, debounceMs, minIntervalMs);\n\n const [mode, setMode] = useState<LiveRefreshMode | null>(subscriptionKey ? 'connecting' : null);\n const [modeKey, setModeKey] = useState<string | null>(subscriptionKey);\n\n // Reset to \"connecting\" (or \"nothing to do\") the moment what we'd subscribe to changes — switching project,\n // sprint or page. This is the React-documented \"adjust state when a value changes\" pattern: a direct setState\n // call guarded by comparing against the last-seen key, during render, rather than in an effect — it lands in\n // the very render that changed, with no stale \"still connecting to the OLD target\" frame first.\n if (subscriptionKey !== modeKey) {\n setModeKey(subscriptionKey);\n setMode(subscriptionKey ? 'connecting' : null);\n }\n\n // The connect window needs a real timer; its only setState call lives inside the timeout's own callback (an\n // update \"when external state changes\"), never synchronously in the effect body. A refused subscription has\n // already errored into 'polling' by now (errors arrive at once), so a still-\"connecting\" one is open and\n // accepted — see the lifecycle note above.\n useEffect(() => {\n if (!subscriptionKey) return;\n const timer = setTimeout(() => {\n setMode((current) => (current === 'connecting' ? 'live' : current));\n }, connectTimeoutMs);\n return () => clearTimeout(timer);\n }, [subscriptionKey, connectTimeoutMs]);\n\n // Rapid-completion guard for the resubscribe below: when each stream closes within RAPID_CLOSE_MS of opening,\n // RAPID_CLOSE_LIMIT times in a row, the transport is not holding the stream open and polling takes over.\n const openedAt = useRef(0);\n const rapidCloses = useRef(0);\n const resubscribeTimer = useRef<ReturnType<typeof setTimeout> | null>(null);\n useEffect(() => {\n openedAt.current = Date.now();\n rapidCloses.current = 0;\n return () => {\n if (resubscribeTimer.current) clearTimeout(resubscribeTimer.current);\n resubscribeTimer.current = null;\n };\n }, [subscriptionKey]);\n\n // The server's idle close (60 s without events), or the ALB ending a long-lived response: routine, so open a\n // fresh stream. Deferred a tick so Apollo finishes tearing down the completed one first. `restart` comes back\n // from `useSubscription` below, so it is reached through a ref kept current by an effect.\n const restartRef = useRef<(() => void) | null>(null);\n const handleComplete = useCallback(() => {\n const lived = Date.now() - openedAt.current;\n rapidCloses.current = lived < RAPID_CLOSE_MS ? rapidCloses.current + 1 : 0;\n if (rapidCloses.current >= RAPID_CLOSE_LIMIT) {\n setMode('polling');\n return;\n }\n resubscribeTimer.current = setTimeout(() => {\n resubscribeTimer.current = null;\n openedAt.current = Date.now();\n restartRef.current?.();\n }, 0);\n }, []);\n\n const { restart } = useSubscription<{\n planmagnetOnProjectUpdated?: LiveEvent;\n planmagnetOnSprintUpdated?: LiveEvent;\n planmagnetOnWorkItemUpdated?: LiveEvent;\n }>(subscriptionDocumentFor(kind), {\n variables: {\n workspaceId,\n projectId: projectId ?? undefined,\n sprintId: sprintId ?? undefined,\n workItemId: workItemId ?? undefined,\n },\n skip,\n onData: () => {\n // Proof the subscription is live: the connect-timeout above only flips a still-\"connecting\" mode, so this\n // is a no-op once already 'live', and never resurrects a mode that errored into 'polling'.\n setMode((current) => (current === 'connecting' ? 'live' : current));\n trigger();\n },\n onError: (error) => {\n // Covers both a real backend/network failure and the expected \"no SSE transport configured\" case in a\n // standalone/Storybook build (see PlanMagnetProvider) — either way the correct behavior is the same:\n // degrade to polling rather than break the page. Logged at debug level only; this is not a user-facing\n // error state.\n console.debug('[useLiveRefresh] subscription unavailable, falling back to polling:', {\n kind,\n message: error instanceof Error ? error.message : String(error),\n });\n setMode('polling');\n },\n onComplete: handleComplete,\n });\n\n useEffect(() => {\n restartRef.current = restart;\n }, [restart]);\n\n // Polling fallback: the same queries, every `pollIntervalMs`, paused while the tab is hidden. No separate\n // throttle window here — the interval itself is the pacing (30s default, already above any ANR-23 floor).\n useEffect(() => {\n if (skip || mode !== 'polling') return;\n const id = setInterval(() => {\n if (typeof document !== 'undefined' && document.visibilityState === 'hidden') return;\n refetch();\n }, pollIntervalMs);\n return () => clearInterval(id);\n }, [skip, mode, pollIntervalMs, refetch]);\n\n return { mode: skip ? null : mode };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"useLiveRefresh.js","names":[],"sources":["../../../src/planmagnet/hooks/useLiveRefresh.ts"],"sourcesContent":["import { useCallback, useEffect, useRef, useState } from 'react';\nimport { gql } from '@apollo/client';\nimport { useApolloClient, useSubscription } from '@apollo/client/react';\nimport { clearGraphqlFetchResponseCache } from '@burdenoff/fe-libs/shared/graphql';\nimport { usePlanMagnet } from '../providers/PlanMagnetProvider';\n\n/**\n * Live updates (PDM-05/11/21, ANR-23): a page subscribes to a thin, workspace-scoped event stream over the\n * gateway SSE path and re-reads its own named queries when something relevant changed — it never applies the\n * event payload as data (the payload carries identifiers and `changedFields` only, by server design, so a\n * subscriber re-reads under its own permissions instead of trusting something that bypassed RBAC and row-level\n * scoping). See `useDataSinkSubscription.ts` (microfe-bigconsole) for the sibling pattern this generalizes:\n * `skip` when the ids a subscription needs haven't loaded yet, and treat a transport/backend error as expected\n * degradation, not a page-breaking failure.\n *\n * Every event stream requires `workspaceId` server-side — a subscription whose `workspaceId` is not the\n * caller's own workspace is refused by the gateway, so this hook always reads it from `usePlanMagnet()` rather\n * than accepting it as a prop a caller could get wrong.\n *\n * Connection lifecycle. SSE has no connect acknowledgement: a refused subscription (bad variables, another\n * workspace, no auth) comes back at once as an error, while an accepted one simply stays open with nothing to\n * say until something changes. So a stream still open and error-free after `connectTimeoutMs` is treated as\n * live, and the first event proves it earlier. The server closes a stream after 60 s without events\n * (`REALTIME_SUBSCRIPTION_IDLE_TIMEOUT_MS` in the svc), which Apollo reports as completion — that is routine,\n * so the hook resubscribes rather than going quiet. A stream that keeps completing within seconds of opening is\n * not healthy and degrades to polling.\n *\n * Graceful fallback (this is the point of the hook, not an edge case): if the subscription errors, or keeps\n * closing as soon as it opens, `mode` becomes `'polling'` and a 30-second interval takes over, paused while the\n * tab is hidden (`document.visibilityState`). There is no reconnect-to-live attempt within one mount — once\n * degraded, a page stays on polling until it remounts. `standalone` dev/Storybook builds (no SSE link\n * configured — see `PlanMagnetProvider`) always take this path; that is expected, not a defect, and is why a\n * subscription error here is never surfaced as a page error.\n */\n\nexport type LiveRefreshKind = 'workItem' | 'project' | 'sprint' | 'roadmap';\nexport type LiveRefreshMode = 'connecting' | 'live' | 'polling';\n\nexport interface LiveEvent {\n kind: string;\n id: string;\n workspaceId: string;\n projectId?: string | null;\n /** `roadmap`-kind events only (`PlanmagnetRoadmapEvent`) — the roadmap this change belongs to. Equal to `id`\n * when the change is to the roadmap itself (as opposed to one of its items). */\n roadmapId?: string | null;\n changedFields: string[];\n actorId: string;\n at: string;\n}\n\nconst ON_PROJECT_UPDATED = gql`\n subscription PlanmagnetLiveProjectUpdated($workspaceId: ID!, $projectId: ID) {\n planmagnetOnProjectUpdated(workspaceId: $workspaceId, projectId: $projectId) {\n kind\n id\n workspaceId\n changedFields\n actorId\n at\n }\n }\n`;\n\nconst ON_SPRINT_UPDATED = gql`\n subscription PlanmagnetLiveSprintUpdated($workspaceId: ID!, $sprintId: ID, $projectId: ID) {\n planmagnetOnSprintUpdated(\n workspaceId: $workspaceId\n sprintId: $sprintId\n projectId: $projectId\n ) {\n kind\n id\n projectId\n workspaceId\n changedFields\n actorId\n at\n }\n }\n`;\n\n// PRM-11: roadmap collaboration — every committed create/update/delete of the roadmap itself, and every\n// create/update/move/delete of one of its items, publishes here. `id` names whatever changed (the roadmap's own\n// id, or one of its items') — compare against `roadmapId` to tell a roadmap-level change from an item-level one,\n// same discipline `DocsDocDetailPagePage`'s `ON_DOC_UPDATED` uses for a single-entity stream.\nconst ON_ROADMAP_UPDATED = gql`\n subscription PlanmagnetLiveRoadmapUpdated($workspaceId: ID!, $roadmapId: ID!) {\n planmagnetOnRoadmapUpdated(workspaceId: $workspaceId, roadmapId: $roadmapId) {\n kind\n id\n roadmapId\n workspaceId\n changedFields\n actorId\n at\n }\n }\n`;\n\nconst ON_WORK_ITEM_UPDATED = gql`\n subscription PlanmagnetLiveWorkItemUpdated($workspaceId: ID!, $projectId: ID, $workItemId: ID) {\n planmagnetOnWorkItemUpdated(\n workspaceId: $workspaceId\n projectId: $projectId\n workItemId: $workItemId\n ) {\n kind\n id\n projectId\n workspaceId\n changedFields\n actorId\n at\n }\n }\n`;\n\nexport interface UseLiveRefreshOptions {\n /** Which event stream to subscribe to — see `PlanmagnetOn{Project,Sprint,WorkItem}Updated` in the schema. */\n kind: LiveRefreshKind;\n /** Narrows the stream server-side. Omitted on `workItem`/`project` this is a workspace-wide stream (the\n * Dashboard and role dashboards want every project's events); `sprint` needs `sprintId` to mean anything on a\n * single sprint's page, so the hook skips subscribing until it has one. */\n projectId?: string | null;\n /** For `kind: 'sprint'`, at least one of `sprintId`/`projectId` is required — one sprint's own events, or\n * every sprint in one project's. Neither means \"skip\" (every sprint workspace-wide has no current caller). */\n sprintId?: string | null;\n /** Narrows a `workItem`-kind stream to one item. Unused by every current caller (the board/dashboards want\n * every item's events for their scope) but supported for a future single-item page. */\n workItemId?: string | null;\n /** Required for `kind: 'roadmap'` — `planmagnetOnRoadmapUpdated` is always scoped to exactly one roadmap\n * (PRM-11), there is no workspace-wide roadmap stream. */\n roadmapId?: string | null;\n /** Apollo operation names this page's queries were declared with — refetched by name\n * (`client.refetchQueries({ include })`) on a (debounced, optionally throttled) event. Never the literal\n * query document, and never the event's own payload used as data. */\n queries: readonly string[];\n /** Coalesces a burst of events (e.g. a bulk update moving 20 cards) into one refetch. Default 500. */\n debounceMs?: number;\n /** A floor between refetches once debounced, for a page whose queries are expensive to re-run on every\n * change (ANR-23: the Dashboard and role dashboards, at most once per 15s). Unset: only the debounce window\n * applies. A pulse arriving inside the floor is never dropped — it runs at the floor's far edge (trailing). */\n minIntervalMs?: number;\n /** How long an open, error-free subscription stays \"Connecting…\" before it counts as live. Default 8000. */\n connectTimeoutMs?: number;\n /** Polling cadence once on the fallback path. Default 30000 (ANR-23/PDM-05: \"every 30s\"). */\n pollIntervalMs?: number;\n /** An extra gate beyond the kind's own required id(s) — e.g. a sprint page's `workItem`-kind stream (no id of\n * its own is required) should still wait for the sprint's `projectId` to resolve, and skip entirely for a\n * team-scoped sprint (no `projectId` at all) rather than falling back to a workspace-wide stream. Default\n * `true` (no extra gate). */\n enabled?: boolean;\n /** Called with the raw event whenever one arrives — ALONGSIDE, never instead of, the debounced `refetch` this\n * hook already schedules. For a page that needs to react to the event itself, not just \"something changed,\n * re-read\" — e.g. the roadmap detail page (PRM-11) telling a DELETED event on the roadmap itself\n * (`event.id === event.roadmapId`) apart from one on an item, to show \"This roadmap was deleted\" instead of\n * racing a refetch against a record that is already gone. This never hands the caller anything to render\n * directly from: `event` is still the same thin notification `useLiveRefresh` never applies as data itself —\n * only `kind`/`id`/`roadmapId` are meaningful, the record always still comes from a query. */\n onEvent?: (event: LiveEvent) => void;\n /** Holds back a pending refetch while `true` — for a page with its own uninterruptible local gesture in\n * progress that a refetch-driven re-render would break (PRM-11: the Now/Next/Later board, for exactly as long\n * as one of its cards is mid-drag, so a live-triggered refetch never replaces the list — and the native HTML5\n * drag source with it — out from under an in-flight drag). An event arriving while paused is never dropped:\n * it still runs through the normal debounce/floor; only the moment the resulting refetch is allowed to\n * actually fire is held, and it fires once, immediately (not re-debounced), the moment this goes back to\n * `false`. Default `false`. */\n paused?: boolean;\n}\n\nexport interface UseLiveRefreshResult {\n /** `null` while a required id (e.g. a sprint page before `sprintId` resolves) hasn't loaded — there is\n * nothing to subscribe to yet, so `LiveStatus` renders nothing rather than a misleading \"Connecting…\". */\n mode: LiveRefreshMode | null;\n}\n\n/** Rolls up more than one `useLiveRefresh` result into the single indicator a page shows (e.g. the sprint\n * detail page subscribes to both sprint events and the sprint's project's work item events). Reports the least\n * favorable non-null mode present — `'polling'` beats `'connecting'` beats `'live'` — so the page never claims\n * \"Live\" while one of its own streams is actually degraded. `null` (nothing subscribed yet) only when every\n * input is `null`. */\nexport function combineLiveRefreshModes(\n ...modes: ReadonlyArray<LiveRefreshMode | null>\n): LiveRefreshMode | null {\n const present = modes.filter((mode): mode is LiveRefreshMode => mode !== null);\n if (present.length === 0) return null;\n if (present.includes('polling')) return 'polling';\n if (present.includes('connecting')) return 'connecting';\n return 'live';\n}\n\nconst RAPID_CLOSE_MS = 5000;\nconst RAPID_CLOSE_LIMIT = 3;\n\nfunction subscriptionDocumentFor(kind: LiveRefreshKind) {\n if (kind === 'project') return ON_PROJECT_UPDATED;\n if (kind === 'sprint') return ON_SPRINT_UPDATED;\n if (kind === 'roadmap') return ON_ROADMAP_UPDATED;\n return ON_WORK_ITEM_UPDATED;\n}\n\n/** A debounced, optionally-throttled trigger: bursts inside `debounceMs` coalesce into one call; once debounced,\n * a call inside `minIntervalMs` of the last one is never dropped — it runs at the floor's far edge instead.\n * `run` is expected to be a `useCallback`-stable function (the hook below's `refetch` is, since it only depends\n * on `client` and `queries`) — accepted as a normal dependency rather than stashed in a ref, so it is never\n * read or written outside an effect/callback. */\nfunction useCoalescedTrigger(\n run: () => void,\n debounceMs: number,\n minIntervalMs: number | undefined\n) {\n const debounceTimer = useRef<ReturnType<typeof setTimeout> | null>(null);\n const floorTimer = useRef<ReturnType<typeof setTimeout> | null>(null);\n const lastRunAt = useRef(0);\n const floorPending = useRef(false);\n\n const runNow = useCallback(() => {\n floorPending.current = false;\n lastRunAt.current = Date.now();\n run();\n }, [run]);\n\n const trigger = useCallback(() => {\n if (debounceTimer.current) clearTimeout(debounceTimer.current);\n debounceTimer.current = setTimeout(() => {\n debounceTimer.current = null;\n if (!minIntervalMs) {\n runNow();\n return;\n }\n const elapsed = Date.now() - lastRunAt.current;\n if (elapsed >= minIntervalMs) {\n runNow();\n return;\n }\n if (floorPending.current) return; // a trailing run is already scheduled for this window\n floorPending.current = true;\n floorTimer.current = setTimeout(runNow, minIntervalMs - elapsed);\n }, debounceMs);\n }, [debounceMs, minIntervalMs, runNow]);\n\n useEffect(\n () => () => {\n if (debounceTimer.current) clearTimeout(debounceTimer.current);\n if (floorTimer.current) clearTimeout(floorTimer.current);\n },\n []\n );\n\n return trigger;\n}\n\nexport function useLiveRefresh({\n kind,\n projectId,\n sprintId,\n workItemId,\n roadmapId,\n queries,\n debounceMs = 500,\n minIntervalMs,\n connectTimeoutMs = 8000,\n pollIntervalMs = 30000,\n enabled = true,\n onEvent,\n paused = false,\n}: UseLiveRefreshOptions): UseLiveRefreshResult {\n const { workspaceId } = usePlanMagnet();\n const client = useApolloClient();\n\n // 'sprint' means something scoped to either one sprint (a sprint's own page) or every sprint in one project (a\n // project-scoped dashboard) — it needs at least one of the two ids, never neither (that would be every sprint\n // in the whole workspace, which no current caller wants). 'project' and 'roadmap' always need their one id —\n // there is no workspace-wide stream for either.\n const requiredIdMissing =\n (kind === 'sprint' && !sprintId && !projectId) ||\n (kind === 'project' && !projectId) ||\n (kind === 'roadmap' && !roadmapId);\n const skip = !workspaceId || requiredIdMissing || !enabled;\n\n // Identifies what we'd subscribe to right now — `null` when there's nothing to. `queries` is deliberately not\n // part of this: which QUERIES a page refetches changing (it never does, in practice — every caller passes a\n // module-level const array) shouldn't reset a live connection, only what STREAM we're on should.\n const subscriptionKey = skip\n ? null\n : `${kind}:${workspaceId}:${projectId ?? ''}:${sprintId ?? ''}:${workItemId ?? ''}:${roadmapId ?? ''}`;\n\n // fe-libs replays an identical query from its 15 s fetch-layer cache (shared/graphql/fetch.ts), and Apollo's\n // refetchQueries goes through it — so an event arriving within 15 s of the page's last read would re-read the\n // stale copy and the change would not show. Measured in prod: a card created ~12 s after the board loaded never\n // appeared. The cache is dropped first, exactly as a successful mutation does.\n const refetch = useCallback(() => {\n clearGraphqlFetchResponseCache();\n void client.refetchQueries({ include: [...queries] });\n }, [client, queries]);\n\n // PRM-11: `paused` (the Now/Next/Later board, mid-drag) holds back the refetch a debounced trigger would\n // otherwise run — read from a ref since the debounce timer's own callback fires well after the render that set\n // `paused`. Nothing is lost: a trigger that lands while paused just marks `pendingWhilePausedRef` instead of\n // calling `refetch`, and the effect below runs the deferred refetch once, immediately, the moment `paused`\n // clears — never a second debounce wait on top of whatever already elapsed before the pause was lifted.\n const pausedRef = useRef(paused);\n useEffect(() => {\n pausedRef.current = paused;\n }, [paused]);\n const pendingWhilePausedRef = useRef(false);\n\n const guardedRefetch = useCallback(() => {\n if (pausedRef.current) {\n pendingWhilePausedRef.current = true;\n return;\n }\n refetch();\n }, [refetch]);\n\n useEffect(() => {\n if (!paused && pendingWhilePausedRef.current) {\n pendingWhilePausedRef.current = false;\n refetch();\n }\n }, [paused, refetch]);\n\n const trigger = useCoalescedTrigger(guardedRefetch, debounceMs, minIntervalMs);\n\n const [mode, setMode] = useState<LiveRefreshMode | null>(subscriptionKey ? 'connecting' : null);\n const [modeKey, setModeKey] = useState<string | null>(subscriptionKey);\n\n // Reset to \"connecting\" (or \"nothing to do\") the moment what we'd subscribe to changes — switching project,\n // sprint or page. This is the React-documented \"adjust state when a value changes\" pattern: a direct setState\n // call guarded by comparing against the last-seen key, during render, rather than in an effect — it lands in\n // the very render that changed, with no stale \"still connecting to the OLD target\" frame first.\n if (subscriptionKey !== modeKey) {\n setModeKey(subscriptionKey);\n setMode(subscriptionKey ? 'connecting' : null);\n }\n\n // The connect window needs a real timer; its only setState call lives inside the timeout's own callback (an\n // update \"when external state changes\"), never synchronously in the effect body. A refused subscription has\n // already errored into 'polling' by now (errors arrive at once), so a still-\"connecting\" one is open and\n // accepted — see the lifecycle note above.\n useEffect(() => {\n if (!subscriptionKey) return;\n const timer = setTimeout(() => {\n setMode((current) => (current === 'connecting' ? 'live' : current));\n }, connectTimeoutMs);\n return () => clearTimeout(timer);\n }, [subscriptionKey, connectTimeoutMs]);\n\n // Rapid-completion guard for the resubscribe below: when each stream closes within RAPID_CLOSE_MS of opening,\n // RAPID_CLOSE_LIMIT times in a row, the transport is not holding the stream open and polling takes over.\n const openedAt = useRef(0);\n const rapidCloses = useRef(0);\n const resubscribeTimer = useRef<ReturnType<typeof setTimeout> | null>(null);\n useEffect(() => {\n openedAt.current = Date.now();\n rapidCloses.current = 0;\n return () => {\n if (resubscribeTimer.current) clearTimeout(resubscribeTimer.current);\n resubscribeTimer.current = null;\n };\n }, [subscriptionKey]);\n\n // The server's idle close (60 s without events), or the ALB ending a long-lived response: routine, so open a\n // fresh stream. Deferred a tick so Apollo finishes tearing down the completed one first. `restart` comes back\n // from `useSubscription` below, so it is reached through a ref kept current by an effect.\n const restartRef = useRef<(() => void) | null>(null);\n const handleComplete = useCallback(() => {\n const lived = Date.now() - openedAt.current;\n rapidCloses.current = lived < RAPID_CLOSE_MS ? rapidCloses.current + 1 : 0;\n if (rapidCloses.current >= RAPID_CLOSE_LIMIT) {\n setMode('polling');\n return;\n }\n resubscribeTimer.current = setTimeout(() => {\n resubscribeTimer.current = null;\n openedAt.current = Date.now();\n restartRef.current?.();\n }, 0);\n }, []);\n\n const { restart } = useSubscription<{\n planmagnetOnProjectUpdated?: LiveEvent;\n planmagnetOnSprintUpdated?: LiveEvent;\n planmagnetOnWorkItemUpdated?: LiveEvent;\n planmagnetOnRoadmapUpdated?: LiveEvent;\n }>(subscriptionDocumentFor(kind), {\n variables: {\n workspaceId,\n projectId: projectId ?? undefined,\n sprintId: sprintId ?? undefined,\n workItemId: workItemId ?? undefined,\n roadmapId: roadmapId ?? undefined,\n },\n skip,\n onData: ({ data: result }) => {\n // Proof the subscription is live: the connect-timeout above only flips a still-\"connecting\" mode, so this\n // is a no-op once already 'live', and never resurrects a mode that errored into 'polling'.\n setMode((current) => (current === 'connecting' ? 'live' : current));\n const event =\n result.data?.planmagnetOnProjectUpdated ??\n result.data?.planmagnetOnSprintUpdated ??\n result.data?.planmagnetOnWorkItemUpdated ??\n result.data?.planmagnetOnRoadmapUpdated;\n if (event) onEvent?.(event);\n trigger();\n },\n onError: (error) => {\n // Covers both a real backend/network failure and the expected \"no SSE transport configured\" case in a\n // standalone/Storybook build (see PlanMagnetProvider) — either way the correct behavior is the same:\n // degrade to polling rather than break the page. Logged at debug level only; this is not a user-facing\n // error state.\n console.debug('[useLiveRefresh] subscription unavailable, falling back to polling:', {\n kind,\n message: error instanceof Error ? error.message : String(error),\n });\n setMode('polling');\n },\n onComplete: handleComplete,\n });\n\n useEffect(() => {\n restartRef.current = restart;\n }, [restart]);\n\n // Polling fallback: the same queries, every `pollIntervalMs`, paused while the tab is hidden. No separate\n // throttle window here — the interval itself is the pacing (30s default, already above any ANR-23 floor).\n // Goes through `guardedRefetch`, not `refetch` directly: the `paused` gate above protects a local gesture from\n // a refetch-driven re-render regardless of which path (live event or poll tick) would have caused it.\n useEffect(() => {\n if (skip || mode !== 'polling') return;\n const id = setInterval(() => {\n if (typeof document !== 'undefined' && document.visibilityState === 'hidden') return;\n guardedRefetch();\n }, pollIntervalMs);\n return () => clearInterval(id);\n }, [skip, mode, pollIntervalMs, guardedRefetch]);\n\n return { mode: skip ? null : mode };\n}\n"],"mappings":"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"}
|
|
@@ -28,7 +28,7 @@ export declare const SYNC_DIRECTIONS: readonly ["INBOUND", "OUTBOUND", "BIDIRECT
|
|
|
28
28
|
export type SyncDirection = (typeof SYNC_DIRECTIONS)[number];
|
|
29
29
|
export declare const SYNC_STATUSES: readonly ["IDLE", "SYNCING", "SUCCESS", "FAILED", "PAUSED", "DISABLED"];
|
|
30
30
|
export type SyncStatus = (typeof SYNC_STATUSES)[number];
|
|
31
|
-
export declare const CONFLICT_RESOLUTIONS: readonly ["SOURCE_WINS", "TARGET_WINS", "MANUAL"];
|
|
31
|
+
export declare const CONFLICT_RESOLUTIONS: readonly ["SOURCE_WINS", "TARGET_WINS", "MERGE", "MANUAL"];
|
|
32
32
|
export type ConflictResolution = (typeof CONFLICT_RESOLUTIONS)[number];
|
|
33
33
|
export declare const CONFLICT_STATUSES: readonly ["OPEN", "RESOLVED"];
|
|
34
34
|
export type ConflictStatus = (typeof CONFLICT_STATUSES)[number];
|
|
@@ -133,6 +133,7 @@ export interface SyncConnectionTestResult {
|
|
|
133
133
|
}
|
|
134
134
|
export declare function providerLabel(provider: string): string;
|
|
135
135
|
export declare function directionLabel(direction: string): string;
|
|
136
|
+
export declare function conflictStrategyLabel(value: string): string;
|
|
136
137
|
/** Whether a capability supports a given direction at all — the single gate the wizard and the edit form both use. */
|
|
137
138
|
export declare function directionSupported(capability: Pick<SyncProviderCapability, 'supportsImport' | 'supportsExport' | 'supportsBidirectional'>, direction: SyncDirection): boolean;
|
|
138
139
|
/** True once a provider can run at least one live direction — the gate for selecting it in the wizard at all. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"syncTypes.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/integrations/syncTypes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,eAAO,MAAM,cAAc,qHAWjB,CAAC;AACX,MAAM,MAAM,YAAY,GAAG,CAAC,OAAO,cAAc,CAAC,CAAC,MAAM,CAAC,CAAC;AAE3D,eAAO,MAAM,eAAe,mDAAoD,CAAC;AACjF,MAAM,MAAM,aAAa,GAAG,CAAC,OAAO,eAAe,CAAC,CAAC,MAAM,CAAC,CAAC;AAE7D,eAAO,MAAM,aAAa,yEAOhB,CAAC;AACX,MAAM,MAAM,UAAU,GAAG,CAAC,OAAO,aAAa,CAAC,CAAC,MAAM,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"syncTypes.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/integrations/syncTypes.ts"],"names":[],"mappings":"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"}
|