@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
@@ -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, LEAVES_PAGE_CONTEXT } from '../utils/mutationResult';\nimport { DOC_STATUSES, DocStatusBadge, type DocStatus } from '../docs/DocStatusBadge';\nimport { DocComments } from '../comments/TargetComments';\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\n <DocComments docId={doc.id} />\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 { useEffect, useRef, useState } from 'react';\nimport { useSearchParams } from 'react-router';\nimport { gql } from '@apollo/client';\nimport { useApolloClient, useMutation, useQuery, useSubscription } 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, Lock, X } from 'lucide-react';\nimport { WorkItemSearchSelect } from '../controls/Pickers';\nimport { MarkdownEditor } from '#src/components/MarkdownEditor';\nimport { usePlanMagnet, usePlanMagnetNavigate } from '../providers/PlanMagnetProvider';\nimport { PersonName } from '../people/People';\nimport { throwOnError, LEAVES_PAGE_CONTEXT } from '../utils/mutationResult';\nimport { isForbidden, mutationErrorMessage } from '../utils/graphqlErrors';\nimport { DOC_STATUSES, DocStatusBadge, type DocStatus } from '../docs/DocStatusBadge';\nimport { DocComments } from '../comments/TargetComments';\nimport { usePresence } from '../hooks/usePresence';\nimport { PresenceAvatars } from '../presence/PresenceAvatars';\nimport { DocSuggestionsPanel } from '../docs/DocSuggestionsPanel';\nimport { DocVersionCompare } from '../docs/DocVersionCompare';\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 lock {\n holderId\n acquiredAt\n expiresAt\n }\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\n// CWF-18 §1: the checkout lock. Acquire/renew share one mutation (15-minute TTL either way); release is\n// holder-only; break force-releases anyone's.\nconst CHECKOUT_DOC = gql`\n mutation DocCheckout($docId: ID!) {\n planmagnetCheckoutDoc(docId: $docId) {\n id\n lock {\n holderId\n acquiredAt\n expiresAt\n }\n }\n }\n`;\nconst RELEASE_DOC = gql`\n mutation DocReleaseLock($docId: ID!) {\n planmagnetReleaseDoc(docId: $docId) {\n id\n lock {\n holderId\n acquiredAt\n expiresAt\n }\n }\n }\n`;\nconst BREAK_DOC_LOCK = gql`\n mutation DocBreakLock($docId: ID!) {\n planmagnetBreakDocLock(docId: $docId) {\n id\n lock {\n holderId\n acquiredAt\n expiresAt\n }\n }\n }\n`;\n\n// CWF-18 §2: propose an edit without checking the doc out — gated on `create`, not `update` (see the schema doc\n// comment on planmagnetSuggestDocEdit), so a reviewer who can comment but not edit can still propose one.\nconst SUGGEST_DOC_EDIT = gql`\n mutation DocSuggestEdit($input: PlanmagnetSuggestDocEditInput!) {\n planmagnetSuggestDocEdit(input: $input) {\n id\n status\n }\n }\n`;\n\n// CWF-18: live doc changes (content/title/status edits, and lock acquire/release) — a thin event that triggers a\n// re-read of DocDetail under the caller's own permissions, same discipline `useLiveRefresh` uses elsewhere.\nconst ON_DOC_UPDATED = gql`\n subscription PlanmagnetOnDocUpdatedFeed($docId: ID!, $workspaceId: ID!) {\n planmagnetOnDocUpdated(docId: $docId, workspaceId: $workspaceId) {\n id\n kind\n actorId\n changedFields\n at\n }\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 DocLock {\n holderId: string;\n acquiredAt: string;\n expiresAt: 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 lock: DocLock | null;\n revisions: Revision[];\n linkedWorkItems: { id: string; key: string; title: string }[];\n}\n\ntype DocMode = 'view' | 'edit' | 'suggest';\n\nconst messageOf = (e: unknown, fallback: string) => (e instanceof Error ? e.message : fallback);\nconst when = (iso: string) => new Date(iso).toLocaleString();\n\n/** \"You are editing — lock expires in N min\" / \"Being edited by X until HH:MM\" + Break lock for everyone else\n * (CWF-18 §1). Renders nothing on a free doc — there is nothing to report. */\nfunction DocLockBanner({\n lock,\n currentUserId,\n now,\n onBreakLock,\n breaking,\n}: {\n lock: DocLock;\n currentUserId: string | undefined;\n now: number;\n onBreakLock: () => void;\n breaking: boolean;\n}) {\n const mine = lock.holderId === currentUserId;\n const minutesLeft = Math.max(0, Math.round((new Date(lock.expiresAt).getTime() - now) / 60_000));\n const until = new Date(lock.expiresAt).toLocaleTimeString([], {\n hour: '2-digit',\n minute: '2-digit',\n });\n\n if (mine) {\n return (\n <p\n role=\"status\"\n data-testid=\"doc-lock-status\"\n className=\"flex items-center gap-1.5 text-sm text-status-warning-text\"\n >\n <Lock className=\"h-3.5 w-3.5\" aria-hidden=\"true\" />\n You are editing — lock expires in {minutesLeft} min.\n </p>\n );\n }\n return (\n <div\n data-testid=\"doc-lock-status\"\n className=\"flex flex-wrap items-center justify-between gap-2 rounded-md border border-status-warning-border bg-status-warning-subtle p-2\"\n >\n <p className=\"flex items-center gap-1.5 text-sm text-status-warning-text\">\n <Lock className=\"h-3.5 w-3.5\" aria-hidden=\"true\" />\n Being edited by <PersonName id={lock.holderId} /> until {until}.\n </p>\n <Button\n size=\"sm\"\n variant=\"outline\"\n disabled={breaking}\n onClick={onBreakLock}\n data-testid=\"doc-break-lock\"\n >\n {breaking ? 'Breaking…' : 'Break lock'}\n </Button>\n </div>\n );\n}\n\nexport function DocsDocDetailPagePage() {\n const [params] = useSearchParams();\n const docId = params.get('docId') ?? '';\n const navigate = usePlanMagnetNavigate();\n const { workspaceId, currentUser } = usePlanMagnet();\n const [mode, setMode] = useState<DocMode>('view');\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 // CWF-18 §2: a \"Suggest an edit\" draft — kept entirely separate from the edit-mode state above, since\n // suggesting never needs the checkout lock.\n const [suggestTitle, setSuggestTitle] = useState('');\n const [suggestContent, setSuggestContent] = useState('');\n const [suggestNote, setSuggestNote] = useState('');\n const [suggestError, setSuggestError] = useState<string | null>(null);\n // The version/title suggestContent/suggestTitle were actually copied from — pinned at startSuggest(), same\n // reason `editBase` is pinned at startEdit(): the live-refresh subscription below re-reads `doc` (so\n // `doc.version`/`doc.title` can advance from someone ELSE's concurrent save) while this draft sits open, and the\n // suggestion must declare the base it was really diffed against, not whatever happens to be on screen at submit\n // time. `suggestBaseTitle` matters even though only `suggestTitle` (the editable draft) is ever sent: without it,\n // \"did the user actually change the title\" gets decided by comparing the draft to a `doc.title` that may have\n // moved since the draft was copied from it, sending a spurious `proposedTitle` (the OLD title) whenever someone\n // else renamed the doc concurrently and the user never touched the title field at all.\n const [suggestBaseVersion, setSuggestBaseVersion] = useState<number | null>(null);\n const [suggestBaseTitle, setSuggestBaseTitle] = useState('');\n\n // CWF-24: who else is looking at this doc right now, and who's mid-comment.\n const presence = usePresence('DOC', docId || 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 // CWF-18 §1: the checkout lock. `checkout` doubles as the renew call (see the effect below) — acquiring and\n // renewing are the same mutation server-side.\n const [checkout, checkoutState] = useMutation(CHECKOUT_DOC, after);\n const [release] = useMutation(RELEASE_DOC, after);\n const [breakLock, breakLockState] = useMutation(BREAK_DOC_LOCK, after);\n // CWF-18 §2.\n const [suggestEdit, suggestEditState] = useMutation(SUGGEST_DOC_EDIT, {\n refetchQueries: ['DocDetail', 'DocSuggestionsPanel'],\n awaitRefetchQueries: true,\n });\n\n const doc = data?.planmagnetDoc;\n const busy = updateState.loading || revertState.loading || removeState.loading;\n\n // A ticking clock for \"lock expires in N min\", live while actively editing — 30 s is plenty for a minutes-\n // granularity display and cheap enough to never matter.\n const [now, setNow] = useState(() => Date.now());\n useEffect(() => {\n if (mode !== 'edit') return;\n const id = setInterval(() => setNow(Date.now()), 30_000);\n return () => clearInterval(id);\n }, [mode]);\n\n // Renew the 15-minute lock every 5 minutes while actively editing, so a long edit session never expires out\n // from under the editor. Best-effort: a failed renew just leaves the existing lock to run down on its own —\n // surfacing it here would interrupt someone mid-edit over something that self-heals the moment they save.\n useEffect(() => {\n if (mode !== 'edit' || !docId) return;\n const id = setInterval(() => {\n void checkout({ variables: { docId } }).catch(() => undefined);\n }, 5 * 60_000);\n return () => clearInterval(id);\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [mode, docId]);\n\n // Release on unmount (closing this view, not merely navigating within it) if this tab still holds the lock —\n // a dedicated ref-backed effect so it fires exactly once on teardown, never on an ordinary render.\n const holdingLockRef = useRef(false);\n useEffect(() => {\n holdingLockRef.current = mode === 'edit';\n }, [mode]);\n useEffect(() => {\n return () => {\n if (holdingLockRef.current && docId) {\n void client.mutate({ mutation: RELEASE_DOC, variables: { docId } }).catch(() => undefined);\n }\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n // CWF-18: live refresh — re-read the doc (content, lock, everything) the moment anyone changes it, including a\n // lock acquired/released elsewhere. Degrades silently on a subscription failure (including the expected \"no\n // SSE transport\" standalone/Storybook case — see `useLiveRefresh`'s own note); this page still works, just not\n // live, exactly the same fallback story `useLiveRefresh` documents elsewhere in this MFE.\n useSubscription(ON_DOC_UPDATED, {\n variables: { docId, workspaceId: workspaceId ?? '' },\n skip: !docId || !workspaceId,\n onData: () => {\n clearGraphqlFetchResponseCache();\n void client\n .query({ query: DOC, variables: { id: docId }, fetchPolicy: 'network-only' })\n .catch(() => undefined);\n },\n onError: (subError) => {\n console.debug('[DocsDocDetailPagePage] doc subscription unavailable:', {\n message: subError instanceof Error ? subError.message : String(subError),\n });\n },\n });\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 // CWF-18 §1: check the doc out FIRST, then enter edit mode — only once the lock is actually held. A refusal\n // (someone else holds it — CONFLICT, naming the holder) leaves the page on the read-only view with the\n // server's own reason shown, rather than opening an editor whose eventual save was never going to be safe.\n const startEdit = async () => {\n setError(null);\n try {\n await throwOnError(checkout({ variables: { docId: doc.id } }));\n } catch (e) {\n setError(\n isForbidden(e)\n ? 'Your role cannot edit this doc.'\n : mutationErrorMessage(e, 'The doc could not be checked out for editing.')\n );\n return;\n }\n setTitle(doc.title);\n setContent(doc.content);\n setChangeNote('');\n setEditBase(doc.version);\n setMode('edit');\n };\n const cancelEdit = () => {\n setMode('view');\n setError(null);\n // mutation-error: best-effort release on cancel — a failure here just leaves the advisory lock to expire on\n // its own (15-minute TTL); it must never block leaving the editor or surface as a page error.\n void release({ variables: { docId: doc.id } }).catch(() => undefined);\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) {\n setMode('view');\n // mutation-error: best-effort release after a successful save — the write itself already succeeded, and\n // showing a release refusal here would only confuse \"did my edit actually save?\".\n void release({ variables: { docId: doc.id } }).catch(() => undefined);\n }\n };\n\n const doBreakLock = async () => {\n setError(null);\n try {\n await throwOnError(breakLock({ variables: { docId: doc.id } }));\n } catch (e) {\n setError(\n isForbidden(e)\n ? 'Your role cannot force-release this lock.'\n : mutationErrorMessage(e, 'The lock could not be broken.')\n );\n }\n };\n\n // CWF-18 §2: suggest mode needs no lock at all — it never touches the doc directly.\n const startSuggest = () => {\n setSuggestTitle(doc.title);\n setSuggestContent(doc.content);\n setSuggestNote('');\n setSuggestError(null);\n setSuggestBaseVersion(doc.version);\n setSuggestBaseTitle(doc.title);\n setMode('suggest');\n };\n const cancelSuggest = () => {\n setMode('view');\n setSuggestError(null);\n };\n const submitSuggestion = async () => {\n setSuggestError(null);\n try {\n await throwOnError(\n suggestEdit({\n variables: {\n input: {\n docId: doc.id,\n // Pinned at startSuggest(), not the live `doc.version` — see `suggestBaseVersion`'s own comment.\n // The `?? doc.version` fallback only matters if this were ever called without startSuggest having\n // run first, which the `mode === 'suggest'` gating below never allows.\n baseVersion: suggestBaseVersion ?? doc.version,\n proposedContent: suggestContent,\n ...(suggestTitle.trim() !== suggestBaseTitle\n ? { proposedTitle: suggestTitle.trim() }\n : {}),\n ...(suggestNote.trim() ? { note: suggestNote.trim() } : {}),\n },\n },\n })\n );\n setMode('view');\n } catch (e) {\n setSuggestError(\n isForbidden(e)\n ? 'Your role cannot suggest edits on this doc.'\n : mutationErrorMessage(e, 'The suggestion could not be submitted.')\n );\n }\n };\n\n const lockedByOther = !!doc.lock && doc.lock.holderId !== currentUser?.id;\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 <PresenceAvatars others={presence.others} />\n </div>\n {mode === 'view' && (\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\n size=\"sm\"\n variant=\"outline\"\n onClick={startSuggest}\n disabled={suggestEditState.loading}\n data-testid=\"doc-suggest\"\n >\n Suggest an edit\n </Button>\n <Button\n size=\"sm\"\n onClick={() => void startEdit()}\n disabled={lockedByOther || checkoutState.loading}\n data-testid=\"doc-edit\"\n >\n {checkoutState.loading ? 'Checking out…' : '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 {doc.lock && (\n <DocLockBanner\n lock={doc.lock}\n currentUserId={currentUser?.id}\n now={now}\n onBreakLock={() => void doBreakLock()}\n breaking={breakLockState.loading}\n />\n )}\n\n {error && (\n <p role=\"alert\" className=\"text-sm text-destructive\" data-testid=\"doc-error\">\n {error}\n </p>\n )}\n {mode === 'edit' && 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 {mode === 'edit' && (\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 size=\"sm\" variant=\"ghost\" onClick={cancelEdit}>\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\n {mode === 'suggest' && (\n <Card data-testid=\"doc-suggest-form\">\n <CardHeader>\n <CardTitle className=\"text-base\">Suggest an edit</CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-3\">\n <p className=\"text-xs text-muted-foreground\">\n Proposed against version {suggestBaseVersion ?? doc.version}. It does not change the\n doc — someone with edit rights accepts or rejects it below.\n </p>\n <Input\n aria-label=\"Suggested title\"\n value={suggestTitle}\n onChange={(e) => setSuggestTitle(e.target.value)}\n />\n <MarkdownEditor value={suggestContent} onChange={setSuggestContent} minHeight={240} />\n <Input\n aria-label=\"Note (optional)\"\n placeholder=\"Why this change? (optional)\"\n value={suggestNote}\n onChange={(e) => setSuggestNote(e.target.value)}\n />\n {suggestError && (\n <p role=\"alert\" className=\"text-sm text-destructive\">\n {suggestError}\n </p>\n )}\n <div className=\"flex justify-end gap-2\">\n <Button size=\"sm\" variant=\"ghost\" onClick={cancelSuggest}>\n Cancel\n </Button>\n <Button\n size=\"sm\"\n onClick={() => void submitSuggestion()}\n disabled={\n suggestEditState.loading || !suggestContent.trim() || !suggestTitle.trim()\n }\n data-testid=\"doc-suggest-submit\"\n >\n {suggestEditState.loading ? 'Submitting…' : 'Submit suggestion'}\n </Button>\n </div>\n </CardContent>\n </Card>\n )}\n\n {mode === 'view' && (\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 && mode === 'view' && (\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 <DocVersionCompare docId={doc.id} versions={revisions} />\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\n <DocSuggestionsPanel\n docId={doc.id}\n docTitle={doc.title}\n docContent={doc.content}\n revisions={revisions}\n currentUserId={currentUser?.id}\n />\n\n <DocComments\n docId={doc.id}\n presence={{ typingActorIds: presence.typingActorIds, notifyTyping: presence.notifyTyping }}\n />\n </div>\n );\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"ProductsFeatureDetailPagePage.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/pages/ProductsFeatureDetailPagePage.tsx"],"names":[],"mappings":"AAoFA,wBAAgB,6BAA6B,4CAyQ5C"}
1
+ {"version":3,"file":"ProductsFeatureDetailPagePage.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/pages/ProductsFeatureDetailPagePage.tsx"],"names":[],"mappings":"AA6FA,wBAAgB,6BAA6B,4CA4Q5C"}