@burdenoff/microfe-planmagnet 2026.1002.2 → 2026.1002.3

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 (154) hide show
  1. package/dist/generated/wspace-operations.d.ts +574 -0
  2. package/dist/generated/wspace-operations.d.ts.map +1 -1
  3. package/dist/generated/wspace-types.d.ts +486 -16
  4. package/dist/generated/wspace-types.d.ts.map +1 -1
  5. package/dist/index.js +8 -8
  6. package/dist/planmagnet/PlanMagnetRoot.d.ts.map +1 -1
  7. package/dist/planmagnet/PlanMagnetRoot.js +174 -168
  8. package/dist/planmagnet/PlanMagnetRoot.js.map +1 -1
  9. package/dist/planmagnet/PlanMagnetRoutes.d.ts.map +1 -1
  10. package/dist/planmagnet/PlanMagnetRoutes.js +63 -59
  11. package/dist/planmagnet/PlanMagnetRoutes.js.map +1 -1
  12. package/dist/planmagnet/board/BulkActions.d.ts.map +1 -1
  13. package/dist/planmagnet/board/BulkActions.js +106 -55
  14. package/dist/planmagnet/board/BulkActions.js.map +1 -1
  15. package/dist/planmagnet/controls/Pickers.d.ts +3 -3
  16. package/dist/planmagnet/controls/Pickers.js.map +1 -1
  17. package/dist/planmagnet/dashboards/DashboardUI.d.ts +0 -4
  18. package/dist/planmagnet/dashboards/DashboardUI.d.ts.map +1 -1
  19. package/dist/planmagnet/dashboards/DashboardUI.js +6 -13
  20. package/dist/planmagnet/dashboards/DashboardUI.js.map +1 -1
  21. package/dist/planmagnet/dashboards/cards/SprintCapacityCard.d.ts.map +1 -1
  22. package/dist/planmagnet/dashboards/cards/SprintCapacityCard.js +70 -59
  23. package/dist/planmagnet/dashboards/cards/SprintCapacityCard.js.map +1 -1
  24. package/dist/planmagnet/forecast/ResourceForecastCard.js +1 -1
  25. package/dist/planmagnet/forecast/ResourceForecastCard.js.map +1 -1
  26. package/dist/planmagnet/index.d.ts +1 -1
  27. package/dist/planmagnet/index.d.ts.map +1 -1
  28. package/dist/planmagnet/pages/DashboardsProjectManagerPagePage.d.ts +3 -3
  29. package/dist/planmagnet/pages/DashboardsProjectManagerPagePage.d.ts.map +1 -1
  30. package/dist/planmagnet/pages/DashboardsProjectManagerPagePage.js +26 -19
  31. package/dist/planmagnet/pages/DashboardsProjectManagerPagePage.js.map +1 -1
  32. package/dist/planmagnet/pages/DashboardsTeamUtilizationPagePage.d.ts +2 -2
  33. package/dist/planmagnet/pages/DashboardsTeamUtilizationPagePage.d.ts.map +1 -1
  34. package/dist/planmagnet/pages/DashboardsTeamUtilizationPagePage.js +18 -12
  35. package/dist/planmagnet/pages/DashboardsTeamUtilizationPagePage.js.map +1 -1
  36. package/dist/planmagnet/pages/ProductsCustomerSatisfactionPagePage.d.ts +2 -0
  37. package/dist/planmagnet/pages/ProductsCustomerSatisfactionPagePage.d.ts.map +1 -0
  38. package/dist/planmagnet/pages/ProductsCustomerSatisfactionPagePage.js +251 -0
  39. package/dist/planmagnet/pages/ProductsCustomerSatisfactionPagePage.js.map +1 -0
  40. package/dist/planmagnet/pages/ProductsProductDetailPagePage.d.ts.map +1 -1
  41. package/dist/planmagnet/pages/ProductsProductDetailPagePage.js +94 -92
  42. package/dist/planmagnet/pages/ProductsProductDetailPagePage.js.map +1 -1
  43. package/dist/planmagnet/pages/ProductsReleaseDetailPagePage.d.ts.map +1 -1
  44. package/dist/planmagnet/pages/ProductsReleaseDetailPagePage.js +116 -114
  45. package/dist/planmagnet/pages/ProductsReleaseDetailPagePage.js.map +1 -1
  46. package/dist/planmagnet/pages/ProjectsPlanningDetailPagePage.d.ts.map +1 -1
  47. package/dist/planmagnet/pages/ProjectsPlanningDetailPagePage.js +174 -159
  48. package/dist/planmagnet/pages/ProjectsPlanningDetailPagePage.js.map +1 -1
  49. package/dist/planmagnet/pages/TasksCreateTaskPagePage.d.ts.map +1 -1
  50. package/dist/planmagnet/pages/TasksCreateTaskPagePage.js +159 -145
  51. package/dist/planmagnet/pages/TasksCreateTaskPagePage.js.map +1 -1
  52. package/dist/planmagnet/pages/TasksEditTaskPagePage.d.ts.map +1 -1
  53. package/dist/planmagnet/pages/TasksEditTaskPagePage.js +161 -143
  54. package/dist/planmagnet/pages/TasksEditTaskPagePage.js.map +1 -1
  55. package/dist/planmagnet/pages/TasksTaskDetailPagePage.d.ts.map +1 -1
  56. package/dist/planmagnet/pages/TasksTaskDetailPagePage.js +157 -155
  57. package/dist/planmagnet/pages/TasksTaskDetailPagePage.js.map +1 -1
  58. package/dist/planmagnet/pages/analytics/DeliveryKpisCard.d.ts.map +1 -1
  59. package/dist/planmagnet/pages/analytics/DeliveryKpisCard.js +97 -99
  60. package/dist/planmagnet/pages/analytics/DeliveryKpisCard.js.map +1 -1
  61. package/dist/planmagnet/product/FeatureLinkPanels.js +66 -66
  62. package/dist/planmagnet/product/ProductFeatureControls.js +38 -38
  63. package/dist/planmagnet/product/ProductFeatureLinkCard.js +8 -8
  64. package/dist/planmagnet/product/ProductLineControls.js +83 -83
  65. package/dist/planmagnet/product/ProductTeamsPanel.js +6 -6
  66. package/dist/planmagnet/product/routes.d.ts +6 -0
  67. package/dist/planmagnet/product/routes.d.ts.map +1 -1
  68. package/dist/planmagnet/product/routes.js +2 -2
  69. package/dist/planmagnet/product/routes.js.map +1 -1
  70. package/dist/planmagnet/satisfaction/DeliverySatisfactionSection.d.ts +20 -0
  71. package/dist/planmagnet/satisfaction/DeliverySatisfactionSection.d.ts.map +1 -0
  72. package/dist/planmagnet/satisfaction/DeliverySatisfactionSection.js +55 -0
  73. package/dist/planmagnet/satisfaction/DeliverySatisfactionSection.js.map +1 -0
  74. package/dist/planmagnet/satisfaction/SatisfactionFigures.d.ts +30 -0
  75. package/dist/planmagnet/satisfaction/SatisfactionFigures.d.ts.map +1 -0
  76. package/dist/planmagnet/satisfaction/SatisfactionFigures.js +282 -0
  77. package/dist/planmagnet/satisfaction/SatisfactionFigures.js.map +1 -0
  78. package/dist/planmagnet/satisfaction/SatisfactionImportDialog.d.ts +9 -0
  79. package/dist/planmagnet/satisfaction/SatisfactionImportDialog.d.ts.map +1 -0
  80. package/dist/planmagnet/satisfaction/SatisfactionImportDialog.js +390 -0
  81. package/dist/planmagnet/satisfaction/SatisfactionImportDialog.js.map +1 -0
  82. package/dist/planmagnet/satisfaction/SatisfactionLinkFields.d.ts +17 -0
  83. package/dist/planmagnet/satisfaction/SatisfactionLinkFields.d.ts.map +1 -0
  84. package/dist/planmagnet/satisfaction/SatisfactionLinkFields.js +77 -0
  85. package/dist/planmagnet/satisfaction/SatisfactionLinkFields.js.map +1 -0
  86. package/dist/planmagnet/satisfaction/SatisfactionReleaseSelect.d.ts +18 -0
  87. package/dist/planmagnet/satisfaction/SatisfactionReleaseSelect.d.ts.map +1 -0
  88. package/dist/planmagnet/satisfaction/SatisfactionReleaseSelect.js +87 -0
  89. package/dist/planmagnet/satisfaction/SatisfactionReleaseSelect.js.map +1 -0
  90. package/dist/planmagnet/satisfaction/SatisfactionResponseDialog.d.ts +13 -0
  91. package/dist/planmagnet/satisfaction/SatisfactionResponseDialog.d.ts.map +1 -0
  92. package/dist/planmagnet/satisfaction/SatisfactionResponseDialog.js +326 -0
  93. package/dist/planmagnet/satisfaction/SatisfactionResponseDialog.js.map +1 -0
  94. package/dist/planmagnet/satisfaction/SatisfactionResponsesTable.d.ts +23 -0
  95. package/dist/planmagnet/satisfaction/SatisfactionResponsesTable.d.ts.map +1 -0
  96. package/dist/planmagnet/satisfaction/SatisfactionResponsesTable.js +258 -0
  97. package/dist/planmagnet/satisfaction/SatisfactionResponsesTable.js.map +1 -0
  98. package/dist/planmagnet/satisfaction/SatisfactionSummaryCard.d.ts +6 -0
  99. package/dist/planmagnet/satisfaction/SatisfactionSummaryCard.d.ts.map +1 -0
  100. package/dist/planmagnet/satisfaction/SatisfactionSummaryCard.js +56 -0
  101. package/dist/planmagnet/satisfaction/SatisfactionSummaryCard.js.map +1 -0
  102. package/dist/planmagnet/satisfaction/SatisfactionWindowPicker.d.ts +16 -0
  103. package/dist/planmagnet/satisfaction/SatisfactionWindowPicker.d.ts.map +1 -0
  104. package/dist/planmagnet/satisfaction/SatisfactionWindowPicker.js +102 -0
  105. package/dist/planmagnet/satisfaction/SatisfactionWindowPicker.js.map +1 -0
  106. package/dist/planmagnet/satisfaction/satisfaction.d.ts +154 -0
  107. package/dist/planmagnet/satisfaction/satisfaction.d.ts.map +1 -0
  108. package/dist/planmagnet/satisfaction/satisfaction.js +210 -0
  109. package/dist/planmagnet/satisfaction/satisfaction.js.map +1 -0
  110. package/dist/planmagnet/satisfaction/satisfactionCsv.d.ts +108 -0
  111. package/dist/planmagnet/satisfaction/satisfactionCsv.d.ts.map +1 -0
  112. package/dist/planmagnet/satisfaction/satisfactionCsv.js +211 -0
  113. package/dist/planmagnet/satisfaction/satisfactionCsv.js.map +1 -0
  114. package/dist/planmagnet/satisfaction/satisfactionForm.d.ts +90 -0
  115. package/dist/planmagnet/satisfaction/satisfactionForm.d.ts.map +1 -0
  116. package/dist/planmagnet/satisfaction/satisfactionForm.js +90 -0
  117. package/dist/planmagnet/satisfaction/satisfactionForm.js.map +1 -0
  118. package/dist/planmagnet/satisfaction/satisfactionOperations.d.ts +116 -0
  119. package/dist/planmagnet/satisfaction/satisfactionOperations.d.ts.map +1 -0
  120. package/dist/planmagnet/satisfaction/satisfactionOperations.js +86 -0
  121. package/dist/planmagnet/satisfaction/satisfactionOperations.js.map +1 -0
  122. package/dist/planmagnet/satisfaction/useSatisfactionAccess.d.ts +20 -0
  123. package/dist/planmagnet/satisfaction/useSatisfactionAccess.d.ts.map +1 -0
  124. package/dist/planmagnet/satisfaction/useSatisfactionAccess.js +31 -0
  125. package/dist/planmagnet/satisfaction/useSatisfactionAccess.js.map +1 -0
  126. package/dist/planmagnet/skills/RequiredSkillsEditor.d.ts +35 -0
  127. package/dist/planmagnet/skills/RequiredSkillsEditor.d.ts.map +1 -0
  128. package/dist/planmagnet/skills/RequiredSkillsEditor.js +124 -0
  129. package/dist/planmagnet/skills/RequiredSkillsEditor.js.map +1 -0
  130. package/dist/planmagnet/skills/SkillChips.d.ts +30 -0
  131. package/dist/planmagnet/skills/SkillChips.d.ts.map +1 -0
  132. package/dist/planmagnet/skills/SkillChips.js +57 -0
  133. package/dist/planmagnet/skills/SkillChips.js.map +1 -0
  134. package/dist/planmagnet/skills/SkillCoverageCard.d.ts +24 -0
  135. package/dist/planmagnet/skills/SkillCoverageCard.d.ts.map +1 -0
  136. package/dist/planmagnet/skills/SkillCoverageCard.js +256 -0
  137. package/dist/planmagnet/skills/SkillCoverageCard.js.map +1 -0
  138. package/dist/planmagnet/skills/WorkItemRequiredSkills.d.ts +10 -0
  139. package/dist/planmagnet/skills/WorkItemRequiredSkills.d.ts.map +1 -0
  140. package/dist/planmagnet/skills/WorkItemRequiredSkills.js +65 -0
  141. package/dist/planmagnet/skills/WorkItemRequiredSkills.js.map +1 -0
  142. package/dist/planmagnet/skills/skillOperations.d.ts +52 -0
  143. package/dist/planmagnet/skills/skillOperations.d.ts.map +1 -0
  144. package/dist/planmagnet/skills/skillOperations.js +44 -0
  145. package/dist/planmagnet/skills/skillOperations.js.map +1 -0
  146. package/dist/planmagnet/skills/skillTags.d.ts +103 -0
  147. package/dist/planmagnet/skills/skillTags.d.ts.map +1 -0
  148. package/dist/planmagnet/skills/skillTags.js +115 -0
  149. package/dist/planmagnet/skills/skillTags.js.map +1 -0
  150. package/dist/planmagnet/skills/useRequiredSkillsDraft.d.ts +28 -0
  151. package/dist/planmagnet/skills/useRequiredSkillsDraft.d.ts.map +1 -0
  152. package/dist/planmagnet/skills/useRequiredSkillsDraft.js +30 -0
  153. package/dist/planmagnet/skills/useRequiredSkillsDraft.js.map +1 -0
  154. package/package.json +1 -1
