@burdenoff/microfe-planmagnet 2026.927.4 → 2026.927.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (141) hide show
  1. package/dist/generated/global-types.d.ts +9 -1
  2. package/dist/generated/global-types.d.ts.map +1 -1
  3. package/dist/generated/wspace-operations.d.ts +0 -12
  4. package/dist/generated/wspace-operations.d.ts.map +1 -1
  5. package/dist/planmagnet/PlanMagnetRoutes.d.ts.map +1 -1
  6. package/dist/planmagnet/PlanMagnetRoutes.js +14 -10
  7. package/dist/planmagnet/PlanMagnetRoutes.js.map +1 -1
  8. package/dist/planmagnet/admin/PlanMagnetAdminRoot.d.ts.map +1 -1
  9. package/dist/planmagnet/admin/PlanMagnetAdminRoot.js +149 -127
  10. package/dist/planmagnet/admin/PlanMagnetAdminRoot.js.map +1 -1
  11. package/dist/planmagnet/board/ProjectBoard.d.ts +2 -0
  12. package/dist/planmagnet/board/ProjectBoard.d.ts.map +1 -1
  13. package/dist/planmagnet/board/ProjectBoard.js +492 -225
  14. package/dist/planmagnet/board/ProjectBoard.js.map +1 -1
  15. package/dist/planmagnet/controls/FeatureControls.d.ts.map +1 -1
  16. package/dist/planmagnet/controls/FeatureControls.js +142 -139
  17. package/dist/planmagnet/controls/FeatureControls.js.map +1 -1
  18. package/dist/planmagnet/controls/FeatureTriage.d.ts +21 -0
  19. package/dist/planmagnet/controls/FeatureTriage.d.ts.map +1 -0
  20. package/dist/planmagnet/controls/FeatureTriage.js +398 -0
  21. package/dist/planmagnet/controls/FeatureTriage.js.map +1 -0
  22. package/dist/planmagnet/controls/MilestoneControls.d.ts.map +1 -1
  23. package/dist/planmagnet/controls/MilestoneControls.js +76 -73
  24. package/dist/planmagnet/controls/MilestoneControls.js.map +1 -1
  25. package/dist/planmagnet/controls/ProjectControls.d.ts.map +1 -1
  26. package/dist/planmagnet/controls/ProjectControls.js +47 -44
  27. package/dist/planmagnet/controls/ProjectControls.js.map +1 -1
  28. package/dist/planmagnet/controls/ReleaseControls.d.ts.map +1 -1
  29. package/dist/planmagnet/controls/ReleaseControls.js +47 -44
  30. package/dist/planmagnet/controls/ReleaseControls.js.map +1 -1
  31. package/dist/planmagnet/controls/WorkItemControls.d.ts.map +1 -1
  32. package/dist/planmagnet/controls/WorkItemControls.js +68 -65
  33. package/dist/planmagnet/controls/WorkItemControls.js.map +1 -1
  34. package/dist/planmagnet/hooks/useCounts.d.ts +39 -0
  35. package/dist/planmagnet/hooks/useCounts.d.ts.map +1 -0
  36. package/dist/planmagnet/hooks/useCounts.js +209 -0
  37. package/dist/planmagnet/hooks/useCounts.js.map +1 -0
  38. package/dist/planmagnet/import/exportColumns.d.ts +52 -0
  39. package/dist/planmagnet/import/exportColumns.d.ts.map +1 -0
  40. package/dist/planmagnet/import/exportColumns.js +81 -0
  41. package/dist/planmagnet/import/exportColumns.js.map +1 -0
  42. package/dist/planmagnet/pages/AnalyticsAnalyticsPagePage.d.ts.map +1 -1
  43. package/dist/planmagnet/pages/AnalyticsAnalyticsPagePage.js +255 -242
  44. package/dist/planmagnet/pages/AnalyticsAnalyticsPagePage.js.map +1 -1
  45. package/dist/planmagnet/pages/DashboardDashboardPagePage.d.ts.map +1 -1
  46. package/dist/planmagnet/pages/DashboardDashboardPagePage.js +199 -160
  47. package/dist/planmagnet/pages/DashboardDashboardPagePage.js.map +1 -1
  48. package/dist/planmagnet/pages/DocsDocDetailPagePage.d.ts.map +1 -1
  49. package/dist/planmagnet/pages/DocsDocDetailPagePage.js +147 -144
  50. package/dist/planmagnet/pages/DocsDocDetailPagePage.js.map +1 -1
  51. package/dist/planmagnet/pages/GoalsGoalsPagePage.d.ts.map +1 -1
  52. package/dist/planmagnet/pages/GoalsGoalsPagePage.js +2 -7
  53. package/dist/planmagnet/pages/GoalsGoalsPagePage.js.map +1 -1
  54. package/dist/planmagnet/pages/GoalsObjectiveDetailPagePage.d.ts.map +1 -1
  55. package/dist/planmagnet/pages/GoalsObjectiveDetailPagePage.js +173 -173
  56. package/dist/planmagnet/pages/GoalsObjectiveDetailPagePage.js.map +1 -1
  57. package/dist/planmagnet/pages/ProductsEditProductPagePage.d.ts.map +1 -1
  58. package/dist/planmagnet/pages/ProductsEditProductPagePage.js +120 -117
  59. package/dist/planmagnet/pages/ProductsEditProductPagePage.js.map +1 -1
  60. package/dist/planmagnet/pages/ProductsFeatureAnalyticsPagePage.d.ts.map +1 -1
  61. package/dist/planmagnet/pages/ProductsFeatureAnalyticsPagePage.js +97 -117
  62. package/dist/planmagnet/pages/ProductsFeatureAnalyticsPagePage.js.map +1 -1
  63. package/dist/planmagnet/pages/ProductsFeatureDetailPagePage.d.ts.map +1 -1
  64. package/dist/planmagnet/pages/ProductsFeatureDetailPagePage.js +168 -103
  65. package/dist/planmagnet/pages/ProductsFeatureDetailPagePage.js.map +1 -1
  66. package/dist/planmagnet/pages/ProductsFeaturesPagePage.d.ts.map +1 -1
  67. package/dist/planmagnet/pages/ProductsFeaturesPagePage.js +117 -103
  68. package/dist/planmagnet/pages/ProductsFeaturesPagePage.js.map +1 -1
  69. package/dist/planmagnet/pages/ProductsProductDetailPagePage.d.ts.map +1 -1
  70. package/dist/planmagnet/pages/ProductsProductDetailPagePage.js +4 -8
  71. package/dist/planmagnet/pages/ProductsProductDetailPagePage.js.map +1 -1
  72. package/dist/planmagnet/pages/ProductsProductsPagePage.d.ts.map +1 -1
  73. package/dist/planmagnet/pages/ProductsProductsPagePage.js +85 -90
  74. package/dist/planmagnet/pages/ProductsProductsPagePage.js.map +1 -1
  75. package/dist/planmagnet/pages/ProductsReleaseDetailPagePage.d.ts.map +1 -1
  76. package/dist/planmagnet/pages/ProductsReleaseDetailPagePage.js +12 -2
  77. package/dist/planmagnet/pages/ProductsReleaseDetailPagePage.js.map +1 -1
  78. package/dist/planmagnet/pages/ProductsReleasesPagePage.d.ts.map +1 -1
  79. package/dist/planmagnet/pages/ProductsReleasesPagePage.js +2 -2
  80. package/dist/planmagnet/pages/ProductsReleasesPagePage.js.map +1 -1
  81. package/dist/planmagnet/pages/ProductsRoadmapAnalyticsPagePage.d.ts.map +1 -1
  82. package/dist/planmagnet/pages/ProductsRoadmapAnalyticsPagePage.js +48 -71
  83. package/dist/planmagnet/pages/ProductsRoadmapAnalyticsPagePage.js.map +1 -1
  84. package/dist/planmagnet/pages/ProductsRoadmapsPagePage.d.ts.map +1 -1
  85. package/dist/planmagnet/pages/ProductsRoadmapsPagePage.js +17 -13
  86. package/dist/planmagnet/pages/ProductsRoadmapsPagePage.js.map +1 -1
  87. package/dist/planmagnet/pages/ProjectsActiveProjectsPagePage.d.ts.map +1 -1
  88. package/dist/planmagnet/pages/ProjectsActiveProjectsPagePage.js +1 -1
  89. package/dist/planmagnet/pages/ProjectsActiveProjectsPagePage.js.map +1 -1
  90. package/dist/planmagnet/pages/ProjectsEditProjectPagePage.js +6 -6
  91. package/dist/planmagnet/pages/ProjectsPlanningDetailPagePage.d.ts.map +1 -1
  92. package/dist/planmagnet/pages/ProjectsPlanningDetailPagePage.js +11 -8
  93. package/dist/planmagnet/pages/ProjectsPlanningDetailPagePage.js.map +1 -1
  94. package/dist/planmagnet/pages/ProjectsPlanningEditPagePage.js +4 -4
  95. package/dist/planmagnet/pages/ProjectsPlanningPagePage.d.ts.map +1 -1
  96. package/dist/planmagnet/pages/ProjectsPlanningPagePage.js +1 -1
  97. package/dist/planmagnet/pages/ProjectsPlanningPagePage.js.map +1 -1
  98. package/dist/planmagnet/pages/ProjectsProjectDetailPagePage.d.ts.map +1 -1
  99. package/dist/planmagnet/pages/ProjectsProjectDetailPagePage.js +152 -139
  100. package/dist/planmagnet/pages/ProjectsProjectDetailPagePage.js.map +1 -1
  101. package/dist/planmagnet/pages/ProjectsProjectsPagePage.d.ts.map +1 -1
  102. package/dist/planmagnet/pages/ProjectsProjectsPagePage.js +75 -74
  103. package/dist/planmagnet/pages/ProjectsProjectsPagePage.js.map +1 -1
  104. package/dist/planmagnet/pages/ReportsReportsPagePage.d.ts +7 -0
  105. package/dist/planmagnet/pages/ReportsReportsPagePage.d.ts.map +1 -1
  106. package/dist/planmagnet/pages/ReportsReportsPagePage.js +39 -201
  107. package/dist/planmagnet/pages/ReportsReportsPagePage.js.map +1 -1
  108. package/dist/planmagnet/pages/SprintsStartSprintPagePage.d.ts.map +1 -1
  109. package/dist/planmagnet/pages/SprintsStartSprintPagePage.js +118 -99
  110. package/dist/planmagnet/pages/SprintsStartSprintPagePage.js.map +1 -1
  111. package/dist/planmagnet/pages/TasksImportPagePage.d.ts +2 -0
  112. package/dist/planmagnet/pages/TasksImportPagePage.d.ts.map +1 -0
  113. package/dist/planmagnet/pages/TasksImportPagePage.js +309 -0
  114. package/dist/planmagnet/pages/TasksImportPagePage.js.map +1 -0
  115. package/dist/planmagnet/pages/TasksTasksPagePage.d.ts.map +1 -1
  116. package/dist/planmagnet/pages/TasksTasksPagePage.js +110 -104
  117. package/dist/planmagnet/pages/TasksTasksPagePage.js.map +1 -1
  118. package/dist/planmagnet/pages/TeamTeamPagePage.d.ts.map +1 -1
  119. package/dist/planmagnet/pages/TeamTeamPagePage.js +99 -177
  120. package/dist/planmagnet/pages/TeamTeamPagePage.js.map +1 -1
  121. package/dist/planmagnet/pages/analytics/DeliveryHealthCards.d.ts.map +1 -1
  122. package/dist/planmagnet/pages/analytics/DeliveryHealthCards.js +8 -4
  123. package/dist/planmagnet/pages/analytics/DeliveryHealthCards.js.map +1 -1
  124. package/dist/planmagnet/releases/ReleaseWorkItemsCard.d.ts.map +1 -1
  125. package/dist/planmagnet/releases/ReleaseWorkItemsCard.js +10 -4
  126. package/dist/planmagnet/releases/ReleaseWorkItemsCard.js.map +1 -1
  127. package/dist/planmagnet/roadmaps/RoadmapControls.d.ts.map +1 -1
  128. package/dist/planmagnet/roadmaps/RoadmapControls.js +151 -138
  129. package/dist/planmagnet/roadmaps/RoadmapControls.js.map +1 -1
  130. package/dist/planmagnet/sprints/SprintControls.d.ts.map +1 -1
  131. package/dist/planmagnet/sprints/SprintControls.js +32 -30
  132. package/dist/planmagnet/sprints/SprintControls.js.map +1 -1
  133. package/dist/planmagnet/utils/mutationResult.d.ts +10 -0
  134. package/dist/planmagnet/utils/mutationResult.d.ts.map +1 -1
  135. package/dist/planmagnet/utils/mutationResult.js +2 -1
  136. package/dist/planmagnet/utils/mutationResult.js.map +1 -1
  137. package/dist/planmagnet/utils/rowNavigation.d.ts +2 -0
  138. package/dist/planmagnet/utils/rowNavigation.d.ts.map +1 -1
  139. package/dist/planmagnet/utils/rowNavigation.js +9 -8
  140. package/dist/planmagnet/utils/rowNavigation.js.map +1 -1
  141. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"DocsDocDetailPagePage.js","names":[],"sources":["../../../src/planmagnet/pages/DocsDocDetailPagePage.tsx"],"sourcesContent":["import { useState } from 'react';\nimport { useSearchParams } from 'react-router';\nimport { gql } from '@apollo/client';\nimport { useApolloClient, useMutation, useQuery } from '@apollo/client/react';\nimport {\n AlertDialog,\n AlertDialogAction,\n AlertDialogCancel,\n AlertDialogContent,\n AlertDialogDescription,\n AlertDialogFooter,\n AlertDialogHeader,\n AlertDialogTitle,\n AlertDialogTrigger,\n Button,\n Card,\n CardContent,\n CardHeader,\n CardTitle,\n Input,\n} from '@burdenoff/fe-libs/ui';\nimport { Markdown } from '@burdenoff/fe-libs/shared/components';\nimport { clearGraphqlFetchResponseCache } from '@burdenoff/fe-libs/shared/graphql';\nimport { ArrowLeft, History, Link2, X } from 'lucide-react';\nimport { WorkItemSearchSelect } from '../controls/Pickers';\nimport { MarkdownEditor } from '#src/components/MarkdownEditor';\nimport { usePlanMagnetNavigate } from '../providers/PlanMagnetProvider';\nimport { PersonName } from '../people/People';\nimport { throwOnError } from '../utils/mutationResult';\nimport { DOC_STATUSES, DocStatusBadge, type DocStatus } from '../docs/DocStatusBadge';\n\n/**\n * One doc (P1.4): read it, edit it with a change note, publish or archive it,\n * browse its history and restore an older version, see and unlink the work\n * items it is attached to, and delete it.\n *\n * Every write carries `expectedVersion` — the version on screen — so a save\n * over someone else's newer edit is refused with the reason, and the editor\n * keeps the unsaved text rather than overwriting theirs.\n */\n\nconst DOC = gql`\n query DocDetail($id: ID!) {\n planmagnetDoc(id: $id) {\n id\n title\n content\n status\n version\n createdBy\n createdAt\n updatedBy\n updatedAt\n revisions {\n id\n version\n title\n content\n changeNote\n createdBy\n createdAt\n }\n linkedWorkItems {\n id\n key\n title\n }\n }\n }\n`;\nconst UPDATE = gql`\n mutation DocUpdate($id: ID!, $input: PlanmagnetUpdateDocInput!) {\n planmagnetUpdateDoc(id: $id, input: $input) {\n id\n version\n status\n }\n }\n`;\nconst REVERT = gql`\n mutation DocRevert($id: ID!, $toVersion: Int!, $expectedVersion: Int) {\n planmagnetRevertDoc(id: $id, toVersion: $toVersion, expectedVersion: $expectedVersion) {\n id\n version\n }\n }\n`;\nconst DELETE = gql`\n mutation DocDelete($id: ID!) {\n planmagnetDeleteDoc(id: $id)\n }\n`;\nconst LINK = gql`\n mutation DocLinkWorkItem($docId: ID!, $workItemId: ID!) {\n planmagnetLinkDocToWorkItem(docId: $docId, workItemId: $workItemId) {\n id\n }\n }\n`;\n\nconst UNLINK = gql`\n mutation DocUnlinkWorkItem($docId: ID!, $workItemId: ID!) {\n planmagnetUnlinkDocFromWorkItem(docId: $docId, workItemId: $workItemId)\n }\n`;\n\ninterface Revision {\n id: string;\n version: number;\n title: string;\n content: string;\n changeNote: string | null;\n createdBy: string;\n createdAt: string;\n}\n\ninterface Doc {\n id: string;\n title: string;\n content: string;\n status: string;\n version: number;\n createdBy: string;\n createdAt: string;\n updatedBy: string | null;\n updatedAt: string;\n revisions: Revision[];\n linkedWorkItems: { id: string; key: string; title: string }[];\n}\n\nconst messageOf = (e: unknown, fallback: string) => (e instanceof Error ? e.message : fallback);\nconst when = (iso: string) => new Date(iso).toLocaleString();\n\nexport function DocsDocDetailPagePage() {\n const [params] = useSearchParams();\n const docId = params.get('docId') ?? '';\n const navigate = usePlanMagnetNavigate();\n const [editing, setEditing] = useState(false);\n const [title, setTitle] = useState('');\n const [content, setContent] = useState('');\n const [changeNote, setChangeNote] = useState('');\n const [error, setError] = useState<string | null>(null);\n const [viewing, setViewing] = useState<number | null>(null);\n // The version the editor started from — not the one on screen now, which a\n // refetch after someone else's save may already have moved past.\n const [editBase, setEditBase] = useState<number | null>(null);\n\n const {\n data,\n loading,\n error: loadError,\n } = useQuery<{ planmagnetDoc: Doc | null }>(DOC, {\n variables: { id: docId },\n skip: !docId,\n // Cached copies go stale here: a deleted doc, or someone else's newer version.\n fetchPolicy: 'cache-and-network',\n });\n const client = useApolloClient();\n const after = { refetchQueries: ['DocDetail'], awaitRefetchQueries: true };\n const [update, updateState] = useMutation(UPDATE, after);\n const [revert, revertState] = useMutation(REVERT, after);\n const [remove, removeState] = useMutation(DELETE, { refetchQueries: ['DocsList'] });\n const [unlink, unlinkState] = useMutation(UNLINK, after);\n const [link, linkState] = useMutation(LINK, {\n ...after,\n refetchQueries: ['DocDetail', 'GetWorkItemDocs'],\n });\n // BOFF-7961: link this doc to a work item from here too (it could only be linked when created from a task).\n const [linkChoice, setLinkChoice] = useState('');\n\n const doc = data?.planmagnetDoc;\n const busy = updateState.loading || revertState.loading || removeState.loading;\n\n // Refusals are panel-level: a refused write refetches, and anything the\n // refetch re-renders must not take the reason with it.\n const run = async (action: () => Promise<{ error?: unknown }>, fallback: string) => {\n setError(null);\n try {\n await throwOnError(action());\n return true;\n } catch (e) {\n setError(messageOf(e, fallback));\n // A refusal because the doc moved on: show the current version, but\n // leave the editor's text alone so nothing typed is lost.\n //\n // fe-libs replays any identical query answered in the last 15 s\n // (fetch.ts RECENT_QUERY_CACHE_TTL_MS) underneath Apollo's fetch\n // policies, and only a SUCCESSFUL mutation clears that cache. Measured in\n // prod 2026-09-25: after a refused save, even a network-only read was\n // served the pre-refusal response, so the page kept the old version and\n // never said the doc had moved on. A refused write is still a write\n // attempt: clear the cache (the public API fe-libs documents for this),\n // then read the doc; it lands in the normalized cache the page renders.\n clearGraphqlFetchResponseCache();\n await client\n .query({ query: DOC, variables: { id: docId }, fetchPolicy: 'network-only' })\n .catch(() => undefined);\n return false;\n }\n };\n\n if (!docId) {\n return <div className=\"p-6 text-sm text-muted-foreground\">No doc selected.</div>;\n }\n if (loading && !data) {\n return <div className=\"p-6 text-sm text-muted-foreground\">Loading doc…</div>;\n }\n if (loadError && !doc) {\n return (\n <p role=\"alert\" className=\"p-6 text-sm text-destructive\">\n The doc could not be loaded: {loadError.message}\n </p>\n );\n }\n if (!doc) {\n return (\n <div className=\"space-y-3 p-6\">\n <p className=\"text-sm text-muted-foreground\">\n This doc does not exist in this workspace, or it was deleted.\n </p>\n <Button size=\"sm\" variant=\"outline\" onClick={() => navigate('/docs/docs-page')}>\n Back to docs\n </Button>\n </div>\n );\n }\n\n const startEditing = () => {\n setTitle(doc.title);\n setContent(doc.content);\n setChangeNote('');\n setError(null);\n setEditBase(doc.version);\n setEditing(true);\n };\n const save = async () => {\n const saved = await run(\n () =>\n update({\n variables: {\n id: doc.id,\n input: {\n title: title.trim(),\n content,\n ...(changeNote.trim() ? { changeNote: changeNote.trim() } : {}),\n expectedVersion: editBase,\n },\n },\n }),\n 'The doc could not be saved.'\n );\n if (saved) setEditing(false);\n };\n\n const revisions = [...doc.revisions].sort((a, b) => b.version - a.version);\n const shown = viewing === null ? null : revisions.find((r) => r.version === viewing);\n\n return (\n <div className=\"space-y-6 p-6\" data-testid=\"doc-detail\">\n <Button size=\"sm\" variant=\"ghost\" onClick={() => navigate('/docs/docs-page')}>\n <ArrowLeft className=\"mr-1 h-4 w-4\" />\n All docs\n </Button>\n\n <div className=\"flex flex-wrap items-start justify-between gap-3\">\n <div className=\"min-w-0 space-y-1\">\n <h1 className=\"text-2xl font-semibold break-words\" data-testid=\"doc-title\">\n {doc.title}\n </h1>\n <p className=\"text-xs text-muted-foreground\">\n <DocStatusBadge status={doc.status} /> · version {doc.version} · updated{' '}\n {when(doc.updatedAt)} by <PersonName id={doc.updatedBy ?? doc.createdBy} />\n </p>\n </div>\n {!editing && (\n <div className=\"flex flex-wrap gap-2\">\n {DOC_STATUSES.filter((s) => s !== doc.status).map((s: DocStatus) => (\n <Button\n key={s}\n size=\"sm\"\n variant=\"outline\"\n disabled={busy}\n onClick={() =>\n run(\n () =>\n update({\n variables: {\n id: doc.id,\n input: { status: s, expectedVersion: doc.version },\n },\n }),\n 'The status could not be changed.'\n )\n }\n >\n {s === 'PUBLISHED' ? 'Publish' : s === 'ARCHIVED' ? 'Archive' : 'Back to draft'}\n </Button>\n ))}\n <Button size=\"sm\" onClick={startEditing} data-testid=\"doc-edit\">\n Edit\n </Button>\n <AlertDialog>\n <AlertDialogTrigger asChild>\n <Button size=\"sm\" variant=\"outline\" disabled={busy}>\n Delete\n </Button>\n </AlertDialogTrigger>\n <AlertDialogContent>\n <AlertDialogHeader>\n <AlertDialogTitle>Delete “{doc.title}”?</AlertDialogTitle>\n <AlertDialogDescription>\n The doc and its history are removed for everyone, and it is unlinked from{' '}\n {doc.linkedWorkItems.length} work item\n {doc.linkedWorkItems.length === 1 ? '' : 's'}.\n </AlertDialogDescription>\n </AlertDialogHeader>\n <AlertDialogFooter>\n <AlertDialogCancel>Keep it</AlertDialogCancel>\n <AlertDialogAction\n className=\"bg-destructive text-destructive-foreground hover:bg-destructive/90\"\n onClick={async () => {\n const deleted = await run(\n () => remove({ variables: { id: doc.id } }),\n 'The doc could not be deleted.'\n );\n if (deleted) navigate('/docs/docs-page');\n }}\n >\n Delete\n </AlertDialogAction>\n </AlertDialogFooter>\n </AlertDialogContent>\n </AlertDialog>\n </div>\n )}\n </div>\n\n {error && (\n <p role=\"alert\" className=\"text-sm text-destructive\" data-testid=\"doc-error\">\n {error}\n </p>\n )}\n {editing && editBase !== doc.version && (\n <p className=\"text-sm text-status-warning-text\" data-testid=\"doc-moved-on\">\n Someone saved version {doc.version} after you started editing version {editBase}. Your\n text is kept below; copy what you need, then discard and edit the latest.\n </p>\n )}\n\n {editing ? (\n <Card>\n <CardContent className=\"space-y-3 pt-6\">\n <Input\n aria-label=\"Doc title\"\n value={title}\n onChange={(e) => setTitle(e.target.value)}\n />\n <MarkdownEditor value={content} onChange={setContent} minHeight={240} />\n <Input\n aria-label=\"Change note\"\n placeholder=\"What changed? (optional, shown in the history)\"\n value={changeNote}\n onChange={(e) => setChangeNote(e.target.value)}\n />\n <div className=\"flex justify-end gap-2\">\n <Button\n size=\"sm\"\n variant=\"ghost\"\n onClick={() => {\n setEditing(false);\n setError(null);\n }}\n >\n {editBase !== doc.version ? 'Discard my edits' : 'Cancel'}\n </Button>\n <Button\n size=\"sm\"\n onClick={save}\n disabled={updateState.loading || editBase !== doc.version}\n data-testid=\"doc-save\"\n >\n {updateState.loading ? 'Saving…' : 'Save new version'}\n </Button>\n </div>\n </CardContent>\n </Card>\n ) : (\n <Card>\n <CardContent className=\"prose prose-sm max-w-none pt-6 dark:prose-invert\">\n {doc.content.trim() ? (\n <Markdown>{doc.content}</Markdown>\n ) : (\n <p className=\"text-sm text-muted-foreground\">This doc is empty.</p>\n )}\n </CardContent>\n </Card>\n )}\n\n <div className=\"grid grid-cols-1 gap-6 lg:grid-cols-2\">\n <Card data-testid=\"doc-history\">\n <CardHeader>\n <CardTitle className=\"flex items-center gap-2\">\n <History className=\"h-4 w-4\" /> History ({revisions.length})\n </CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-2\">\n <ol className=\"space-y-2\">\n {revisions.map((rev) => {\n const current = rev.version === doc.version;\n return (\n <li\n key={rev.id}\n className=\"space-y-1 rounded-md border p-2\"\n data-testid=\"doc-revision\"\n >\n <div className=\"flex flex-wrap items-center justify-between gap-2\">\n <span className=\"text-sm font-medium\">\n Version {rev.version}\n {current ? ' (current)' : ''}\n </span>\n <span className=\"flex gap-1\">\n <Button\n size=\"sm\"\n variant=\"ghost\"\n className=\"h-7 text-xs\"\n aria-expanded={viewing === rev.version}\n onClick={() => setViewing(viewing === rev.version ? null : rev.version)}\n >\n {viewing === rev.version ? 'Hide' : 'View'}\n </Button>\n {!current && !editing && (\n <AlertDialog>\n <AlertDialogTrigger asChild>\n <Button\n size=\"sm\"\n variant=\"outline\"\n className=\"h-7 text-xs\"\n disabled={busy}\n data-testid=\"doc-restore\"\n >\n Restore\n </Button>\n </AlertDialogTrigger>\n <AlertDialogContent>\n <AlertDialogHeader>\n <AlertDialogTitle>Restore version {rev.version}?</AlertDialogTitle>\n <AlertDialogDescription>\n Its title and text become version {doc.version + 1}. Nothing is\n deleted: version {doc.version} stays in the history.\n </AlertDialogDescription>\n </AlertDialogHeader>\n <AlertDialogFooter>\n <AlertDialogCancel>Cancel</AlertDialogCancel>\n <AlertDialogAction\n onClick={() =>\n run(\n () =>\n revert({\n variables: {\n id: doc.id,\n toVersion: rev.version,\n expectedVersion: doc.version,\n },\n }),\n 'The version could not be restored.'\n )\n }\n >\n Restore\n </AlertDialogAction>\n </AlertDialogFooter>\n </AlertDialogContent>\n </AlertDialog>\n )}\n </span>\n </div>\n <p className=\"text-xs text-muted-foreground\">\n {when(rev.createdAt)} · <PersonName id={rev.createdBy} />\n {rev.changeNote ? ` · ${rev.changeNote}` : ''}\n </p>\n </li>\n );\n })}\n </ol>\n {shown && (\n <div\n className=\"space-y-1 rounded-md border bg-muted/40 p-3\"\n data-testid=\"doc-revision-view\"\n >\n <p className=\"text-sm font-medium\">\n Version {shown.version}: {shown.title}\n </p>\n <div className=\"prose prose-sm max-w-none dark:prose-invert\">\n <Markdown>{shown.content}</Markdown>\n </div>\n </div>\n )}\n </CardContent>\n </Card>\n\n <Card data-testid=\"doc-linked-items\">\n <CardHeader>\n <CardTitle className=\"flex items-center gap-2\">\n <Link2 className=\"h-4 w-4\" /> Linked work items ({doc.linkedWorkItems.length})\n </CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-2\">\n {doc.linkedWorkItems.length === 0 && (\n <p className=\"text-sm text-muted-foreground\">\n Not linked to any work item yet. Pick one below, or use &ldquo;Link existing\n doc&rdquo; on the task.\n </p>\n )}\n {doc.linkedWorkItems.map((item) => (\n <div\n key={item.id}\n className=\"flex items-center justify-between gap-2 rounded-md border p-2\"\n >\n <button\n type=\"button\"\n className=\"min-w-0 truncate text-left text-sm hover:underline\"\n onClick={() =>\n navigate(`/tasks/task-detail-page?taskId=${encodeURIComponent(item.id)}`)\n }\n >\n <span className=\"text-muted-foreground\">{item.key}</span> {item.title}\n </button>\n <Button\n size=\"icon\"\n variant=\"ghost\"\n disabled={unlinkState.loading}\n aria-label={`Unlink ${item.title}`}\n onClick={() =>\n run(\n () => unlink({ variables: { docId: doc.id, workItemId: item.id } }),\n 'The work item could not be unlinked.'\n )\n }\n >\n <X className=\"h-4 w-4\" />\n </Button>\n </div>\n ))}\n <div className=\"space-y-2 pt-2\" data-testid=\"doc-link-work-item\">\n <WorkItemSearchSelect\n id=\"doc-link-work-item-select\"\n value={linkChoice}\n onChange={setLinkChoice}\n exclude={doc.linkedWorkItems.map((w) => w.id)}\n />\n <Button\n size=\"sm\"\n variant=\"outline\"\n disabled={!linkChoice || linkState.loading}\n onClick={async () => {\n const done = await run(\n () => link({ variables: { docId: doc.id, workItemId: linkChoice } }),\n 'The work item could not be linked.'\n );\n if (done) setLinkChoice('');\n }}\n >\n <Link2 className=\"mr-1 h-4 w-4\" />\n Link work item\n </Button>\n </div>\n </CardContent>\n </Card>\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"DocsDocDetailPagePage.js","names":[],"sources":["../../../src/planmagnet/pages/DocsDocDetailPagePage.tsx"],"sourcesContent":["import { useState } from 'react';\nimport { useSearchParams } from 'react-router';\nimport { gql } from '@apollo/client';\nimport { useApolloClient, useMutation, useQuery } from '@apollo/client/react';\nimport {\n AlertDialog,\n AlertDialogAction,\n AlertDialogCancel,\n AlertDialogContent,\n AlertDialogDescription,\n AlertDialogFooter,\n AlertDialogHeader,\n AlertDialogTitle,\n AlertDialogTrigger,\n Button,\n Card,\n CardContent,\n CardHeader,\n CardTitle,\n Input,\n} from '@burdenoff/fe-libs/ui';\nimport { Markdown } from '@burdenoff/fe-libs/shared/components';\nimport { clearGraphqlFetchResponseCache } from '@burdenoff/fe-libs/shared/graphql';\nimport { ArrowLeft, History, Link2, X } from 'lucide-react';\nimport { WorkItemSearchSelect } from '../controls/Pickers';\nimport { MarkdownEditor } from '#src/components/MarkdownEditor';\nimport { usePlanMagnetNavigate } from '../providers/PlanMagnetProvider';\nimport { PersonName } from '../people/People';\nimport { throwOnError, LEAVES_PAGE_CONTEXT } from '../utils/mutationResult';\nimport { DOC_STATUSES, DocStatusBadge, type DocStatus } from '../docs/DocStatusBadge';\n\n/**\n * One doc (P1.4): read it, edit it with a change note, publish or archive it,\n * browse its history and restore an older version, see and unlink the work\n * items it is attached to, and delete it.\n *\n * Every write carries `expectedVersion` — the version on screen — so a save\n * over someone else's newer edit is refused with the reason, and the editor\n * keeps the unsaved text rather than overwriting theirs.\n */\n\nconst DOC = gql`\n query DocDetail($id: ID!) {\n planmagnetDoc(id: $id) {\n id\n title\n content\n status\n version\n createdBy\n createdAt\n updatedBy\n updatedAt\n revisions {\n id\n version\n title\n content\n changeNote\n createdBy\n createdAt\n }\n linkedWorkItems {\n id\n key\n title\n }\n }\n }\n`;\nconst UPDATE = gql`\n mutation DocUpdate($id: ID!, $input: PlanmagnetUpdateDocInput!) {\n planmagnetUpdateDoc(id: $id, input: $input) {\n id\n version\n status\n }\n }\n`;\nconst REVERT = gql`\n mutation DocRevert($id: ID!, $toVersion: Int!, $expectedVersion: Int) {\n planmagnetRevertDoc(id: $id, toVersion: $toVersion, expectedVersion: $expectedVersion) {\n id\n version\n }\n }\n`;\nconst DELETE = gql`\n mutation DocDelete($id: ID!) {\n planmagnetDeleteDoc(id: $id)\n }\n`;\nconst LINK = gql`\n mutation DocLinkWorkItem($docId: ID!, $workItemId: ID!) {\n planmagnetLinkDocToWorkItem(docId: $docId, workItemId: $workItemId) {\n id\n }\n }\n`;\n\nconst UNLINK = gql`\n mutation DocUnlinkWorkItem($docId: ID!, $workItemId: ID!) {\n planmagnetUnlinkDocFromWorkItem(docId: $docId, workItemId: $workItemId)\n }\n`;\n\ninterface Revision {\n id: string;\n version: number;\n title: string;\n content: string;\n changeNote: string | null;\n createdBy: string;\n createdAt: string;\n}\n\ninterface Doc {\n id: string;\n title: string;\n content: string;\n status: string;\n version: number;\n createdBy: string;\n createdAt: string;\n updatedBy: string | null;\n updatedAt: string;\n revisions: Revision[];\n linkedWorkItems: { id: string; key: string; title: string }[];\n}\n\nconst messageOf = (e: unknown, fallback: string) => (e instanceof Error ? e.message : fallback);\nconst when = (iso: string) => new Date(iso).toLocaleString();\n\nexport function DocsDocDetailPagePage() {\n const [params] = useSearchParams();\n const docId = params.get('docId') ?? '';\n const navigate = usePlanMagnetNavigate();\n const [editing, setEditing] = useState(false);\n const [title, setTitle] = useState('');\n const [content, setContent] = useState('');\n const [changeNote, setChangeNote] = useState('');\n const [error, setError] = useState<string | null>(null);\n const [viewing, setViewing] = useState<number | null>(null);\n // The version the editor started from — not the one on screen now, which a\n // refetch after someone else's save may already have moved past.\n const [editBase, setEditBase] = useState<number | null>(null);\n\n const {\n data,\n loading,\n error: loadError,\n } = useQuery<{ planmagnetDoc: Doc | null }>(DOC, {\n variables: { id: docId },\n skip: !docId,\n // Cached copies go stale here: a deleted doc, or someone else's newer version.\n fetchPolicy: 'cache-and-network',\n });\n const client = useApolloClient();\n const after = { refetchQueries: ['DocDetail'], awaitRefetchQueries: true };\n const [update, updateState] = useMutation(UPDATE, after);\n const [revert, revertState] = useMutation(REVERT, after);\n const [remove, removeState] = useMutation(DELETE, {\n refetchQueries: ['DocsList'],\n context: LEAVES_PAGE_CONTEXT,\n });\n const [unlink, unlinkState] = useMutation(UNLINK, after);\n const [link, linkState] = useMutation(LINK, {\n ...after,\n refetchQueries: ['DocDetail', 'GetWorkItemDocs'],\n });\n // BOFF-7961: link this doc to a work item from here too (it could only be linked when created from a task).\n const [linkChoice, setLinkChoice] = useState('');\n\n const doc = data?.planmagnetDoc;\n const busy = updateState.loading || revertState.loading || removeState.loading;\n\n // Refusals are panel-level: a refused write refetches, and anything the\n // refetch re-renders must not take the reason with it.\n const run = async (action: () => Promise<{ error?: unknown }>, fallback: string) => {\n setError(null);\n try {\n await throwOnError(action());\n return true;\n } catch (e) {\n setError(messageOf(e, fallback));\n // A refusal because the doc moved on: show the current version, but\n // leave the editor's text alone so nothing typed is lost.\n //\n // fe-libs replays any identical query answered in the last 15 s\n // (fetch.ts RECENT_QUERY_CACHE_TTL_MS) underneath Apollo's fetch\n // policies, and only a SUCCESSFUL mutation clears that cache. Measured in\n // prod 2026-09-25: after a refused save, even a network-only read was\n // served the pre-refusal response, so the page kept the old version and\n // never said the doc had moved on. A refused write is still a write\n // attempt: clear the cache (the public API fe-libs documents for this),\n // then read the doc; it lands in the normalized cache the page renders.\n clearGraphqlFetchResponseCache();\n await client\n .query({ query: DOC, variables: { id: docId }, fetchPolicy: 'network-only' })\n .catch(() => undefined);\n return false;\n }\n };\n\n if (!docId) {\n return <div className=\"p-6 text-sm text-muted-foreground\">No doc selected.</div>;\n }\n if (loading && !data) {\n return <div className=\"p-6 text-sm text-muted-foreground\">Loading doc…</div>;\n }\n if (loadError && !doc) {\n return (\n <p role=\"alert\" className=\"p-6 text-sm text-destructive\">\n The doc could not be loaded: {loadError.message}\n </p>\n );\n }\n if (!doc) {\n return (\n <div className=\"space-y-3 p-6\">\n <p className=\"text-sm text-muted-foreground\">\n This doc does not exist in this workspace, or it was deleted.\n </p>\n <Button size=\"sm\" variant=\"outline\" onClick={() => navigate('/docs/docs-page')}>\n Back to docs\n </Button>\n </div>\n );\n }\n\n const startEditing = () => {\n setTitle(doc.title);\n setContent(doc.content);\n setChangeNote('');\n setError(null);\n setEditBase(doc.version);\n setEditing(true);\n };\n const save = async () => {\n const saved = await run(\n () =>\n update({\n variables: {\n id: doc.id,\n input: {\n title: title.trim(),\n content,\n ...(changeNote.trim() ? { changeNote: changeNote.trim() } : {}),\n expectedVersion: editBase,\n },\n },\n }),\n 'The doc could not be saved.'\n );\n if (saved) setEditing(false);\n };\n\n const revisions = [...doc.revisions].sort((a, b) => b.version - a.version);\n const shown = viewing === null ? null : revisions.find((r) => r.version === viewing);\n\n return (\n <div className=\"space-y-6 p-6\" data-testid=\"doc-detail\">\n <Button size=\"sm\" variant=\"ghost\" onClick={() => navigate('/docs/docs-page')}>\n <ArrowLeft className=\"mr-1 h-4 w-4\" />\n All docs\n </Button>\n\n <div className=\"flex flex-wrap items-start justify-between gap-3\">\n <div className=\"min-w-0 space-y-1\">\n <h1 className=\"text-2xl font-semibold break-words\" data-testid=\"doc-title\">\n {doc.title}\n </h1>\n <p className=\"text-xs text-muted-foreground\">\n <DocStatusBadge status={doc.status} /> · version {doc.version} · updated{' '}\n {when(doc.updatedAt)} by <PersonName id={doc.updatedBy ?? doc.createdBy} />\n </p>\n </div>\n {!editing && (\n <div className=\"flex flex-wrap gap-2\">\n {DOC_STATUSES.filter((s) => s !== doc.status).map((s: DocStatus) => (\n <Button\n key={s}\n size=\"sm\"\n variant=\"outline\"\n disabled={busy}\n onClick={() =>\n run(\n () =>\n update({\n variables: {\n id: doc.id,\n input: { status: s, expectedVersion: doc.version },\n },\n }),\n 'The status could not be changed.'\n )\n }\n >\n {s === 'PUBLISHED' ? 'Publish' : s === 'ARCHIVED' ? 'Archive' : 'Back to draft'}\n </Button>\n ))}\n <Button size=\"sm\" onClick={startEditing} data-testid=\"doc-edit\">\n Edit\n </Button>\n <AlertDialog>\n <AlertDialogTrigger asChild>\n <Button size=\"sm\" variant=\"outline\" disabled={busy}>\n Delete\n </Button>\n </AlertDialogTrigger>\n <AlertDialogContent>\n <AlertDialogHeader>\n <AlertDialogTitle>Delete “{doc.title}”?</AlertDialogTitle>\n <AlertDialogDescription>\n The doc and its history are removed for everyone, and it is unlinked from{' '}\n {doc.linkedWorkItems.length} work item\n {doc.linkedWorkItems.length === 1 ? '' : 's'}.\n </AlertDialogDescription>\n </AlertDialogHeader>\n <AlertDialogFooter>\n <AlertDialogCancel>Keep it</AlertDialogCancel>\n <AlertDialogAction\n className=\"bg-destructive text-destructive-foreground hover:bg-destructive/90\"\n onClick={async () => {\n const deleted = await run(\n () => remove({ variables: { id: doc.id } }),\n 'The doc could not be deleted.'\n );\n if (deleted) navigate('/docs/docs-page');\n }}\n >\n Delete\n </AlertDialogAction>\n </AlertDialogFooter>\n </AlertDialogContent>\n </AlertDialog>\n </div>\n )}\n </div>\n\n {error && (\n <p role=\"alert\" className=\"text-sm text-destructive\" data-testid=\"doc-error\">\n {error}\n </p>\n )}\n {editing && editBase !== doc.version && (\n <p className=\"text-sm text-status-warning-text\" data-testid=\"doc-moved-on\">\n Someone saved version {doc.version} after you started editing version {editBase}. Your\n text is kept below; copy what you need, then discard and edit the latest.\n </p>\n )}\n\n {editing ? (\n <Card>\n <CardContent className=\"space-y-3 pt-6\">\n <Input\n aria-label=\"Doc title\"\n value={title}\n onChange={(e) => setTitle(e.target.value)}\n />\n <MarkdownEditor value={content} onChange={setContent} minHeight={240} />\n <Input\n aria-label=\"Change note\"\n placeholder=\"What changed? (optional, shown in the history)\"\n value={changeNote}\n onChange={(e) => setChangeNote(e.target.value)}\n />\n <div className=\"flex justify-end gap-2\">\n <Button\n size=\"sm\"\n variant=\"ghost\"\n onClick={() => {\n setEditing(false);\n setError(null);\n }}\n >\n {editBase !== doc.version ? 'Discard my edits' : 'Cancel'}\n </Button>\n <Button\n size=\"sm\"\n onClick={save}\n disabled={updateState.loading || editBase !== doc.version}\n data-testid=\"doc-save\"\n >\n {updateState.loading ? 'Saving…' : 'Save new version'}\n </Button>\n </div>\n </CardContent>\n </Card>\n ) : (\n <Card>\n <CardContent className=\"prose prose-sm max-w-none pt-6 dark:prose-invert\">\n {doc.content.trim() ? (\n <Markdown>{doc.content}</Markdown>\n ) : (\n <p className=\"text-sm text-muted-foreground\">This doc is empty.</p>\n )}\n </CardContent>\n </Card>\n )}\n\n <div className=\"grid grid-cols-1 gap-6 lg:grid-cols-2\">\n <Card data-testid=\"doc-history\">\n <CardHeader>\n <CardTitle className=\"flex items-center gap-2\">\n <History className=\"h-4 w-4\" /> History ({revisions.length})\n </CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-2\">\n <ol className=\"space-y-2\">\n {revisions.map((rev) => {\n const current = rev.version === doc.version;\n return (\n <li\n key={rev.id}\n className=\"space-y-1 rounded-md border p-2\"\n data-testid=\"doc-revision\"\n >\n <div className=\"flex flex-wrap items-center justify-between gap-2\">\n <span className=\"text-sm font-medium\">\n Version {rev.version}\n {current ? ' (current)' : ''}\n </span>\n <span className=\"flex gap-1\">\n <Button\n size=\"sm\"\n variant=\"ghost\"\n className=\"h-7 text-xs\"\n aria-expanded={viewing === rev.version}\n onClick={() => setViewing(viewing === rev.version ? null : rev.version)}\n >\n {viewing === rev.version ? 'Hide' : 'View'}\n </Button>\n {!current && !editing && (\n <AlertDialog>\n <AlertDialogTrigger asChild>\n <Button\n size=\"sm\"\n variant=\"outline\"\n className=\"h-7 text-xs\"\n disabled={busy}\n data-testid=\"doc-restore\"\n >\n Restore\n </Button>\n </AlertDialogTrigger>\n <AlertDialogContent>\n <AlertDialogHeader>\n <AlertDialogTitle>Restore version {rev.version}?</AlertDialogTitle>\n <AlertDialogDescription>\n Its title and text become version {doc.version + 1}. Nothing is\n deleted: version {doc.version} stays in the history.\n </AlertDialogDescription>\n </AlertDialogHeader>\n <AlertDialogFooter>\n <AlertDialogCancel>Cancel</AlertDialogCancel>\n <AlertDialogAction\n onClick={() =>\n run(\n () =>\n revert({\n variables: {\n id: doc.id,\n toVersion: rev.version,\n expectedVersion: doc.version,\n },\n }),\n 'The version could not be restored.'\n )\n }\n >\n Restore\n </AlertDialogAction>\n </AlertDialogFooter>\n </AlertDialogContent>\n </AlertDialog>\n )}\n </span>\n </div>\n <p className=\"text-xs text-muted-foreground\">\n {when(rev.createdAt)} · <PersonName id={rev.createdBy} />\n {rev.changeNote ? ` · ${rev.changeNote}` : ''}\n </p>\n </li>\n );\n })}\n </ol>\n {shown && (\n <div\n className=\"space-y-1 rounded-md border bg-muted/40 p-3\"\n data-testid=\"doc-revision-view\"\n >\n <p className=\"text-sm font-medium\">\n Version {shown.version}: {shown.title}\n </p>\n <div className=\"prose prose-sm max-w-none dark:prose-invert\">\n <Markdown>{shown.content}</Markdown>\n </div>\n </div>\n )}\n </CardContent>\n </Card>\n\n <Card data-testid=\"doc-linked-items\">\n <CardHeader>\n <CardTitle className=\"flex items-center gap-2\">\n <Link2 className=\"h-4 w-4\" /> Linked work items ({doc.linkedWorkItems.length})\n </CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-2\">\n {doc.linkedWorkItems.length === 0 && (\n <p className=\"text-sm text-muted-foreground\">\n Not linked to any work item yet. Pick one below, or use &ldquo;Link existing\n doc&rdquo; on the task.\n </p>\n )}\n {doc.linkedWorkItems.map((item) => (\n <div\n key={item.id}\n className=\"flex items-center justify-between gap-2 rounded-md border p-2\"\n >\n <button\n type=\"button\"\n className=\"min-w-0 truncate text-left text-sm hover:underline\"\n onClick={() =>\n navigate(`/tasks/task-detail-page?taskId=${encodeURIComponent(item.id)}`)\n }\n >\n <span className=\"text-muted-foreground\">{item.key}</span> {item.title}\n </button>\n <Button\n size=\"icon\"\n variant=\"ghost\"\n disabled={unlinkState.loading}\n aria-label={`Unlink ${item.title}`}\n onClick={() =>\n run(\n () => unlink({ variables: { docId: doc.id, workItemId: item.id } }),\n 'The work item could not be unlinked.'\n )\n }\n >\n <X className=\"h-4 w-4\" />\n </Button>\n </div>\n ))}\n <div className=\"space-y-2 pt-2\" data-testid=\"doc-link-work-item\">\n <WorkItemSearchSelect\n id=\"doc-link-work-item-select\"\n value={linkChoice}\n onChange={setLinkChoice}\n exclude={doc.linkedWorkItems.map((w) => w.id)}\n />\n <Button\n size=\"sm\"\n variant=\"outline\"\n disabled={!linkChoice || linkState.loading}\n onClick={async () => {\n const done = await run(\n () => link({ variables: { docId: doc.id, workItemId: linkChoice } }),\n 'The work item could not be linked.'\n );\n if (done) setLinkChoice('');\n }}\n >\n <Link2 className=\"mr-1 h-4 w-4\" />\n Link work item\n </Button>\n </div>\n </CardContent>\n </Card>\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"GoalsGoalsPagePage.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/pages/GoalsGoalsPagePage.tsx"],"names":[],"mappings":"AA6DA,wBAAgB,kBAAkB,4CAwHjC"}
1
+ {"version":3,"file":"GoalsGoalsPagePage.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/pages/GoalsGoalsPagePage.tsx"],"names":[],"mappings":"AA6DA,wBAAgB,kBAAkB,4CA2HjC"}
@@ -79,14 +79,9 @@ function x() {
79
79
  children: [/* @__PURE__ */ d("div", { children: [/* @__PURE__ */ u("h1", {
80
80
  className: "text-2xl font-semibold",
81
81
  children: "Objectives & Key Results"
82
- }), /* @__PURE__ */ d("p", {
82
+ }), /* @__PURE__ */ u("p", {
83
83
  className: "text-sm text-muted-foreground",
84
- children: [
85
- O,
86
- " objective",
87
- O === 1 ? "" : "s",
88
- " in this workspace"
89
- ]
84
+ children: S === "all" ? `${O} objective${O === 1 ? "" : "s"} in this workspace` : `${O} ${S.toLowerCase()} match`
90
85
  })] }), /* @__PURE__ */ d(f, {
91
86
  size: "sm",
92
87
  onClick: () => x("/goals/create-objective-page"),
@@ -1 +1 @@
1
- {"version":3,"file":"GoalsGoalsPagePage.js","names":[],"sources":["../../../src/planmagnet/pages/GoalsGoalsPagePage.tsx"],"sourcesContent":["// eslint-disable-next-line @typescript-eslint/ban-ts-comment\n// @ts-nocheck\nimport { useState } from 'react';\nimport { gql } from '@apollo/client';\nimport { useQuery } from '@apollo/client/react';\nimport { Card, CardContent, CardHeader, CardTitle, Button } from '@burdenoff/fe-libs/ui';\nimport { Flag, Calendar, User, Plus } from 'lucide-react';\nimport { usePlanMagnet, usePlanMagnetNavigate } from '../providers/PlanMagnetProvider';\nimport { PersonName } from '../people/People';\n\n// BOFF-4441: OKR / goal tracking bounded core — real Objective + KeyResult\n// data. `progress` on both types is computed live by the backend from\n// current/target values, never fabricated on the client.\nconst GET_OBJECTIVES = gql`\n query GetObjectives($workspaceId: ID!, $filter: PlanmagnetObjectiveFilter) {\n planmagnetObjectives(workspaceId: $workspaceId, filter: $filter, pagination: { first: 100 }) {\n items {\n id\n title\n description\n ownerId\n periodLabel\n status\n progress\n endDate\n keyResults {\n id\n }\n }\n totalCount\n }\n }\n`;\n\nconst statusColor: Record<string, string> = {\n DRAFT: 'bg-gray-100 text-gray-800',\n ACTIVE: 'bg-blue-100 text-blue-800',\n COMPLETED: 'bg-green-100 text-green-800',\n CANCELLED: 'bg-red-100 text-red-800',\n};\n\nconst objectiveStatuses = ['all', 'DRAFT', 'ACTIVE', 'COMPLETED', 'CANCELLED'] as const;\n\nfunction ProgressBar({ progress }: { progress: number }) {\n const pct = Math.max(0, Math.min(100, Math.round(progress)));\n return (\n <div className=\"space-y-1\">\n <div className=\"flex justify-between text-xs text-muted-foreground\">\n <span>Progress</span>\n <span>{pct}%</span>\n </div>\n <div className=\"h-2 w-full rounded-full bg-muted overflow-hidden\">\n <div\n className=\"h-full rounded-full bg-primary transition-all\"\n style={{ width: `${pct}%` }}\n />\n </div>\n </div>\n );\n}\n\nexport function GoalsGoalsPagePage() {\n const { workspaceId } = usePlanMagnet();\n const navigate = usePlanMagnetNavigate();\n const [filter, setFilter] = useState<string>('all');\n\n const {\n data,\n loading,\n error,\n } = // eslint-disable-next-line @typescript-eslint/no-explicit-any\n useQuery<Record<string, any>>(GET_OBJECTIVES, {\n variables: {\n workspaceId: workspaceId ?? 'default',\n filter: filter !== 'all' ? { status: filter } : undefined,\n },\n skip: !workspaceId,\n });\n\n // Only before the first load: a refetch must not blank the page (it\n\n // unmounts every component on it and loses their state).\n\n if (loading && !data)\n return (\n <div className=\"flex items-center justify-center p-12\">\n <div className=\"animate-spin h-8 w-8 border-2 border-primary border-t-transparent rounded-full\" />\n </div>\n );\n if (error)\n return <div className=\"p-6 text-red-500\">Error loading objectives: {error.message}</div>;\n\n const objectives = data?.planmagnetObjectives?.items ?? [];\n const totalCount = data?.planmagnetObjectives?.totalCount ?? 0;\n\n return (\n <div className=\"space-y-6 p-6\">\n <div className=\"flex items-center justify-between\">\n <div>\n <h1 className=\"text-2xl font-semibold\">Objectives &amp; Key Results</h1>\n <p className=\"text-sm text-muted-foreground\">\n {totalCount} objective{totalCount === 1 ? '' : 's'} in this workspace\n </p>\n </div>\n <Button size=\"sm\" onClick={() => navigate('/goals/create-objective-page')}>\n <Plus className=\"mr-1 h-4 w-4\" />\n New Objective\n </Button>\n </div>\n <div className=\"flex gap-1\">\n {objectiveStatuses.map((s) => (\n <button\n key={s}\n onClick={() => setFilter(s)}\n className={`px-3 py-1.5 text-sm rounded-md capitalize ${filter === s ? 'bg-primary text-primary-foreground' : 'text-muted-foreground hover:bg-muted'}`}\n >\n {s === 'all' ? 'All' : s.toLowerCase()}\n </button>\n ))}\n </div>\n <div className=\"grid grid-cols-1 md:grid-cols-2 gap-4\">\n {objectives.map(\n (obj: {\n id: string;\n title: string;\n description: string | null;\n ownerId: string;\n periodLabel: string;\n status: string;\n progress: number;\n endDate: string | null;\n keyResults: { id: string }[];\n }) => (\n <Card\n key={obj.id}\n className=\"cursor-pointer hover:border-primary transition-colors\"\n onClick={() => navigate(`/goals/objective-detail-page?objectiveId=${obj.id}`)}\n >\n <CardHeader className=\"pb-2\">\n <div className=\"flex items-center justify-between\">\n <CardTitle className=\"text-base flex items-center gap-2\">\n <Flag className=\"h-4 w-4 text-primary\" />\n {obj.title}\n </CardTitle>\n <span\n className={`text-xs px-2 py-0.5 rounded-full capitalize ${statusColor[obj.status] ?? 'bg-gray-100'}`}\n >\n {obj.status.toLowerCase()}\n </span>\n </div>\n </CardHeader>\n <CardContent className=\"space-y-3\">\n <p className=\"text-sm text-muted-foreground\">\n {obj.description ?? 'No description'}\n </p>\n <ProgressBar progress={obj.progress} />\n <div className=\"flex gap-3 text-xs text-muted-foreground\">\n <span className=\"flex items-center gap-1\">\n <Calendar className=\"h-3 w-3\" />\n {obj.periodLabel}\n </span>\n <span className=\"flex items-center gap-1\">\n <User className=\"h-3 w-3\" />\n <PersonName id={obj.ownerId} />\n </span>\n </div>\n <div className=\"text-xs text-muted-foreground\">\n {obj.keyResults.length} key result{obj.keyResults.length === 1 ? '' : 's'}\n </div>\n </CardContent>\n </Card>\n )\n )}\n </div>\n {objectives.length === 0 && (\n <p className=\"text-center text-muted-foreground py-8\">\n No objectives found. Create one to start tracking a goal.\n </p>\n )}\n </div>\n );\n}\n"],"mappings":";;;;;;;;;AAaA,IAAM,IAAiB,CAAG;;;;;;;;;;;;;;;;;;;GAqBpB,IAAsC;CAC1C,OAAO;CACP,QAAQ;CACR,WAAW;CACX,WAAW;CACZ,EAEK,IAAoB;CAAC;CAAO;CAAS;CAAU;CAAa;CAAY;AAE9E,SAAS,EAAY,EAAE,eAAkC;CACvD,IAAM,IAAM,KAAK,IAAI,GAAG,KAAK,IAAI,KAAK,KAAK,MAAM,EAAS,CAAC,CAAC;AAC5D,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf,CACE,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,QAAD,EAAA,UAAM,YAAe,CAAA,EACrB,kBAAC,QAAD,EAAA,UAAA,CAAO,GAAI,IAAQ,EAAA,CAAA,CACf;MACN,kBAAC,OAAD;GAAK,WAAU;aACb,kBAAC,OAAD;IACE,WAAU;IACV,OAAO,EAAE,OAAO,GAAG,EAAI,IAAI;IAC3B,CAAA;GACE,CAAA,CACF;;;AAIV,SAAgB,IAAqB;CACnC,IAAM,EAAE,mBAAgB,GAAe,EACjC,IAAW,GAAuB,EAClC,CAAC,GAAQ,KAAa,EAAiB,MAAM,EAE7C,EACJ,SACA,YACA,aAEA,EAA8B,GAAgB;EAC5C,WAAW;GACT,aAAa,KAAe;GAC5B,QAAQ,MAAW,QAA6B,KAAA,IAArB,EAAE,QAAQ,GAAQ;GAC9C;EACD,MAAM,CAAC;EACR,CAAC;AAMJ,KAAI,KAAW,CAAC,EACd,QACE,kBAAC,OAAD;EAAK,WAAU;YACb,kBAAC,OAAD,EAAK,WAAU,kFAAmF,CAAA;EAC9F,CAAA;AAEV,KAAI,EACF,QAAO,kBAAC,OAAD;EAAK,WAAU;YAAf,CAAkC,8BAA2B,EAAM,QAAc;;CAE1F,IAAM,IAAa,GAAM,sBAAsB,SAAS,EAAE,EACpD,IAAa,GAAM,sBAAsB,cAAc;AAE7D,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf;GACE,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,MAAD;KAAI,WAAU;eAAyB;KAAiC,CAAA,EACxE,kBAAC,KAAD;KAAG,WAAU;eAAb;MACG;MAAW;MAAW,MAAe,IAAI,KAAK;MAAI;MACjD;OACA,EAAA,CAAA,EACN,kBAAC,GAAD;KAAQ,MAAK;KAAK,eAAe,EAAS,+BAA+B;eAAzE,CACE,kBAAC,GAAD,EAAM,WAAU,gBAAiB,CAAA,EAAA,gBAE1B;OACL;;GACN,kBAAC,OAAD;IAAK,WAAU;cACZ,EAAkB,KAAK,MACtB,kBAAC,UAAD;KAEE,eAAe,EAAU,EAAE;KAC3B,WAAW,6CAA6C,MAAW,IAAI,uCAAuC;eAE7G,MAAM,QAAQ,QAAQ,EAAE,aAAa;KAC/B,EALF,EAKE,CACT;IACE,CAAA;GACN,kBAAC,OAAD;IAAK,WAAU;cACZ,EAAW,KACT,MAWC,kBAAC,GAAD;KAEE,WAAU;KACV,eAAe,EAAS,4CAA4C,EAAI,KAAK;eAH/E,CAKE,kBAAC,GAAD;MAAY,WAAU;gBACpB,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,GAAD;QAAW,WAAU;kBAArB,CACE,kBAAC,GAAD,EAAM,WAAU,wBAAyB,CAAA,EACxC,EAAI,MACK;WACZ,kBAAC,QAAD;QACE,WAAW,+CAA+C,EAAY,EAAI,WAAW;kBAEpF,EAAI,OAAO,aAAa;QACpB,CAAA,CACH;;MACK,CAAA,EACb,kBAAC,GAAD;MAAa,WAAU;gBAAvB;OACE,kBAAC,KAAD;QAAG,WAAU;kBACV,EAAI,eAAe;QAClB,CAAA;OACJ,kBAAC,GAAD,EAAa,UAAU,EAAI,UAAY,CAAA;OACvC,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,QAAD;SAAM,WAAU;mBAAhB,CACE,kBAAC,GAAD,EAAU,WAAU,WAAY,CAAA,EAC/B,EAAI,YACA;YACP,kBAAC,QAAD;SAAM,WAAU;mBAAhB,CACE,kBAAC,GAAD,EAAM,WAAU,WAAY,CAAA,EAC5B,kBAAC,GAAD,EAAY,IAAI,EAAI,SAAW,CAAA,CAC1B;WACH;;OACN,kBAAC,OAAD;QAAK,WAAU;kBAAf;SACG,EAAI,WAAW;SAAO;SAAY,EAAI,WAAW,WAAW,IAAI,KAAK;SAClE;;OACM;QACT;OApCA,EAAI,GAoCJ,CAEV;IACG,CAAA;GACL,EAAW,WAAW,KACrB,kBAAC,KAAD;IAAG,WAAU;cAAyC;IAElD,CAAA;GAEF"}
1
+ {"version":3,"file":"GoalsGoalsPagePage.js","names":[],"sources":["../../../src/planmagnet/pages/GoalsGoalsPagePage.tsx"],"sourcesContent":["// eslint-disable-next-line @typescript-eslint/ban-ts-comment\n// @ts-nocheck\nimport { useState } from 'react';\nimport { gql } from '@apollo/client';\nimport { useQuery } from '@apollo/client/react';\nimport { Card, CardContent, CardHeader, CardTitle, Button } from '@burdenoff/fe-libs/ui';\nimport { Flag, Calendar, User, Plus } from 'lucide-react';\nimport { usePlanMagnet, usePlanMagnetNavigate } from '../providers/PlanMagnetProvider';\nimport { PersonName } from '../people/People';\n\n// BOFF-4441: OKR / goal tracking bounded core — real Objective + KeyResult\n// data. `progress` on both types is computed live by the backend from\n// current/target values, never fabricated on the client.\nconst GET_OBJECTIVES = gql`\n query GetObjectives($workspaceId: ID!, $filter: PlanmagnetObjectiveFilter) {\n planmagnetObjectives(workspaceId: $workspaceId, filter: $filter, pagination: { first: 100 }) {\n items {\n id\n title\n description\n ownerId\n periodLabel\n status\n progress\n endDate\n keyResults {\n id\n }\n }\n totalCount\n }\n }\n`;\n\nconst statusColor: Record<string, string> = {\n DRAFT: 'bg-gray-100 text-gray-800',\n ACTIVE: 'bg-blue-100 text-blue-800',\n COMPLETED: 'bg-green-100 text-green-800',\n CANCELLED: 'bg-red-100 text-red-800',\n};\n\nconst objectiveStatuses = ['all', 'DRAFT', 'ACTIVE', 'COMPLETED', 'CANCELLED'] as const;\n\nfunction ProgressBar({ progress }: { progress: number }) {\n const pct = Math.max(0, Math.min(100, Math.round(progress)));\n return (\n <div className=\"space-y-1\">\n <div className=\"flex justify-between text-xs text-muted-foreground\">\n <span>Progress</span>\n <span>{pct}%</span>\n </div>\n <div className=\"h-2 w-full rounded-full bg-muted overflow-hidden\">\n <div\n className=\"h-full rounded-full bg-primary transition-all\"\n style={{ width: `${pct}%` }}\n />\n </div>\n </div>\n );\n}\n\nexport function GoalsGoalsPagePage() {\n const { workspaceId } = usePlanMagnet();\n const navigate = usePlanMagnetNavigate();\n const [filter, setFilter] = useState<string>('all');\n\n const {\n data,\n loading,\n error,\n } = // eslint-disable-next-line @typescript-eslint/no-explicit-any\n useQuery<Record<string, any>>(GET_OBJECTIVES, {\n variables: {\n workspaceId: workspaceId ?? 'default',\n filter: filter !== 'all' ? { status: filter } : undefined,\n },\n skip: !workspaceId,\n });\n\n // Only before the first load: a refetch must not blank the page (it\n\n // unmounts every component on it and loses their state).\n\n if (loading && !data)\n return (\n <div className=\"flex items-center justify-center p-12\">\n <div className=\"animate-spin h-8 w-8 border-2 border-primary border-t-transparent rounded-full\" />\n </div>\n );\n if (error)\n return <div className=\"p-6 text-red-500\">Error loading objectives: {error.message}</div>;\n\n const objectives = data?.planmagnetObjectives?.items ?? [];\n const totalCount = data?.planmagnetObjectives?.totalCount ?? 0;\n\n return (\n <div className=\"space-y-6 p-6\">\n <div className=\"flex items-center justify-between\">\n <div>\n <h1 className=\"text-2xl font-semibold\">Objectives &amp; Key Results</h1>\n <p className=\"text-sm text-muted-foreground\">\n {/* Under a tab, the count is of what matches it (BOFF-7990). */}\n {filter === 'all'\n ? `${totalCount} objective${totalCount === 1 ? '' : 's'} in this workspace`\n : `${totalCount} ${filter.toLowerCase()} match`}\n </p>\n </div>\n <Button size=\"sm\" onClick={() => navigate('/goals/create-objective-page')}>\n <Plus className=\"mr-1 h-4 w-4\" />\n New Objective\n </Button>\n </div>\n <div className=\"flex gap-1\">\n {objectiveStatuses.map((s) => (\n <button\n key={s}\n onClick={() => setFilter(s)}\n className={`px-3 py-1.5 text-sm rounded-md capitalize ${filter === s ? 'bg-primary text-primary-foreground' : 'text-muted-foreground hover:bg-muted'}`}\n >\n {s === 'all' ? 'All' : s.toLowerCase()}\n </button>\n ))}\n </div>\n <div className=\"grid grid-cols-1 md:grid-cols-2 gap-4\">\n {objectives.map(\n (obj: {\n id: string;\n title: string;\n description: string | null;\n ownerId: string;\n periodLabel: string;\n status: string;\n progress: number;\n endDate: string | null;\n keyResults: { id: string }[];\n }) => (\n <Card\n key={obj.id}\n className=\"cursor-pointer hover:border-primary transition-colors\"\n onClick={() => navigate(`/goals/objective-detail-page?objectiveId=${obj.id}`)}\n >\n <CardHeader className=\"pb-2\">\n <div className=\"flex items-center justify-between\">\n <CardTitle className=\"text-base flex items-center gap-2\">\n <Flag className=\"h-4 w-4 text-primary\" />\n {obj.title}\n </CardTitle>\n <span\n className={`text-xs px-2 py-0.5 rounded-full capitalize ${statusColor[obj.status] ?? 'bg-gray-100'}`}\n >\n {obj.status.toLowerCase()}\n </span>\n </div>\n </CardHeader>\n <CardContent className=\"space-y-3\">\n <p className=\"text-sm text-muted-foreground\">\n {obj.description ?? 'No description'}\n </p>\n <ProgressBar progress={obj.progress} />\n <div className=\"flex gap-3 text-xs text-muted-foreground\">\n <span className=\"flex items-center gap-1\">\n <Calendar className=\"h-3 w-3\" />\n {obj.periodLabel}\n </span>\n <span className=\"flex items-center gap-1\">\n <User className=\"h-3 w-3\" />\n <PersonName id={obj.ownerId} />\n </span>\n </div>\n <div className=\"text-xs text-muted-foreground\">\n {obj.keyResults.length} key result{obj.keyResults.length === 1 ? '' : 's'}\n </div>\n </CardContent>\n </Card>\n )\n )}\n </div>\n {objectives.length === 0 && (\n <p className=\"text-center text-muted-foreground py-8\">\n No objectives found. Create one to start tracking a goal.\n </p>\n )}\n </div>\n );\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"GoalsObjectiveDetailPagePage.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/pages/GoalsObjectiveDetailPagePage.tsx"],"names":[],"mappings":"AAiQA,wBAAgB,4BAA4B,4CAwQ3C"}
1
+ {"version":3,"file":"GoalsObjectiveDetailPagePage.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/pages/GoalsObjectiveDetailPagePage.tsx"],"names":[],"mappings":"AAiQA,wBAAgB,4BAA4B,4CA0Q3C"}