@burdenoff/microfe-planmagnet 2026.930.23 → 2026.930.25

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.
Files changed (111) hide show
  1. package/dist/index.js +6 -5
  2. package/dist/planmagnet/PlanMagnetRoot.d.ts.map +1 -1
  3. package/dist/planmagnet/PlanMagnetRoot.js +154 -147
  4. package/dist/planmagnet/PlanMagnetRoot.js.map +1 -1
  5. package/dist/planmagnet/PlanMagnetRoutes.d.ts.map +1 -1
  6. package/dist/planmagnet/PlanMagnetRoutes.js +66 -62
  7. package/dist/planmagnet/PlanMagnetRoutes.js.map +1 -1
  8. package/dist/planmagnet/comments/CommentThread.d.ts +8 -1
  9. package/dist/planmagnet/comments/CommentThread.d.ts.map +1 -1
  10. package/dist/planmagnet/comments/CommentThread.js +188 -181
  11. package/dist/planmagnet/comments/CommentThread.js.map +1 -1
  12. package/dist/planmagnet/comments/TargetComments.d.ts +10 -2
  13. package/dist/planmagnet/comments/TargetComments.d.ts.map +1 -1
  14. package/dist/planmagnet/comments/TargetComments.js +10 -8
  15. package/dist/planmagnet/comments/TargetComments.js.map +1 -1
  16. package/dist/planmagnet/comments/WorkItemComments.d.ts +7 -1
  17. package/dist/planmagnet/comments/WorkItemComments.d.ts.map +1 -1
  18. package/dist/planmagnet/comments/WorkItemComments.js +5 -4
  19. package/dist/planmagnet/comments/WorkItemComments.js.map +1 -1
  20. package/dist/planmagnet/controls/FeatureControls.d.ts +11 -0
  21. package/dist/planmagnet/controls/FeatureControls.d.ts.map +1 -1
  22. package/dist/planmagnet/controls/FeatureControls.js +305 -135
  23. package/dist/planmagnet/controls/FeatureControls.js.map +1 -1
  24. package/dist/planmagnet/controls/Pickers.d.ts +18 -0
  25. package/dist/planmagnet/controls/Pickers.d.ts.map +1 -1
  26. package/dist/planmagnet/controls/Pickers.js +78 -1
  27. package/dist/planmagnet/controls/Pickers.js.map +1 -1
  28. package/dist/planmagnet/controls/PrioritizationControls.d.ts +72 -0
  29. package/dist/planmagnet/controls/PrioritizationControls.d.ts.map +1 -0
  30. package/dist/planmagnet/controls/PrioritizationControls.js +246 -0
  31. package/dist/planmagnet/controls/PrioritizationControls.js.map +1 -0
  32. package/dist/planmagnet/discussion/WorkItemDiscussionPanel.d.ts +8 -0
  33. package/dist/planmagnet/discussion/WorkItemDiscussionPanel.d.ts.map +1 -0
  34. package/dist/planmagnet/discussion/WorkItemDiscussionPanel.js +217 -0
  35. package/dist/planmagnet/discussion/WorkItemDiscussionPanel.js.map +1 -0
  36. package/dist/planmagnet/docs/DocDiffView.d.ts +26 -0
  37. package/dist/planmagnet/docs/DocDiffView.d.ts.map +1 -0
  38. package/dist/planmagnet/docs/DocDiffView.js +52 -0
  39. package/dist/planmagnet/docs/DocDiffView.js.map +1 -0
  40. package/dist/planmagnet/docs/DocSuggestionsPanel.d.ts +13 -0
  41. package/dist/planmagnet/docs/DocSuggestionsPanel.d.ts.map +1 -0
  42. package/dist/planmagnet/docs/DocSuggestionsPanel.js +308 -0
  43. package/dist/planmagnet/docs/DocSuggestionsPanel.js.map +1 -0
  44. package/dist/planmagnet/docs/DocVersionCompare.d.ts +7 -0
  45. package/dist/planmagnet/docs/DocVersionCompare.d.ts.map +1 -0
  46. package/dist/planmagnet/docs/DocVersionCompare.js +105 -0
  47. package/dist/planmagnet/docs/DocVersionCompare.js.map +1 -0
  48. package/dist/planmagnet/hooks/usePresence.d.ts +39 -0
  49. package/dist/planmagnet/hooks/usePresence.d.ts.map +1 -0
  50. package/dist/planmagnet/hooks/usePresence.js +146 -0
  51. package/dist/planmagnet/hooks/usePresence.js.map +1 -0
  52. package/dist/planmagnet/index.d.ts +1 -0
  53. package/dist/planmagnet/index.d.ts.map +1 -1
  54. package/dist/planmagnet/index.js +1 -0
  55. package/dist/planmagnet/links/CodeLinksPanel.d.ts +22 -0
  56. package/dist/planmagnet/links/CodeLinksPanel.d.ts.map +1 -0
  57. package/dist/planmagnet/links/CodeLinksPanel.js +94 -0
  58. package/dist/planmagnet/links/CodeLinksPanel.js.map +1 -0
  59. package/dist/planmagnet/pages/DocsDocDetailPagePage.d.ts.map +1 -1
  60. package/dist/planmagnet/pages/DocsDocDetailPagePage.js +488 -217
  61. package/dist/planmagnet/pages/DocsDocDetailPagePage.js.map +1 -1
  62. package/dist/planmagnet/pages/ProductsFeatureDetailPagePage.d.ts.map +1 -1
  63. package/dist/planmagnet/pages/ProductsFeatureDetailPagePage.js +131 -125
  64. package/dist/planmagnet/pages/ProductsFeatureDetailPagePage.js.map +1 -1
  65. package/dist/planmagnet/pages/ProductsFeaturesPagePage.d.ts.map +1 -1
  66. package/dist/planmagnet/pages/ProductsFeaturesPagePage.js +183 -99
  67. package/dist/planmagnet/pages/ProductsFeaturesPagePage.js.map +1 -1
  68. package/dist/planmagnet/pages/ProductsNewFeaturePagePage.d.ts.map +1 -1
  69. package/dist/planmagnet/pages/ProductsNewFeaturePagePage.js +113 -97
  70. package/dist/planmagnet/pages/ProductsNewFeaturePagePage.js.map +1 -1
  71. package/dist/planmagnet/pages/ProductsPrioritizationPagePage.d.ts +2 -0
  72. package/dist/planmagnet/pages/ProductsPrioritizationPagePage.d.ts.map +1 -0
  73. package/dist/planmagnet/pages/ProductsPrioritizationPagePage.js +363 -0
  74. package/dist/planmagnet/pages/ProductsPrioritizationPagePage.js.map +1 -0
  75. package/dist/planmagnet/pages/ProductsProductDetailPagePage.d.ts.map +1 -1
  76. package/dist/planmagnet/pages/ProductsProductDetailPagePage.js +96 -79
  77. package/dist/planmagnet/pages/ProductsProductDetailPagePage.js.map +1 -1
  78. package/dist/planmagnet/pages/ProductsRoadmapDetailPagePage.d.ts.map +1 -1
  79. package/dist/planmagnet/pages/ProductsRoadmapDetailPagePage.js +144 -128
  80. package/dist/planmagnet/pages/ProductsRoadmapDetailPagePage.js.map +1 -1
  81. package/dist/planmagnet/pages/ProjectsProjectDetailPagePage.d.ts.map +1 -1
  82. package/dist/planmagnet/pages/ProjectsProjectDetailPagePage.js +205 -196
  83. package/dist/planmagnet/pages/ProjectsProjectDetailPagePage.js.map +1 -1
  84. package/dist/planmagnet/pages/TasksTaskDetailPagePage.d.ts.map +1 -1
  85. package/dist/planmagnet/pages/TasksTaskDetailPagePage.js +194 -160
  86. package/dist/planmagnet/pages/TasksTaskDetailPagePage.js.map +1 -1
  87. package/dist/planmagnet/presence/PresenceAvatars.d.ts +16 -0
  88. package/dist/planmagnet/presence/PresenceAvatars.d.ts.map +1 -0
  89. package/dist/planmagnet/presence/PresenceAvatars.js +56 -0
  90. package/dist/planmagnet/presence/PresenceAvatars.js.map +1 -0
  91. package/dist/planmagnet/product/ProductControls.d.ts +33 -0
  92. package/dist/planmagnet/product/ProductControls.d.ts.map +1 -1
  93. package/dist/planmagnet/product/ProductControls.js +593 -268
  94. package/dist/planmagnet/product/ProductControls.js.map +1 -1
  95. package/dist/planmagnet/product/routes.d.ts +10 -0
  96. package/dist/planmagnet/product/routes.d.ts.map +1 -0
  97. package/dist/planmagnet/product/routes.js +6 -0
  98. package/dist/planmagnet/product/routes.js.map +1 -0
  99. package/dist/planmagnet/roadmaps/RoadmapControls.d.ts +10 -0
  100. package/dist/planmagnet/roadmaps/RoadmapControls.d.ts.map +1 -1
  101. package/dist/planmagnet/roadmaps/RoadmapControls.js +235 -184
  102. package/dist/planmagnet/roadmaps/RoadmapControls.js.map +1 -1
  103. package/dist/planmagnet/utils/graphqlErrors.d.ts +6 -0
  104. package/dist/planmagnet/utils/graphqlErrors.d.ts.map +1 -1
  105. package/dist/planmagnet/utils/graphqlErrors.js +5 -2
  106. package/dist/planmagnet/utils/graphqlErrors.js.map +1 -1
  107. package/dist/planmagnet/utils/lineDiff.d.ts +21 -0
  108. package/dist/planmagnet/utils/lineDiff.d.ts.map +1 -0
  109. package/dist/planmagnet/utils/lineDiff.js +36 -0
  110. package/dist/planmagnet/utils/lineDiff.js.map +1 -0
  111. package/package.json +1 -1