@@ -0,0 +1,115 @@
1
+ import { CombinedGraphQLErrors as e } from "../../node_modules/@apollo/client/errors/CombinedGraphQLErrors.js";
2
+ function t(e) {
3
+ return e.replace(/\s+/g, " ").trim();
4
+ }
5
+ function n(e) {
6
+ return t(e).toLowerCase();
7
+ }
8
+ function r(e) {
9
+ for (let t of e) {
10
+ let e = t.codePointAt(0) ?? 0;
11
+ if (e < 32 || e >= 127 && e <= 159) return !0;
12
+ }
13
+ return !1;
14
+ }
15
+ var i = /[,\n]/;
16
+ function a(e, a) {
17
+ let o = [...e], s = new Set(e.map(n)), c = [], l = null;
18
+ for (let e of a.split(i)) {
19
+ let i = t(e);
20
+ if (!i) continue;
21
+ if (r(i)) {
22
+ l ||= { kind: "invalid" }, c.push(i);
23
+ continue;
24
+ }
25
+ if ([...i].length > 64) {
26
+ l ||= {
27
+ kind: "tooLong",
28
+ max: 64
29
+ }, c.push(i);
30
+ continue;
31
+ }
32
+ let a = i.toLowerCase();
33
+ if (s.has(a)) {
34
+ l ||= {
35
+ kind: "duplicate",
36
+ skill: o.find((e) => n(e) === a) ?? i
37
+ };
38
+ continue;
39
+ }
40
+ if (o.length >= 20) {
41
+ l ||= {
42
+ kind: "tooMany",
43
+ max: 20
44
+ }, c.push(i);
45
+ continue;
46
+ }
47
+ s.add(a), o.push(i);
48
+ }
49
+ return {
50
+ skills: o,
51
+ rest: c.join(", "),
52
+ problem: l
53
+ };
54
+ }
55
+ function o(e, t) {
56
+ return e.filter((e, n) => n !== t);
57
+ }
58
+ function s(e, t) {
59
+ return e.length === t.length && e.every((e, n) => e === t[n]);
60
+ }
61
+ function c(t) {
62
+ return e.is(t) && t.errors.find((e) => e.extensions?.field === "requiredSkills")?.message || null;
63
+ }
64
+ var l = {
65
+ UNCOVERED: 0,
66
+ UNKNOWN: 1,
67
+ COVERED: 2,
68
+ NO_DEMAND: 3
69
+ };
70
+ function u(e) {
71
+ return l[e] ?? Object.keys(l).length;
72
+ }
73
+ function d(e) {
74
+ return e.map((e, t) => ({
75
+ row: e,
76
+ index: t
77
+ })).sort((e, t) => u(e.row.status) - u(t.row.status) || e.index - t.index).map(({ row: e }) => e);
78
+ }
79
+ function f(e) {
80
+ let t = {
81
+ UNCOVERED: 0,
82
+ UNKNOWN: 0,
83
+ COVERED: 0,
84
+ NO_DEMAND: 0
85
+ };
86
+ for (let n of e) n.status in t && (t[n.status] += 1);
87
+ return t;
88
+ }
89
+ function p(e) {
90
+ switch (e) {
91
+ case "UNCOVERED": return "destructive";
92
+ case "UNKNOWN": return "warning";
93
+ case "COVERED": return "success";
94
+ case "NO_DEMAND": return "secondary";
95
+ default: return "outline";
96
+ }
97
+ }
98
+ function m(e) {
99
+ let t = {};
100
+ return e.projectId && (t.projectId = e.projectId), e.teamId && (t.teamId = e.teamId), e.sprintId && (t.sprintId = e.sprintId), t;
101
+ }
102
+ function h(e) {
103
+ let t = e.toLowerCase();
104
+ return t.startsWith("alphaapp.") || t.startsWith("alpha.") ? "https://alphaapp.crewfoundry.com" : t === "localhost" || t === "127.0.0.1" || t.endsWith(".localhost") || t.endsWith(".local.burdenoff.com") ? "https://crewfoundry.local.burdenoff.com" : "https://app.crewfoundry.com";
105
+ }
106
+ function g(e, t) {
107
+ return `${h(t)}/crewfoundry/hr/employee-profile-page?id=${encodeURIComponent(e)}`;
108
+ }
109
+ function _(e) {
110
+ return `${h(e)}/crewfoundry/hr/employees-page`;
111
+ }
112
+ //#endregion
113
+ export { a as addSkillTags, p as coverageBadgeVariant, f as coverageCounts, m as coverageFilter, g as crewFoundryEmployeeUrl, _ as crewFoundryEmployeesUrl, d as orderCoverage, o as removeSkillAt, c as requiredSkillsRefusal, s as sameSkillList, n as skillKey };
114
+
115
+ //# sourceMappingURL=skillTags.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"skillTags.js","names":[],"sources":["../../../src/planmagnet/skills/skillTags.ts"],"sourcesContent":["import { CombinedGraphQLErrors } from '@apollo/client/errors';\n\n/**\n * Skill tags (ANR-07 skill view, ANR-28 skill utilization). PlanMagnet owns the skills a WORK ITEM requires\n * (`WorkItem.requiredSkills`); CrewFoundry owns PEOPLE's skills, which PlanMagnet only reads (owner decision\n * 2026-10-02). The two meet by exact tag matching on a key: the tag trimmed, whitespace runs collapsed, lower-cased.\n * No proficiency levels, no synonyms.\n *\n * The service is the authority on the write rules (wspace-planmagnet-svc `src/utils/required-skills.ts`: tidy,\n * case-insensitive duplicates dropped keeping the first spelling, at most 20 tags of at most 64 characters, an empty\n * or control-character tag refused). The editor applies the same rules as the user types, so the common mistakes\n * are explained before anything is sent; a refusal that still comes back is shown on the field\n * (`requiredSkillsRefusal`).\n */\n\n/** The most skills one work item may require (the service's MAX_REQUIRED_SKILLS). */\nexport const MAX_REQUIRED_SKILLS = 20;\n\n/** The longest one skill may be, in characters (Unicode code points) after tidying (the service's MAX_SKILL_LENGTH). */\nexport const MAX_SKILL_LENGTH = 64;\n\n/** Trim and collapse every run of whitespace to one space: the stored and displayed form of a tag. */\nexport function tidySkill(value: string): string {\n return value.replace(/\\s+/g, ' ').trim();\n}\n\n/** The matching key of a tag. Two tags are the same skill exactly when their keys are equal. */\nexport function skillKey(value: string): string {\n return tidySkill(value).toLowerCase();\n}\n\n/** C0/C1 control characters (and DEL) that survive tidying. The service refuses them; checked by code point. */\nfunction hasControlCharacter(value: string): boolean {\n for (const char of value) {\n const code = char.codePointAt(0) ?? 0;\n if (code < 0x20 || (code >= 0x7f && code <= 0x9f)) return true;\n }\n return false;\n}\n\n/** Why something the user typed was not added to the list. */\nexport type SkillTagProblem =\n | { kind: 'duplicate'; skill: string }\n | { kind: 'tooLong'; max: number }\n | { kind: 'tooMany'; max: number }\n | { kind: 'invalid' };\n\nexport interface AddSkillTagsResult {\n /** The list with every acceptable new tag appended, in the order typed. */\n skills: string[];\n /** What could not be added, to leave in the input: a refusal never silently drops what was typed. */\n rest: string;\n /** The first reason something was not added, or null when everything typed was added. */\n problem: SkillTagProblem | null;\n}\n\n/** A pasted list (\"React, Postgres\" or one per line) adds several tags at once. */\nconst SEPARATORS = /[,\\n]/;\n\n/**\n * Add what the user typed — one skill, or several separated by commas or line breaks — to `current`. Each tag is\n * tidied; blank parts are ignored; a tag already on the list (ignoring case) is not added twice, and the list keeps\n * its first spelling, as the service does. A tag that is too long, has a control character, or would go past\n * MAX_REQUIRED_SKILLS stays in `rest` with the reason in `problem`.\n */\nexport function addSkillTags(current: readonly string[], typed: string): AddSkillTagsResult {\n const skills = [...current];\n const keys = new Set(current.map(skillKey));\n const kept: string[] = [];\n let problem: SkillTagProblem | null = null;\n for (const part of typed.split(SEPARATORS)) {\n const tag = tidySkill(part);\n if (!tag) continue;\n if (hasControlCharacter(tag)) {\n if (!problem) problem = { kind: 'invalid' };\n kept.push(tag);\n continue;\n }\n if ([...tag].length > MAX_SKILL_LENGTH) {\n if (!problem) problem = { kind: 'tooLong', max: MAX_SKILL_LENGTH };\n kept.push(tag);\n continue;\n }\n const key = tag.toLowerCase();\n if (keys.has(key)) {\n if (!problem) {\n problem = { kind: 'duplicate', skill: skills.find((s) => skillKey(s) === key) ?? tag };\n }\n continue;\n }\n if (skills.length >= MAX_REQUIRED_SKILLS) {\n if (!problem) problem = { kind: 'tooMany', max: MAX_REQUIRED_SKILLS };\n kept.push(tag);\n continue;\n }\n keys.add(key);\n skills.push(tag);\n }\n return { skills, rest: kept.join(', '), problem };\n}\n\n/** `current` without the tag at `index`. */\nexport function removeSkillAt(current: readonly string[], index: number): string[] {\n return current.filter((_, i) => i !== index);\n}\n\n/** Same tags in the same order and spelling: what \"unchanged\" means for a save that sends only changed fields. */\nexport function sameSkillList(a: readonly string[], b: readonly string[]): boolean {\n return a.length === b.length && a.every((skill, i) => skill === b[i]);\n}\n\n/**\n * The service's own reason when a write was refused because of `requiredSkills` (BAD_USER_INPUT tagged\n * `extensions.field: requiredSkills`, e.g. \"A work item can require at most 20 skills\"), or null for any other\n * failure — which the caller shows the way it shows every other refusal.\n */\nexport function requiredSkillsRefusal(error: unknown): string | null {\n if (!CombinedGraphQLErrors.is(error)) return null;\n const refusal = error.errors.find((e) => e.extensions?.field === 'requiredSkills');\n return refusal?.message || null;\n}\n\n// ─── Coverage (planmagnetSkillCoverage) ──────────────────────────────────────\n\nexport type SkillCoverageStatus = 'UNCOVERED' | 'UNKNOWN' | 'COVERED' | 'NO_DEMAND';\n\n/** UNCOVERED first, then UNKNOWN, COVERED and NO_DEMAND — the order the service documents. */\nconst STATUS_ORDER: Record<SkillCoverageStatus, number> = {\n UNCOVERED: 0,\n UNKNOWN: 1,\n COVERED: 2,\n NO_DEMAND: 3,\n};\n\nfunction statusRank(status: string): number {\n return (STATUS_ORDER as Record<string, number>)[status] ?? Object.keys(STATUS_ORDER).length;\n}\n\n/**\n * Rows in status order (UNCOVERED first), keeping the service's order within a status (most open work first, then\n * by skill). The service already sends them this way; sorting here keeps \"uncovered first\" true on screen even if\n * that changes. A status this client does not know yet goes last rather than disappearing.\n */\nexport function orderCoverage<T extends { status: string }>(rows: readonly T[]): T[] {\n return rows\n .map((row, index) => ({ row, index }))\n .sort((a, b) => statusRank(a.row.status) - statusRank(b.row.status) || a.index - b.index)\n .map(({ row }) => row);\n}\n\n/** How many rows have each status. */\nexport function coverageCounts(\n rows: readonly { status: string }[]\n): Record<SkillCoverageStatus, number> {\n const counts: Record<SkillCoverageStatus, number> = {\n UNCOVERED: 0,\n UNKNOWN: 0,\n COVERED: 0,\n NO_DEMAND: 0,\n };\n for (const row of rows) {\n if (row.status in counts) counts[row.status as SkillCoverageStatus] += 1;\n }\n return counts;\n}\n\nexport type CoverageBadgeVariant = 'destructive' | 'warning' | 'success' | 'secondary' | 'outline';\n\n/**\n * Severity, not identity: uncovered demand is the problem, unknown needs a look, covered is fine, no demand is\n * neutral.\n */\nexport function coverageBadgeVariant(status: string): CoverageBadgeVariant {\n switch (status) {\n case 'UNCOVERED':\n return 'destructive';\n case 'UNKNOWN':\n return 'warning';\n case 'COVERED':\n return 'success';\n case 'NO_DEMAND':\n return 'secondary';\n default:\n return 'outline';\n }\n}\n\n/**\n * The filter for planmagnetSkillCoverage with only the ids actually chosen: an empty id is left out (the service\n * answers NOT_FOUND for an id that is not a live project / team / sprint of the workspace).\n */\nexport function coverageFilter(filter: {\n projectId?: string | null;\n teamId?: string | null;\n sprintId?: string | null;\n}): { projectId?: string; teamId?: string; sprintId?: string } {\n const out: { projectId?: string; teamId?: string; sprintId?: string } = {};\n if (filter.projectId) out.projectId = filter.projectId;\n if (filter.teamId) out.teamId = filter.teamId;\n if (filter.sprintId) out.sprintId = filter.sprintId;\n return out;\n}\n\n// ─── CrewFoundry links ───────────────────────────────────────────────────────\n\n/**\n * CrewFoundry's app in the same environment as this page: alpha shells (`alphaapp.*`) link to alpha, the local dev\n * proxy (`*.local.burdenoff.com`, localhost) to the local CrewFoundry shell, everything else to production.\n */\nexport function crewFoundryOrigin(hostname: string): string {\n const host = hostname.toLowerCase();\n if (host.startsWith('alphaapp.') || host.startsWith('alpha.')) {\n return 'https://alphaapp.crewfoundry.com';\n }\n if (\n host === 'localhost' ||\n host === '127.0.0.1' ||\n host.endsWith('.localhost') ||\n host.endsWith('.local.burdenoff.com')\n ) {\n return 'https://crewfoundry.local.burdenoff.com';\n }\n return 'https://app.crewfoundry.com';\n}\n\n/**\n * A CrewFoundry employee's profile, where their skills are edited (CrewFoundry mounts its pages under\n * /crewfoundry).\n */\nexport function crewFoundryEmployeeUrl(employeeId: string, hostname: string): string {\n return `${crewFoundryOrigin(hostname)}/crewfoundry/hr/employee-profile-page?id=${encodeURIComponent(employeeId)}`;\n}\n\n/** CrewFoundry's employee list. */\nexport function crewFoundryEmployeesUrl(hostname: string): string {\n return `${crewFoundryOrigin(hostname)}/crewfoundry/hr/employees-page`;\n}\n"],"mappings":"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"}
@@ -0,0 +1,28 @@
1
+ export interface RequiredSkillsDraft {
2
+ /** What the editor shows: the user's edits, else the saved list. */
3
+ value: string[];
4
+ setValue: (next: string[]) => void;
5
+ /** The saved list, or null until it has loaded (or when it could not be loaded). */
6
+ saved: string[] | null;
7
+ loading: boolean;
8
+ /** Why the saved list could not be loaded, or null. */
9
+ loadError: string | null;
10
+ /**
11
+ * The list to send as `requiredSkills`, or undefined when it is unchanged — or not loaded: a list the user never
12
+ * saw is never sent, so a failed read can't clear the item's skills.
13
+ */
14
+ changedValue: string[] | undefined;
15
+ /** The service's refusal of the last save, shown on the field until the list changes. */
16
+ serverError: string | null;
17
+ setServerError: (message: string | null) => void;
18
+ /** Drop the user's edits so the editor follows the saved list again (after a successful save). */
19
+ reset: () => void;
20
+ }
21
+ /**
22
+ * The edit page's required-skills state (ANR-07, ANR-28): the saved list comes from the block's own query
23
+ * (`WorkItemRequiredSkills`, the PRM-01 rule — a field the gateway does not serve must not blank the whole edit
24
+ * page), and the user's edits are kept apart from it, so the save sends `requiredSkills` only when it changed.
25
+ * Add 'WorkItemRequiredSkills' to the save's refetchQueries so `saved` is fresh before `reset()`.
26
+ */
27
+ export declare function useRequiredSkillsDraft(workItemId: string, skip?: boolean): RequiredSkillsDraft;
28
+ //# sourceMappingURL=useRequiredSkillsDraft.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useRequiredSkillsDraft.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/skills/useRequiredSkillsDraft.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,mBAAmB;IAClC,oEAAoE;IACpE,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;IACnC,oFAAoF;IACpF,KAAK,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;IACvB,OAAO,EAAE,OAAO,CAAC;IACjB,uDAAuD;IACvD,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB;;;OAGG;IACH,YAAY,EAAE,MAAM,EAAE,GAAG,SAAS,CAAC;IACnC,yFAAyF;IACzF,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,cAAc,EAAE,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAC;IACjD,kGAAkG;IAClG,KAAK,EAAE,MAAM,IAAI,CAAC;CACnB;AAED;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,UAAQ,GAAG,mBAAmB,CA2B5F"}
@@ -0,0 +1,30 @@
1
+ import { sameSkillList as e } from "./skillTags.js";
2
+ import { WORK_ITEM_REQUIRED_SKILLS as t } from "./skillOperations.js";
3
+ import { useState as n } from "react";
4
+ import { useQuery as r } from "@apollo/client/react";
5
+ //#region src/planmagnet/skills/useRequiredSkillsDraft.ts
6
+ function i(i, a = !1) {
7
+ let { data: o, loading: s, error: c } = r(t, {
8
+ variables: { id: i },
9
+ skip: a || !i
10
+ }), l = o?.planmagnetWorkItem?.requiredSkills ?? null, [u, d] = n(null), [f, p] = n(null);
11
+ return {
12
+ value: u ?? l ?? [],
13
+ setValue: (e) => {
14
+ d(e), p(null);
15
+ },
16
+ saved: l,
17
+ loading: s && !o,
18
+ loadError: l === null && c ? c.message : null,
19
+ changedValue: l !== null && u !== null && !e(u, l) ? u : void 0,
20
+ serverError: f,
21
+ setServerError: p,
22
+ reset: () => {
23
+ d(null), p(null);
24
+ }
25
+ };
26
+ }
27
+ //#endregion
28
+ export { i as useRequiredSkillsDraft };
29
+
30
+ //# sourceMappingURL=useRequiredSkillsDraft.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useRequiredSkillsDraft.js","names":[],"sources":["../../../src/planmagnet/skills/useRequiredSkillsDraft.ts"],"sourcesContent":["import { useState } from 'react';\nimport { useQuery } from '@apollo/client/react';\nimport { WORK_ITEM_REQUIRED_SKILLS, type WorkItemRequiredSkillsData } from './skillOperations';\nimport { sameSkillList } from './skillTags';\n\nexport interface RequiredSkillsDraft {\n /** What the editor shows: the user's edits, else the saved list. */\n value: string[];\n setValue: (next: string[]) => void;\n /** The saved list, or null until it has loaded (or when it could not be loaded). */\n saved: string[] | null;\n loading: boolean;\n /** Why the saved list could not be loaded, or null. */\n loadError: string | null;\n /**\n * The list to send as `requiredSkills`, or undefined when it is unchanged — or not loaded: a list the user never\n * saw is never sent, so a failed read can't clear the item's skills.\n */\n changedValue: string[] | undefined;\n /** The service's refusal of the last save, shown on the field until the list changes. */\n serverError: string | null;\n setServerError: (message: string | null) => void;\n /** Drop the user's edits so the editor follows the saved list again (after a successful save). */\n reset: () => void;\n}\n\n/**\n * The edit page's required-skills state (ANR-07, ANR-28): the saved list comes from the block's own query\n * (`WorkItemRequiredSkills`, the PRM-01 rule — a field the gateway does not serve must not blank the whole edit\n * page), and the user's edits are kept apart from it, so the save sends `requiredSkills` only when it changed.\n * Add 'WorkItemRequiredSkills' to the save's refetchQueries so `saved` is fresh before `reset()`.\n */\nexport function useRequiredSkillsDraft(workItemId: string, skip = false): RequiredSkillsDraft {\n const { data, loading, error } = useQuery<WorkItemRequiredSkillsData>(WORK_ITEM_REQUIRED_SKILLS, {\n variables: { id: workItemId },\n skip: skip || !workItemId,\n });\n const saved = data?.planmagnetWorkItem?.requiredSkills ?? null;\n const [draft, setDraft] = useState<string[] | null>(null);\n const [serverError, setServerError] = useState<string | null>(null);\n\n return {\n value: draft ?? saved ?? [],\n setValue: (next) => {\n setDraft(next);\n setServerError(null);\n },\n saved,\n loading: loading && !data,\n loadError: saved === null && error ? error.message : null,\n changedValue:\n saved !== null && draft !== null && !sameSkillList(draft, saved) ? draft : undefined,\n serverError,\n setServerError,\n reset: () => {\n setDraft(null);\n setServerError(null);\n },\n };\n}\n"],"mappings":";;;;;AAgCA,SAAgB,EAAuB,GAAoB,IAAO,IAA4B;CAC5F,IAAM,EAAE,SAAM,YAAS,aAAU,EAAqC,GAA2B;EAC/F,WAAW,EAAE,IAAI,GAAY;EAC7B,MAAM,KAAQ,CAAC;EAChB,CAAC,EACI,IAAQ,GAAM,oBAAoB,kBAAkB,MACpD,CAAC,GAAO,KAAY,EAA0B,KAAK,EACnD,CAAC,GAAa,KAAkB,EAAwB,KAAK;AAEnE,QAAO;EACL,OAAO,KAAS,KAAS,EAAE;EAC3B,WAAW,MAAS;AAElB,GADA,EAAS,EAAK,EACd,EAAe,KAAK;;EAEtB;EACA,SAAS,KAAW,CAAC;EACrB,WAAW,MAAU,QAAQ,IAAQ,EAAM,UAAU;EACrD,cACE,MAAU,QAAQ,MAAU,QAAQ,CAAC,EAAc,GAAO,EAAM,GAAG,IAAQ,KAAA;EAC7E;EACA;EACA,aAAa;AAEX,GADA,EAAS,KAAK,EACd,EAAe,KAAK;;EAEvB"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@burdenoff/microfe-planmagnet",
3
- "version": "2026.1002.2",
3
+ "version": "2026.1002.3",
4
4
  "description": "Burdenoff PlanMagnet Microfrontend",
5
5
  "type": "module",
6
6
  "files": [