@@ -0,0 +1,246 @@
1
+ import { ErrorLine as e } from "./ConfirmButton.js";
2
+ import { parseIntField as t } from "../utils/formValidation.js";
3
+ import { jsx as n, jsxs as r } from "react/jsx-runtime";
4
+ import { Badge as i, Input as a } from "@burdenoff/fe-libs/ui";
5
+ //#region src/planmagnet/controls/PrioritizationControls.tsx
6
+ var o = [
7
+ "MUST",
8
+ "SHOULD",
9
+ "COULD",
10
+ "WONT"
11
+ ], s = [
12
+ "BASIC",
13
+ "PERFORMANCE",
14
+ "DELIGHTER",
15
+ "INDIFFERENT",
16
+ "REVERSE"
17
+ ], c = [
18
+ "QUICK_WIN",
19
+ "MAJOR_PROJECT",
20
+ "FILL_IN",
21
+ "TIME_SINK"
22
+ ], l = {
23
+ MUST: {
24
+ label: "Must",
25
+ hint: "Non-negotiable — the release does not ship without it.",
26
+ variant: "default"
27
+ },
28
+ SHOULD: {
29
+ label: "Should",
30
+ hint: "Important, but the release can still ship without it.",
31
+ variant: "info"
32
+ },
33
+ COULD: {
34
+ label: "Could",
35
+ hint: "Nice to have if there is time.",
36
+ variant: "secondary"
37
+ },
38
+ WONT: {
39
+ label: "Won't",
40
+ hint: "Explicitly out of scope this time around.",
41
+ variant: "outline"
42
+ }
43
+ }, u = {
44
+ BASIC: {
45
+ label: "Basic",
46
+ hint: "Expected — its absence causes dissatisfaction, its presence goes unnoticed.",
47
+ variant: "secondary"
48
+ },
49
+ PERFORMANCE: {
50
+ label: "Performance",
51
+ hint: "Satisfaction scales with how well it is delivered.",
52
+ variant: "info"
53
+ },
54
+ DELIGHTER: {
55
+ label: "Delighter",
56
+ hint: "Unexpected — its absence is not missed, its presence delights.",
57
+ variant: "success"
58
+ },
59
+ INDIFFERENT: {
60
+ label: "Indifferent",
61
+ hint: "Users do not care either way.",
62
+ variant: "outline"
63
+ },
64
+ REVERSE: {
65
+ label: "Reverse",
66
+ hint: "Some users prefer this NOT be there.",
67
+ variant: "destructive"
68
+ }
69
+ }, d = {
70
+ QUICK_WIN: {
71
+ label: "Quick win",
72
+ hint: "High value, low effort (value ≥ 6, effort ≤ 5) — do these first.",
73
+ variant: "success"
74
+ },
75
+ MAJOR_PROJECT: {
76
+ label: "Major project",
77
+ hint: "High value, high effort (value ≥ 6, effort ≥ 6) — plan and staff deliberately.",
78
+ variant: "info"
79
+ },
80
+ FILL_IN: {
81
+ label: "Fill-in",
82
+ hint: "Low value, low effort (value ≤ 5, effort ≤ 5) — fine as filler work.",
83
+ variant: "secondary"
84
+ },
85
+ TIME_SINK: {
86
+ label: "Time sink",
87
+ hint: "Low value, high effort (value ≤ 5, effort ≥ 6) — avoid or deprioritize.",
88
+ variant: "destructive"
89
+ }
90
+ }, f = (e) => e && e in l ? l[e].label : "Not set", p = (e) => e && e in u ? u[e].label : "Not set";
91
+ function m({ quadrant: e, hideWhenUnscored: t = !1, className: r }) {
92
+ if (!e) return t ? null : /* @__PURE__ */ n(i, {
93
+ variant: "outline",
94
+ className: r,
95
+ "data-testid": "prio-quadrant-badge",
96
+ children: "Unscored"
97
+ });
98
+ let a = e in d ? d[e] : null;
99
+ return /* @__PURE__ */ n(i, {
100
+ variant: a?.variant ?? "outline",
101
+ className: r,
102
+ "data-testid": "prio-quadrant-badge",
103
+ children: a?.label ?? e
104
+ });
105
+ }
106
+ var h = {
107
+ moscow: "",
108
+ kano: "",
109
+ valueScore: "",
110
+ effortScore: ""
111
+ }, g = "mt-1 w-full rounded-md border border-input bg-background px-3 py-2 text-sm disabled:opacity-60";
112
+ function _({ idPrefix: i, values: c, onChange: d, disabled: f = !1 }) {
113
+ let p = t(c.valueScore, {
114
+ min: 1,
115
+ max: 10
116
+ }), m = t(c.effortScore, {
117
+ min: 1,
118
+ max: 10
119
+ });
120
+ return /* @__PURE__ */ r("div", {
121
+ className: "space-y-3",
122
+ "data-testid": "prio-fields",
123
+ children: [/* @__PURE__ */ r("div", {
124
+ className: "grid grid-cols-2 gap-3",
125
+ children: [/* @__PURE__ */ r("div", { children: [
126
+ /* @__PURE__ */ n("label", {
127
+ htmlFor: `${i}-moscow`,
128
+ className: "text-sm font-medium",
129
+ children: "MoSCoW"
130
+ }),
131
+ /* @__PURE__ */ r("select", {
132
+ id: `${i}-moscow`,
133
+ "data-testid": "prio-field-moscow",
134
+ className: g,
135
+ value: c.moscow,
136
+ disabled: f,
137
+ onChange: (e) => d({
138
+ ...c,
139
+ moscow: e.target.value
140
+ }),
141
+ children: [/* @__PURE__ */ n("option", {
142
+ value: "",
143
+ children: "Not set"
144
+ }), o.map((e) => /* @__PURE__ */ n("option", {
145
+ value: e,
146
+ children: l[e].label
147
+ }, e))]
148
+ }),
149
+ /* @__PURE__ */ n("p", {
150
+ className: "mt-1 text-xs text-muted-foreground",
151
+ children: c.moscow && c.moscow in l ? l[c.moscow].hint : "Must/Should/Could/Won't for this release."
152
+ })
153
+ ] }), /* @__PURE__ */ r("div", { children: [
154
+ /* @__PURE__ */ n("label", {
155
+ htmlFor: `${i}-kano`,
156
+ className: "text-sm font-medium",
157
+ children: "Kano"
158
+ }),
159
+ /* @__PURE__ */ r("select", {
160
+ id: `${i}-kano`,
161
+ "data-testid": "prio-field-kano",
162
+ className: g,
163
+ value: c.kano,
164
+ disabled: f,
165
+ onChange: (e) => d({
166
+ ...c,
167
+ kano: e.target.value
168
+ }),
169
+ children: [/* @__PURE__ */ n("option", {
170
+ value: "",
171
+ children: "Not set"
172
+ }), s.map((e) => /* @__PURE__ */ n("option", {
173
+ value: e,
174
+ children: u[e].label
175
+ }, e))]
176
+ }),
177
+ /* @__PURE__ */ n("p", {
178
+ className: "mt-1 text-xs text-muted-foreground",
179
+ children: c.kano && c.kano in u ? u[c.kano].hint : "How this shapes customer satisfaction."
180
+ })
181
+ ] })]
182
+ }), /* @__PURE__ */ r("div", {
183
+ className: "grid grid-cols-2 gap-3",
184
+ children: [/* @__PURE__ */ r("div", { children: [
185
+ /* @__PURE__ */ n("label", {
186
+ htmlFor: `${i}-value-score`,
187
+ className: "text-sm font-medium",
188
+ children: "Value score (1-10)"
189
+ }),
190
+ /* @__PURE__ */ n(a, {
191
+ id: `${i}-value-score`,
192
+ "data-testid": "prio-field-value-score",
193
+ type: "number",
194
+ min: 1,
195
+ max: 10,
196
+ className: "mt-1",
197
+ placeholder: "Not set",
198
+ value: c.valueScore,
199
+ "aria-invalid": p.invalid,
200
+ disabled: f,
201
+ onChange: (e) => d({
202
+ ...c,
203
+ valueScore: e.target.value
204
+ })
205
+ }),
206
+ p.invalid && /* @__PURE__ */ n(e, { message: "Value score is a whole number from 1 to 10." })
207
+ ] }), /* @__PURE__ */ r("div", { children: [
208
+ /* @__PURE__ */ n("label", {
209
+ htmlFor: `${i}-effort-score`,
210
+ className: "text-sm font-medium",
211
+ children: "Effort score (1-10)"
212
+ }),
213
+ /* @__PURE__ */ n(a, {
214
+ id: `${i}-effort-score`,
215
+ "data-testid": "prio-field-effort-score",
216
+ type: "number",
217
+ min: 1,
218
+ max: 10,
219
+ className: "mt-1",
220
+ placeholder: "Not set",
221
+ value: c.effortScore,
222
+ "aria-invalid": m.invalid,
223
+ disabled: f,
224
+ onChange: (e) => d({
225
+ ...c,
226
+ effortScore: e.target.value
227
+ })
228
+ }),
229
+ m.invalid && /* @__PURE__ */ n(e, { message: "Effort score is a whole number from 1 to 10." })
230
+ ] })]
231
+ })]
232
+ });
233
+ }
234
+ function v(e) {
235
+ return !t(e.valueScore, {
236
+ min: 1,
237
+ max: 10
238
+ }).invalid && !t(e.effortScore, {
239
+ min: 1,
240
+ max: 10
241
+ }).invalid;
242
+ }
243
+ //#endregion
244
+ export { h as EMPTY_PRIORITIZATION_FORM, u as KANO_META, s as KANO_VALUES, l as MOSCOW_META, o as MOSCOW_VALUES, _ as PrioritizationScoreFields, d as QUADRANT_META, c as QUADRANT_VALUES, m as QuadrantBadge, p as kanoLabel, f as moscowLabel, v as prioritizationScoresValid };
245
+
246
+ //# sourceMappingURL=PrioritizationControls.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PrioritizationControls.js","names":[],"sources":["../../../src/planmagnet/controls/PrioritizationControls.tsx"],"sourcesContent":["import { Badge, Input } from '@burdenoff/fe-libs/ui';\nimport { parseIntField } from '../utils/formValidation';\nimport { ErrorLine } from './ConfirmButton';\n\n/**\n * Prioritization (PRM-27): MoSCoW, Kano and Value-vs-Effort scoring shared by the feature request create/edit\n * form, the feature detail page, and the roadmap initiative (RoadmapItem) edit dialog — one presentational form\n * fragment plus the badge that shows the server-computed Value-vs-Effort quadrant, so the three surfaces (and the\n * prioritization page's own item cards) render it identically instead of three near-copies drifting apart.\n *\n * The quadrant itself is never computed client-side — `FeatureRequest.valueEffortQuadrant` /\n * `RoadmapItem.valueEffortQuadrant` are server-derived (and not stored), so `QuadrantBadge` only ever renders a\n * value that came back from a query or a mutation response.\n */\n\nexport const MOSCOW_VALUES = ['MUST', 'SHOULD', 'COULD', 'WONT'] as const;\nexport type MoscowValue = (typeof MOSCOW_VALUES)[number];\n\nexport const KANO_VALUES = ['BASIC', 'PERFORMANCE', 'DELIGHTER', 'INDIFFERENT', 'REVERSE'] as const;\nexport type KanoValue = (typeof KANO_VALUES)[number];\n\nexport const QUADRANT_VALUES = ['QUICK_WIN', 'MAJOR_PROJECT', 'FILL_IN', 'TIME_SINK'] as const;\nexport type QuadrantValue = (typeof QUADRANT_VALUES)[number];\n\ntype BadgeVariant =\n | 'default'\n | 'success'\n | 'warning'\n | 'info'\n | 'secondary'\n | 'destructive'\n | 'outline';\n\nexport const MOSCOW_META: Record<\n MoscowValue,\n { label: string; hint: string; variant: BadgeVariant }\n> = {\n MUST: {\n label: 'Must',\n hint: 'Non-negotiable — the release does not ship without it.',\n variant: 'default',\n },\n SHOULD: {\n label: 'Should',\n hint: 'Important, but the release can still ship without it.',\n variant: 'info',\n },\n COULD: { label: 'Could', hint: 'Nice to have if there is time.', variant: 'secondary' },\n WONT: { label: \"Won't\", hint: 'Explicitly out of scope this time around.', variant: 'outline' },\n};\n\nexport const KANO_META: Record<KanoValue, { label: string; hint: string; variant: BadgeVariant }> =\n {\n BASIC: {\n label: 'Basic',\n hint: 'Expected — its absence causes dissatisfaction, its presence goes unnoticed.',\n variant: 'secondary',\n },\n PERFORMANCE: {\n label: 'Performance',\n hint: 'Satisfaction scales with how well it is delivered.',\n variant: 'info',\n },\n DELIGHTER: {\n label: 'Delighter',\n hint: 'Unexpected — its absence is not missed, its presence delights.',\n variant: 'success',\n },\n INDIFFERENT: {\n label: 'Indifferent',\n hint: 'Users do not care either way.',\n variant: 'outline',\n },\n REVERSE: {\n label: 'Reverse',\n hint: 'Some users prefer this NOT be there.',\n variant: 'destructive',\n },\n };\n\nexport const QUADRANT_META: Record<\n QuadrantValue,\n { label: string; hint: string; variant: BadgeVariant }\n> = {\n QUICK_WIN: {\n label: 'Quick win',\n hint: 'High value, low effort (value ≥ 6, effort ≤ 5) — do these first.',\n variant: 'success',\n },\n MAJOR_PROJECT: {\n label: 'Major project',\n hint: 'High value, high effort (value ≥ 6, effort ≥ 6) — plan and staff deliberately.',\n variant: 'info',\n },\n FILL_IN: {\n label: 'Fill-in',\n hint: 'Low value, low effort (value ≤ 5, effort ≤ 5) — fine as filler work.',\n variant: 'secondary',\n },\n TIME_SINK: {\n label: 'Time sink',\n hint: 'Low value, high effort (value ≤ 5, effort ≥ 6) — avoid or deprioritize.',\n variant: 'destructive',\n },\n};\n\nexport const moscowLabel = (value: string | null | undefined): string =>\n value && value in MOSCOW_META ? MOSCOW_META[value as MoscowValue].label : 'Not set';\nexport const kanoLabel = (value: string | null | undefined): string =>\n value && value in KANO_META ? KANO_META[value as KanoValue].label : 'Not set';\nexport const quadrantLabel = (value: string | null | undefined): string =>\n value && value in QUADRANT_META ? QUADRANT_META[value as QuadrantValue].label : 'Unscored';\n\n/**\n * The Value-vs-Effort quadrant, read back from the server (`FeatureRequest.valueEffortQuadrant` /\n * `RoadmapItem.valueEffortQuadrant` / a `PlanmagnetPrioritizationBucket.bucket`) — never computed here. Renders\n * an \"Unscored\" badge when null (one or both of valueScore/effortScore is not set yet) unless `hideWhenUnscored`.\n */\nexport function QuadrantBadge({\n quadrant,\n hideWhenUnscored = false,\n className,\n}: {\n quadrant: string | null | undefined;\n hideWhenUnscored?: boolean;\n className?: string;\n}) {\n if (!quadrant) {\n if (hideWhenUnscored) return null;\n return (\n <Badge variant=\"outline\" className={className} data-testid=\"prio-quadrant-badge\">\n Unscored\n </Badge>\n );\n }\n const meta = quadrant in QUADRANT_META ? QUADRANT_META[quadrant as QuadrantValue] : null;\n return (\n <Badge\n variant={meta?.variant ?? 'outline'}\n className={className}\n data-testid=\"prio-quadrant-badge\"\n >\n {meta?.label ?? quadrant}\n </Badge>\n );\n}\n\nexport interface PrioritizationFormValues {\n /** '' = not set */\n moscow: string;\n /** '' = not set */\n kano: string;\n /** '' = not set, else a whole number 1-10 as text */\n valueScore: string;\n /** '' = not set, else a whole number 1-10 as text */\n effortScore: string;\n}\n\nexport const EMPTY_PRIORITIZATION_FORM: PrioritizationFormValues = {\n moscow: '',\n kano: '',\n valueScore: '',\n effortScore: '',\n};\n\nconst selectClass =\n 'mt-1 w-full rounded-md border border-input bg-background px-3 py-2 text-sm disabled:opacity-60';\n\n/**\n * The four prioritization inputs (PRM-27): MoSCoW and Kano selects (each with a short explanation of the\n * selected value), and 1-10 value/effort number inputs. Fully controlled; clearing a select or number input back\n * to empty is how a caller sends `null` (cleared) rather than omitting the field. Shared by the feature request\n * create/edit form, the feature detail page, and the roadmap initiative edit dialog.\n */\nexport function PrioritizationScoreFields({\n idPrefix,\n values,\n onChange,\n disabled = false,\n}: {\n idPrefix: string;\n values: PrioritizationFormValues;\n onChange: (next: PrioritizationFormValues) => void;\n disabled?: boolean;\n}) {\n const value = parseIntField(values.valueScore, { min: 1, max: 10 });\n const effort = parseIntField(values.effortScore, { min: 1, max: 10 });\n\n return (\n <div className=\"space-y-3\" data-testid=\"prio-fields\">\n <div className=\"grid grid-cols-2 gap-3\">\n <div>\n <label htmlFor={`${idPrefix}-moscow`} className=\"text-sm font-medium\">\n MoSCoW\n </label>\n <select\n id={`${idPrefix}-moscow`}\n data-testid=\"prio-field-moscow\"\n className={selectClass}\n value={values.moscow}\n disabled={disabled}\n onChange={(e) => onChange({ ...values, moscow: e.target.value })}\n >\n <option value=\"\">Not set</option>\n {MOSCOW_VALUES.map((v) => (\n <option key={v} value={v}>\n {MOSCOW_META[v].label}\n </option>\n ))}\n </select>\n <p className=\"mt-1 text-xs text-muted-foreground\">\n {values.moscow && values.moscow in MOSCOW_META\n ? MOSCOW_META[values.moscow as MoscowValue].hint\n : \"Must/Should/Could/Won't for this release.\"}\n </p>\n </div>\n <div>\n <label htmlFor={`${idPrefix}-kano`} className=\"text-sm font-medium\">\n Kano\n </label>\n <select\n id={`${idPrefix}-kano`}\n data-testid=\"prio-field-kano\"\n className={selectClass}\n value={values.kano}\n disabled={disabled}\n onChange={(e) => onChange({ ...values, kano: e.target.value })}\n >\n <option value=\"\">Not set</option>\n {KANO_VALUES.map((v) => (\n <option key={v} value={v}>\n {KANO_META[v].label}\n </option>\n ))}\n </select>\n <p className=\"mt-1 text-xs text-muted-foreground\">\n {values.kano && values.kano in KANO_META\n ? KANO_META[values.kano as KanoValue].hint\n : 'How this shapes customer satisfaction.'}\n </p>\n </div>\n </div>\n <div className=\"grid grid-cols-2 gap-3\">\n <div>\n <label htmlFor={`${idPrefix}-value-score`} className=\"text-sm font-medium\">\n Value score (1-10)\n </label>\n <Input\n id={`${idPrefix}-value-score`}\n data-testid=\"prio-field-value-score\"\n type=\"number\"\n min={1}\n max={10}\n className=\"mt-1\"\n placeholder=\"Not set\"\n value={values.valueScore}\n aria-invalid={value.invalid}\n disabled={disabled}\n onChange={(e) => onChange({ ...values, valueScore: e.target.value })}\n />\n {value.invalid && <ErrorLine message=\"Value score is a whole number from 1 to 10.\" />}\n </div>\n <div>\n <label htmlFor={`${idPrefix}-effort-score`} className=\"text-sm font-medium\">\n Effort score (1-10)\n </label>\n <Input\n id={`${idPrefix}-effort-score`}\n data-testid=\"prio-field-effort-score\"\n type=\"number\"\n min={1}\n max={10}\n className=\"mt-1\"\n placeholder=\"Not set\"\n value={values.effortScore}\n aria-invalid={effort.invalid}\n disabled={disabled}\n onChange={(e) => onChange({ ...values, effortScore: e.target.value })}\n />\n {effort.invalid && <ErrorLine message=\"Effort score is a whole number from 1 to 10.\" />}\n </div>\n </div>\n </div>\n );\n}\n\n/** True when neither of the two score fields holds an out-of-range value (empty is always valid — it means \"clear\"). */\nexport function prioritizationScoresValid(values: PrioritizationFormValues): boolean {\n return (\n !parseIntField(values.valueScore, { min: 1, max: 10 }).invalid &&\n !parseIntField(values.effortScore, { min: 1, max: 10 }).invalid\n );\n}\n"],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ export declare function WorkItemDiscussionPanel({ workItemId, workItemKey, workItemTitle, assigneeId, discussionConversationId, }: {
2
+ workItemId: string;
3
+ workItemKey: string;
4
+ workItemTitle: string;
5
+ assigneeId: string | null;
6
+ discussionConversationId: string | null;
7
+ }): import("react/jsx-runtime").JSX.Element;
8
+ //# sourceMappingURL=WorkItemDiscussionPanel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"WorkItemDiscussionPanel.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/discussion/WorkItemDiscussionPanel.tsx"],"names":[],"mappings":"AA2OA,wBAAgB,uBAAuB,CAAC,EACtC,UAAU,EACV,WAAW,EACX,aAAa,EACb,UAAU,EACV,wBAAwB,GACzB,EAAE;IACD,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,wBAAwB,EAAE,MAAM,GAAG,IAAI,CAAC;CACzC,2CA6DA"}
@@ -0,0 +1,217 @@
1
+ import { usePlanMagnet as e } from "../providers/PlanMagnetProvider.js";
2
+ import { throwOnError as t } from "../utils/mutationResult.js";
3
+ import { ErrorLine as n } from "../controls/ConfirmButton.js";
4
+ import { isForbidden as r, mutationErrorMessage as i } from "../utils/graphqlErrors.js";
5
+ import { useDeferredValue as a, useEffect as o, useState as s } from "react";
6
+ import { ExternalLink as c, MessageCircle as l, MessagesSquare as u, Unlink as d } from "lucide-react";
7
+ import { gql as f } from "@apollo/client";
8
+ import { useMutation as p } from "@apollo/client/react";
9
+ import { jsx as m, jsxs as h } from "react/jsx-runtime";
10
+ import { Button as g, Card as _, CardContent as v, CardHeader as y, CardTitle as b, Checkbox as x, Input as S, ResponsiveDialog as C, ResponsiveDialogContent as w, ResponsiveDialogDescription as T, ResponsiveDialogFooter as E, ResponsiveDialogHeader as D, ResponsiveDialogTitle as O, ResponsiveDialogTrigger as k } from "@burdenoff/fe-libs/ui";
11
+ import { useWorkspaceDirectory as A } from "@burdenoff/fe-libs/sharing";
12
+ //#region src/planmagnet/discussion/WorkItemDiscussionPanel.tsx
13
+ var j = f`
14
+ mutation PlanmagnetSetWorkItemDiscussionLink($workItemId: ID!, $conversationId: ID) {
15
+ planmagnetSetWorkItemDiscussion(workItemId: $workItemId, conversationId: $conversationId) {
16
+ id
17
+ discussionConversationId
18
+ }
19
+ }
20
+ `, M = f`
21
+ mutation CreateWorkItemDiscussionConversation($input: CreateConversationInput!) {
22
+ createConversation(input: $input) {
23
+ success
24
+ conversation {
25
+ id
26
+ }
27
+ }
28
+ }
29
+ `, N = {
30
+ refetchQueries: ["GetWorkItemDetail"],
31
+ awaitRefetchQueries: !0
32
+ };
33
+ function P({ workItemId: c, workItemKey: l, workItemTitle: d, assigneeId: f }) {
34
+ let { workspaceId: _, authToken: v, apiGatewayUrl: y, currentUser: b } = e(), [P, F] = s(!1), [I, L] = s(""), R = a(I.trim()), [z, B] = s(/* @__PURE__ */ new Set()), [V, H] = s(null), U = A({
35
+ workspaceId: _ ?? "",
36
+ authToken: v ?? void 0,
37
+ apiGatewayUrl: y ?? void 0
38
+ }), { search: W } = U;
39
+ o(() => {
40
+ W(R);
41
+ }, [R, W]);
42
+ let [G, K] = p(M), [q, J] = p(j, N), Y = K.loading || J.loading, X = (e, t) => B((n) => {
43
+ let r = new Set(n);
44
+ return t ? r.add(e) : r.delete(e), r;
45
+ }), Z = async () => {
46
+ if (_) {
47
+ H(null);
48
+ try {
49
+ let e = await t(G({ variables: { input: {
50
+ workspaceId: _,
51
+ type: "GROUP",
52
+ title: `${l}: ${d}`,
53
+ participantIds: [...z]
54
+ } } })), n = e.data?.createConversation?.conversation?.id;
55
+ if (!e.data?.createConversation?.success || !n) throw Error("The conversation could not be created.");
56
+ await t(q({ variables: {
57
+ workItemId: c,
58
+ conversationId: n
59
+ } })), F(!1), L("");
60
+ } catch (e) {
61
+ H(r(e) ? "Your role cannot start a conversation in this workspace." : i(e, "The discussion could not be started."));
62
+ }
63
+ }
64
+ };
65
+ return /* @__PURE__ */ h(C, {
66
+ open: P,
67
+ onOpenChange: (e) => {
68
+ if (F(e), e) {
69
+ let e = /* @__PURE__ */ new Set();
70
+ f && e.add(f), b?.id && e.add(b.id), B(e);
71
+ } else L(""), H(null);
72
+ },
73
+ children: [/* @__PURE__ */ m(k, {
74
+ asChild: !0,
75
+ children: /* @__PURE__ */ h(g, {
76
+ size: "sm",
77
+ "data-testid": "discussion-start",
78
+ children: [/* @__PURE__ */ m(u, {
79
+ className: "mr-1 h-4 w-4",
80
+ "aria-hidden": "true"
81
+ }), "Discuss in Conversations"]
82
+ })
83
+ }), /* @__PURE__ */ h(w, {
84
+ className: "max-w-lg",
85
+ children: [
86
+ /* @__PURE__ */ h(D, { children: [/* @__PURE__ */ m(O, { children: "Start a discussion" }), /* @__PURE__ */ m(T, { children: "Creates a group conversation for this task and links it back here. Add anyone else who should be in it." })] }),
87
+ /* @__PURE__ */ h("p", {
88
+ className: "text-sm font-medium",
89
+ children: [
90
+ l,
91
+ ": ",
92
+ d
93
+ ]
94
+ }),
95
+ /* @__PURE__ */ m(S, {
96
+ value: I,
97
+ onChange: (e) => L(e.target.value),
98
+ placeholder: "Search workspace members",
99
+ "aria-label": "Search workspace members"
100
+ }),
101
+ /* @__PURE__ */ h("div", {
102
+ className: "max-h-64 space-y-1 overflow-y-auto",
103
+ children: [
104
+ U.loading && U.members.length === 0 && /* @__PURE__ */ m("p", {
105
+ className: "py-4 text-center text-sm text-muted-foreground",
106
+ children: "Loading…"
107
+ }),
108
+ !U.loading && U.members.length === 0 && /* @__PURE__ */ m("p", {
109
+ className: "py-4 text-center text-sm text-muted-foreground",
110
+ children: "No matching members."
111
+ }),
112
+ U.members.map((e) => {
113
+ let t = `discussion-participant-${e.id}`;
114
+ return /* @__PURE__ */ h("label", {
115
+ htmlFor: t,
116
+ className: "flex cursor-pointer items-center gap-3 rounded-md border p-2 text-sm hover:bg-muted",
117
+ children: [/* @__PURE__ */ m(x, {
118
+ id: t,
119
+ checked: z.has(e.id),
120
+ onCheckedChange: (t) => X(e.id, t === !0)
121
+ }), /* @__PURE__ */ h("span", {
122
+ className: "min-w-0 flex-1 truncate",
123
+ children: [
124
+ e.name,
125
+ e.id === b?.id && /* @__PURE__ */ m("span", {
126
+ className: "text-muted-foreground",
127
+ children: " (you)"
128
+ }),
129
+ e.id === f && /* @__PURE__ */ m("span", {
130
+ className: "text-muted-foreground",
131
+ children: " · assignee"
132
+ })
133
+ ]
134
+ })]
135
+ }, e.id);
136
+ }),
137
+ U.hasMore && /* @__PURE__ */ m("button", {
138
+ type: "button",
139
+ className: "w-full py-1 text-xs text-primary hover:underline",
140
+ onClick: U.fetchMore,
141
+ children: "Show more members"
142
+ })
143
+ ]
144
+ }),
145
+ V && /* @__PURE__ */ m(n, { message: V }),
146
+ /* @__PURE__ */ m(E, { children: /* @__PURE__ */ m(g, {
147
+ onClick: () => void Z(),
148
+ disabled: z.size === 0 || Y,
149
+ "data-testid": "discussion-create-confirm",
150
+ children: Y ? "Starting…" : `Start discussion (${z.size})`
151
+ }) })
152
+ ]
153
+ })]
154
+ });
155
+ }
156
+ function F({ workItemId: e, workItemKey: r, workItemTitle: a, assigneeId: o, discussionConversationId: u }) {
157
+ let [f, x] = p(j, N), [S, C] = s(null), w = async () => {
158
+ C(null);
159
+ try {
160
+ await t(f({ variables: {
161
+ workItemId: e,
162
+ conversationId: null
163
+ } }));
164
+ } catch (e) {
165
+ C(i(e, "The discussion could not be unlinked."));
166
+ }
167
+ };
168
+ return /* @__PURE__ */ h(_, {
169
+ "data-testid": "work-item-discussion",
170
+ children: [/* @__PURE__ */ m(y, { children: /* @__PURE__ */ h(b, {
171
+ className: "flex items-center gap-2",
172
+ children: [/* @__PURE__ */ m(l, {
173
+ className: "h-4 w-4",
174
+ "aria-hidden": "true"
175
+ }), "Discussion"]
176
+ }) }), /* @__PURE__ */ h(v, {
177
+ className: "space-y-3",
178
+ children: [S && /* @__PURE__ */ m(n, { message: S }), u ? /* @__PURE__ */ h("div", {
179
+ className: "flex flex-wrap items-center justify-between gap-2",
180
+ children: [/* @__PURE__ */ h("a", {
181
+ href: `/conversations/${encodeURIComponent(u)}`,
182
+ className: "inline-flex items-center gap-1 text-sm font-medium text-primary hover:underline",
183
+ "data-testid": "discussion-open",
184
+ children: ["Open discussion", /* @__PURE__ */ m(c, {
185
+ className: "h-3.5 w-3.5",
186
+ "aria-hidden": "true"
187
+ })]
188
+ }), /* @__PURE__ */ h(g, {
189
+ size: "sm",
190
+ variant: "ghost",
191
+ disabled: x.loading,
192
+ onClick: () => void w(),
193
+ "data-testid": "discussion-unlink",
194
+ children: [/* @__PURE__ */ m(d, {
195
+ className: "mr-1 h-3.5 w-3.5",
196
+ "aria-hidden": "true"
197
+ }), "Unlink"]
198
+ })]
199
+ }) : /* @__PURE__ */ h("div", {
200
+ className: "space-y-2",
201
+ children: [/* @__PURE__ */ m("p", {
202
+ className: "text-sm text-muted-foreground",
203
+ children: "Not linked to a conversation yet. Starting one creates a group chat for this task in Conversations."
204
+ }), /* @__PURE__ */ m(P, {
205
+ workItemId: e,
206
+ workItemKey: r,
207
+ workItemTitle: a,
208
+ assigneeId: o
209
+ })]
210
+ })]
211
+ })]
212
+ });
213
+ }
214
+ //#endregion
215
+ export { F as WorkItemDiscussionPanel };
216
+
217
+ //# sourceMappingURL=WorkItemDiscussionPanel.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"WorkItemDiscussionPanel.js","names":[],"sources":["../../../src/planmagnet/discussion/WorkItemDiscussionPanel.tsx"],"sourcesContent":["import { useDeferredValue, useEffect, useState } from 'react';\nimport { gql } from '@apollo/client';\nimport { useMutation } from '@apollo/client/react';\nimport {\n Button,\n Card,\n CardContent,\n CardHeader,\n CardTitle,\n Checkbox,\n Input,\n ResponsiveDialog,\n ResponsiveDialogContent,\n ResponsiveDialogDescription,\n ResponsiveDialogFooter,\n ResponsiveDialogHeader,\n ResponsiveDialogTitle,\n ResponsiveDialogTrigger,\n} from '@burdenoff/fe-libs/ui';\nimport { useWorkspaceDirectory } from '@burdenoff/fe-libs/sharing';\nimport { ExternalLink, MessageCircle, MessagesSquare, Unlink as UnlinkIcon } from 'lucide-react';\nimport { usePlanMagnet } from '../providers/PlanMagnetProvider';\nimport { ErrorLine } from '../controls/ConfirmButton';\nimport { throwOnError } from '../utils/mutationResult';\nimport { isForbidden, mutationErrorMessage } from '../utils/graphqlErrors';\n\n/**\n * Discussion on a task (CWF-26): a link to a real group conversation in the platform Conversations product, not\n * a comment thread of its own. `WorkItem.discussionConversationId` only stores and validates the id — the\n * conversation itself is created HERE, as the signed-in user, through the Conversations subgraph's own\n * `createConversation` (a different subgraph than everything else on this page; see its schema doc comment).\n * Once created, `planmagnetSetWorkItemDiscussion` links it back to this item.\n *\n * \"Open discussion\" is a plain `<a>`, not this MFE's own `usePlanMagnetNavigate()` — that hook prepends THIS\n * microfrontend's basePath, and `/conversations/<id>` is a sibling app's own top-level route, not a planmagnet\n * page (there is, correctly, no such route in `PlanMagnetRoutes.tsx`).\n */\n\nconst SET_DISCUSSION = gql`\n mutation PlanmagnetSetWorkItemDiscussionLink($workItemId: ID!, $conversationId: ID) {\n planmagnetSetWorkItemDiscussion(workItemId: $workItemId, conversationId: $conversationId) {\n id\n discussionConversationId\n }\n }\n`;\n\nconst CREATE_DISCUSSION_CONVERSATION = gql`\n mutation CreateWorkItemDiscussionConversation($input: CreateConversationInput!) {\n createConversation(input: $input) {\n success\n conversation {\n id\n }\n }\n }\n`;\n\nconst DISCUSSION_REFETCH = { refetchQueries: ['GetWorkItemDetail'], awaitRefetchQueries: true };\n\n/** Create a group conversation for this task and link it — participants default to the assignee + the current\n * user, editable through the same search-then-checkbox workspace directory picker\n * `roadmaps/RoadmapReviewPanel.tsx`'s `RequestReviewDialog` uses for reviewers. */\nfunction StartDiscussionDialog({\n workItemId,\n workItemKey,\n workItemTitle,\n assigneeId,\n}: {\n workItemId: string;\n workItemKey: string;\n workItemTitle: string;\n assigneeId: string | null;\n}) {\n const { workspaceId, authToken, apiGatewayUrl, currentUser } = usePlanMagnet();\n const [open, setOpen] = useState(false);\n const [search, setSearch] = useState('');\n const deferredSearch = useDeferredValue(search.trim());\n const [selected, setSelected] = useState<Set<string>>(new Set());\n const [error, setError] = useState<string | null>(null);\n\n const directory = useWorkspaceDirectory({\n workspaceId: workspaceId ?? '',\n authToken: authToken ?? undefined,\n apiGatewayUrl: apiGatewayUrl ?? undefined,\n });\n const { search: setDirectoryFilter } = directory;\n useEffect(() => {\n setDirectoryFilter(deferredSearch);\n }, [deferredSearch, setDirectoryFilter]);\n\n const [createConversation, createState] = useMutation<{\n createConversation: { success: boolean; conversation: { id: string } | null };\n }>(CREATE_DISCUSSION_CONVERSATION);\n const [setDiscussion, setDiscussionState] = useMutation(SET_DISCUSSION, DISCUSSION_REFETCH);\n const busy = createState.loading || setDiscussionState.loading;\n\n const toggle = (id: string, checked: boolean) =>\n setSelected((prev) => {\n const next = new Set(prev);\n if (checked) next.add(id);\n else next.delete(id);\n return next;\n });\n\n const submit = async () => {\n if (!workspaceId) return;\n setError(null);\n try {\n const created = await throwOnError(\n createConversation({\n variables: {\n input: {\n workspaceId,\n type: 'GROUP',\n title: `${workItemKey}: ${workItemTitle}`,\n participantIds: [...selected],\n },\n },\n })\n );\n const conversationId = created.data?.createConversation?.conversation?.id;\n if (!created.data?.createConversation?.success || !conversationId) {\n throw new Error('The conversation could not be created.');\n }\n await throwOnError(setDiscussion({ variables: { workItemId, conversationId } }));\n setOpen(false);\n setSearch('');\n } catch (e) {\n setError(\n isForbidden(e)\n ? 'Your role cannot start a conversation in this workspace.'\n : mutationErrorMessage(e, 'The discussion could not be started.')\n );\n }\n };\n\n return (\n <ResponsiveDialog\n open={open}\n onOpenChange={(next) => {\n setOpen(next);\n if (next) {\n // Default selection (assignee + self), recomputed on every open so an earlier session's manual picks\n // never leak into a later one.\n const defaults = new Set<string>();\n if (assigneeId) defaults.add(assigneeId);\n if (currentUser?.id) defaults.add(currentUser.id);\n setSelected(defaults);\n } else {\n setSearch('');\n setError(null);\n }\n }}\n >\n <ResponsiveDialogTrigger asChild>\n <Button size=\"sm\" data-testid=\"discussion-start\">\n <MessagesSquare className=\"mr-1 h-4 w-4\" aria-hidden=\"true\" />\n Discuss in Conversations\n </Button>\n </ResponsiveDialogTrigger>\n <ResponsiveDialogContent className=\"max-w-lg\">\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>Start a discussion</ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n Creates a group conversation for this task and links it back here. Add anyone else who\n should be in it.\n </ResponsiveDialogDescription>\n </ResponsiveDialogHeader>\n <p className=\"text-sm font-medium\">\n {workItemKey}: {workItemTitle}\n </p>\n <Input\n value={search}\n onChange={(e) => setSearch(e.target.value)}\n placeholder=\"Search workspace members\"\n aria-label=\"Search workspace members\"\n />\n <div className=\"max-h-64 space-y-1 overflow-y-auto\">\n {directory.loading && directory.members.length === 0 && (\n <p className=\"py-4 text-center text-sm text-muted-foreground\">Loading…</p>\n )}\n {!directory.loading && directory.members.length === 0 && (\n <p className=\"py-4 text-center text-sm text-muted-foreground\">No matching members.</p>\n )}\n {directory.members.map((member) => {\n const inputId = `discussion-participant-${member.id}`;\n return (\n <label\n key={member.id}\n htmlFor={inputId}\n className=\"flex cursor-pointer items-center gap-3 rounded-md border p-2 text-sm hover:bg-muted\"\n >\n <Checkbox\n id={inputId}\n checked={selected.has(member.id)}\n onCheckedChange={(checked) => toggle(member.id, checked === true)}\n />\n <span className=\"min-w-0 flex-1 truncate\">\n {member.name}\n {member.id === currentUser?.id && (\n <span className=\"text-muted-foreground\"> (you)</span>\n )}\n {member.id === assigneeId && (\n <span className=\"text-muted-foreground\"> · assignee</span>\n )}\n </span>\n </label>\n );\n })}\n {directory.hasMore && (\n <button\n type=\"button\"\n className=\"w-full py-1 text-xs text-primary hover:underline\"\n onClick={directory.fetchMore}\n >\n Show more members\n </button>\n )}\n </div>\n {error && <ErrorLine message={error} />}\n <ResponsiveDialogFooter>\n <Button\n onClick={() => void submit()}\n disabled={selected.size === 0 || busy}\n data-testid=\"discussion-create-confirm\"\n >\n {busy ? 'Starting…' : `Start discussion (${selected.size})`}\n </Button>\n </ResponsiveDialogFooter>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n );\n}\n\nexport function WorkItemDiscussionPanel({\n workItemId,\n workItemKey,\n workItemTitle,\n assigneeId,\n discussionConversationId,\n}: {\n workItemId: string;\n workItemKey: string;\n workItemTitle: string;\n assigneeId: string | null;\n discussionConversationId: string | null;\n}) {\n const [unlink, unlinkState] = useMutation(SET_DISCUSSION, DISCUSSION_REFETCH);\n const [error, setError] = useState<string | null>(null);\n\n const doUnlink = async () => {\n setError(null);\n try {\n await throwOnError(unlink({ variables: { workItemId, conversationId: null } }));\n } catch (e) {\n setError(mutationErrorMessage(e, 'The discussion could not be unlinked.'));\n }\n };\n\n return (\n <Card data-testid=\"work-item-discussion\">\n <CardHeader>\n <CardTitle className=\"flex items-center gap-2\">\n <MessageCircle className=\"h-4 w-4\" aria-hidden=\"true\" />\n Discussion\n </CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-3\">\n {error && <ErrorLine message={error} />}\n {discussionConversationId ? (\n <div className=\"flex flex-wrap items-center justify-between gap-2\">\n <a\n href={`/conversations/${encodeURIComponent(discussionConversationId)}`}\n className=\"inline-flex items-center gap-1 text-sm font-medium text-primary hover:underline\"\n data-testid=\"discussion-open\"\n >\n Open discussion\n <ExternalLink className=\"h-3.5 w-3.5\" aria-hidden=\"true\" />\n </a>\n <Button\n size=\"sm\"\n variant=\"ghost\"\n disabled={unlinkState.loading}\n onClick={() => void doUnlink()}\n data-testid=\"discussion-unlink\"\n >\n <UnlinkIcon className=\"mr-1 h-3.5 w-3.5\" aria-hidden=\"true\" />\n Unlink\n </Button>\n </div>\n ) : (\n <div className=\"space-y-2\">\n <p className=\"text-sm text-muted-foreground\">\n Not linked to a conversation yet. Starting one creates a group chat for this task in\n Conversations.\n </p>\n <StartDiscussionDialog\n workItemId={workItemId}\n workItemKey={workItemKey}\n workItemTitle={workItemTitle}\n assigneeId={assigneeId}\n />\n </div>\n )}\n </CardContent>\n </Card>\n );\n}\n"],"mappings":"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"}
@@ -0,0 +1,26 @@
1
+ import { LineDiffKind } from '../utils/lineDiff';
2
+
3
+ /**
4
+ * Renders ADDED/REMOVED/UNCHANGED hunks (CWF-18 §2/§3) — shared by the suggestion diff preview
5
+ * (`DocSuggestionsPanel`, a client-side `lineDiff`) and the version-compare panel (`DocVersionCompare`, the
6
+ * server's `planmagnetDocRevisionDiff`); both produce the same `{kind, text}` shape.
7
+ *
8
+ * Colour is never the only signal: every line carries a leading `+`/`−`/` ` marker AND a screen-reader-only
9
+ * "Added:"/"Removed:" prefix, not just a background tint.
10
+ */
11
+ export interface DocDiffHunkLike {
12
+ kind: LineDiffKind;
13
+ text: string;
14
+ }
15
+ export declare function DocDiffHunks({ hunks, truncated, emptyMessage, testId, }: {
16
+ hunks: readonly DocDiffHunkLike[];
17
+ truncated?: boolean;
18
+ emptyMessage?: string;
19
+ /**
20
+ * Overridable so two instances never collide on the same page. `DocVersionCompare` (one result at a time) keeps
21
+ * the default `doc-version-diff`; `DocSuggestionsPanel` passes a per-suggestion id, since several suggestions'
22
+ * previews can be expanded at once in the Open tab.
23
+ */
24
+ testId?: string;
25
+ }): import("react/jsx-runtime").JSX.Element;
26
+ //# sourceMappingURL=DocDiffView.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DocDiffView.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/docs/DocDiffView.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAEtD;;;;;;;GAOG;AAEH,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,YAAY,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;CACd;AAgBD,wBAAgB,YAAY,CAAC,EAC3B,KAAK,EACL,SAAS,EACT,YAAgC,EAChC,MAA2B,GAC5B,EAAE;IACD,KAAK,EAAE,SAAS,eAAe,EAAE,CAAC;IAClC,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;;OAIG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,2CA0BA"}
@@ -0,0 +1,52 @@
1
+ import { jsx as e, jsxs as t } from "react/jsx-runtime";
2
+ //#region src/planmagnet/docs/DocDiffView.tsx
3
+ var n = {
4
+ ADDED: {
5
+ symbol: "+",
6
+ srLabel: "Added: ",
7
+ className: "bg-status-success-subtle text-status-success-text"
8
+ },
9
+ REMOVED: {
10
+ symbol: "−",
11
+ srLabel: "Removed: ",
12
+ className: "bg-status-error-subtle text-status-error-text"
13
+ },
14
+ UNCHANGED: {
15
+ symbol: " ",
16
+ srLabel: "",
17
+ className: "text-muted-foreground"
18
+ }
19
+ };
20
+ function r({ hunks: r, truncated: i, emptyMessage: a = "No differences.", testId: o = "doc-version-diff" }) {
21
+ return r.length === 0 ? /* @__PURE__ */ e("p", {
22
+ className: "text-sm text-muted-foreground",
23
+ children: a
24
+ }) : /* @__PURE__ */ t("div", {
25
+ className: "max-h-96 space-y-0.5 overflow-y-auto rounded-md border border-border-default bg-muted/30 p-2 font-mono text-xs",
26
+ "data-testid": o,
27
+ children: [r.map((r, i) => {
28
+ let a = n[r.kind];
29
+ return /* @__PURE__ */ t("div", {
30
+ className: `whitespace-pre-wrap rounded-sm px-1 ${a.className}`,
31
+ children: [
32
+ /* @__PURE__ */ t("span", {
33
+ "aria-hidden": "true",
34
+ children: [a.symbol, " "]
35
+ }),
36
+ /* @__PURE__ */ e("span", {
37
+ className: "sr-only",
38
+ children: a.srLabel
39
+ }),
40
+ r.text.length > 0 ? r.text : "\xA0"
41
+ ]
42
+ }, i);
43
+ }), i && /* @__PURE__ */ e("p", {
44
+ className: "pt-1 text-[11px] italic text-muted-foreground",
45
+ children: "Truncated — only the first part of the difference is shown."
46
+ })]
47
+ });
48
+ }
49
+ //#endregion
50
+ export { r as DocDiffHunks };
51
+
52
+ //# sourceMappingURL=DocDiffView.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DocDiffView.js","names":[],"sources":["../../../src/planmagnet/docs/DocDiffView.tsx"],"sourcesContent":["import type { LineDiffKind } from '../utils/lineDiff';\n\n/**\n * Renders ADDED/REMOVED/UNCHANGED hunks (CWF-18 §2/§3) — shared by the suggestion diff preview\n * (`DocSuggestionsPanel`, a client-side `lineDiff`) and the version-compare panel (`DocVersionCompare`, the\n * server's `planmagnetDocRevisionDiff`); both produce the same `{kind, text}` shape.\n *\n * Colour is never the only signal: every line carries a leading `+`/`−`/` ` marker AND a screen-reader-only\n * \"Added:\"/\"Removed:\" prefix, not just a background tint.\n */\n\nexport interface DocDiffHunkLike {\n kind: LineDiffKind;\n text: string;\n}\n\nconst KIND_META: Record<LineDiffKind, { symbol: string; srLabel: string; className: string }> = {\n ADDED: {\n symbol: '+',\n srLabel: 'Added: ',\n className: 'bg-status-success-subtle text-status-success-text',\n },\n REMOVED: {\n symbol: '−',\n srLabel: 'Removed: ',\n className: 'bg-status-error-subtle text-status-error-text',\n },\n UNCHANGED: { symbol: ' ', srLabel: '', className: 'text-muted-foreground' },\n};\n\nexport function DocDiffHunks({\n hunks,\n truncated,\n emptyMessage = 'No differences.',\n testId = 'doc-version-diff',\n}: {\n hunks: readonly DocDiffHunkLike[];\n truncated?: boolean;\n emptyMessage?: string;\n /**\n * Overridable so two instances never collide on the same page. `DocVersionCompare` (one result at a time) keeps\n * the default `doc-version-diff`; `DocSuggestionsPanel` passes a per-suggestion id, since several suggestions'\n * previews can be expanded at once in the Open tab.\n */\n testId?: string;\n}) {\n if (hunks.length === 0) {\n return <p className=\"text-sm text-muted-foreground\">{emptyMessage}</p>;\n }\n return (\n <div\n className=\"max-h-96 space-y-0.5 overflow-y-auto rounded-md border border-border-default bg-muted/30 p-2 font-mono text-xs\"\n data-testid={testId}\n >\n {hunks.map((hunk, index) => {\n const meta = KIND_META[hunk.kind];\n return (\n <div key={index} className={`whitespace-pre-wrap rounded-sm px-1 ${meta.className}`}>\n <span aria-hidden=\"true\">{meta.symbol} </span>\n <span className=\"sr-only\">{meta.srLabel}</span>\n {hunk.text.length > 0 ? hunk.text : ' '}\n </div>\n );\n })}\n {truncated && (\n <p className=\"pt-1 text-[11px] italic text-muted-foreground\">\n Truncated — only the first part of the difference is shown.\n </p>\n )}\n </div>\n );\n}\n"],"mappings":";;AAgBA,IAAM,IAA0F;CAC9F,OAAO;EACL,QAAQ;EACR,SAAS;EACT,WAAW;EACZ;CACD,SAAS;EACP,QAAQ;EACR,SAAS;EACT,WAAW;EACZ;CACD,WAAW;EAAE,QAAQ;EAAK,SAAS;EAAI,WAAW;EAAyB;CAC5E;AAED,SAAgB,EAAa,EAC3B,UACA,cACA,kBAAe,mBACf,YAAS,sBAWR;AAID,QAHI,EAAM,WAAW,IACZ,kBAAC,KAAD;EAAG,WAAU;YAAiC;EAAiB,CAAA,GAGtE,kBAAC,OAAD;EACE,WAAU;EACV,eAAa;YAFf,CAIG,EAAM,KAAK,GAAM,MAAU;GAC1B,IAAM,IAAO,EAAU,EAAK;AAC5B,UACE,kBAAC,OAAD;IAAiB,WAAW,uCAAuC,EAAK;cAAxE;KACE,kBAAC,QAAD;MAAM,eAAY;gBAAlB,CAA0B,EAAK,QAAO,IAAQ;;KAC9C,kBAAC,QAAD;MAAM,WAAU;gBAAW,EAAK;MAAe,CAAA;KAC9C,EAAK,KAAK,SAAS,IAAI,EAAK,OAAO;KAChC;MAJI,EAIJ;IAER,EACD,KACC,kBAAC,KAAD;GAAG,WAAU;aAAgD;GAEzD,CAAA,CAEF"}
@@ -0,0 +1,13 @@
1
+ export interface DocRevisionForDiff {
2
+ version: number;
3
+ title: string;
4
+ content: string;
5
+ }
6
+ export declare function DocSuggestionsPanel({ docId, docTitle, docContent, revisions, currentUserId, }: {
7
+ docId: string;
8
+ docTitle: string;
9
+ docContent: string;
10
+ revisions: readonly DocRevisionForDiff[];
11
+ currentUserId: string | undefined;
12
+ }): import("react/jsx-runtime").JSX.Element;
13
+ //# sourceMappingURL=DocSuggestionsPanel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DocSuggestionsPanel.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/docs/DocSuggestionsPanel.tsx"],"names":[],"mappings":"AA2GA,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;CACjB;AA0QD,wBAAgB,mBAAmB,CAAC,EAClC,KAAK,EACL,QAAQ,EACR,UAAU,EACV,SAAS,EACT,aAAa,GACd,EAAE;IACD,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,SAAS,kBAAkB,EAAE,CAAC;IACzC,aAAa,EAAE,MAAM,GAAG,SAAS,CAAC;CACnC,2CAuCA"}