@burdenoff/microfe-planmagnet 2026.925.18 → 2026.926.2

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 (92) hide show
  1. package/dist/generated/wspace-types.d.ts +61 -2
  2. package/dist/generated/wspace-types.d.ts.map +1 -1
  3. package/dist/planmagnet/PlanMagnetRoot.d.ts.map +1 -1
  4. package/dist/planmagnet/PlanMagnetRoot.js +90 -79
  5. package/dist/planmagnet/PlanMagnetRoot.js.map +1 -1
  6. package/dist/planmagnet/PlanMagnetRoutes.d.ts.map +1 -1
  7. package/dist/planmagnet/PlanMagnetRoutes.js +124 -127
  8. package/dist/planmagnet/PlanMagnetRoutes.js.map +1 -1
  9. package/dist/planmagnet/create/CreateDialogs.d.ts.map +1 -1
  10. package/dist/planmagnet/create/CreateDialogs.js +149 -124
  11. package/dist/planmagnet/create/CreateDialogs.js.map +1 -1
  12. package/dist/planmagnet/pages/DocsDocDetailPagePage.d.ts.map +1 -1
  13. package/dist/planmagnet/pages/DocsDocDetailPagePage.js +1 -1
  14. package/dist/planmagnet/pages/DocsDocDetailPagePage.js.map +1 -1
  15. package/dist/planmagnet/pages/NotFoundPage.d.ts +6 -0
  16. package/dist/planmagnet/pages/NotFoundPage.d.ts.map +1 -0
  17. package/dist/planmagnet/pages/NotFoundPage.js +18 -0
  18. package/dist/planmagnet/pages/NotFoundPage.js.map +1 -0
  19. package/dist/planmagnet/pages/ProductsCreateProductPagePage.d.ts.map +1 -1
  20. package/dist/planmagnet/pages/ProductsCreateProductPagePage.js +93 -98
  21. package/dist/planmagnet/pages/ProductsCreateProductPagePage.js.map +1 -1
  22. package/dist/planmagnet/pages/ProductsCreateRoadmapPagePage.d.ts.map +1 -1
  23. package/dist/planmagnet/pages/ProductsCreateRoadmapPagePage.js +72 -79
  24. package/dist/planmagnet/pages/ProductsCreateRoadmapPagePage.js.map +1 -1
  25. package/dist/planmagnet/pages/ProductsEditProductPagePage.d.ts.map +1 -1
  26. package/dist/planmagnet/pages/ProductsEditProductPagePage.js +112 -123
  27. package/dist/planmagnet/pages/ProductsEditProductPagePage.js.map +1 -1
  28. package/dist/planmagnet/pages/ProductsNewFeaturePagePage.d.ts.map +1 -1
  29. package/dist/planmagnet/pages/ProductsNewFeaturePagePage.js +103 -87
  30. package/dist/planmagnet/pages/ProductsNewFeaturePagePage.js.map +1 -1
  31. package/dist/planmagnet/pages/ProductsProductDetailPagePage.d.ts.map +1 -1
  32. package/dist/planmagnet/pages/ProductsProductDetailPagePage.js +92 -98
  33. package/dist/planmagnet/pages/ProductsProductDetailPagePage.js.map +1 -1
  34. package/dist/planmagnet/pages/ProductsProductsPagePage.d.ts.map +1 -1
  35. package/dist/planmagnet/pages/ProductsProductsPagePage.js +88 -96
  36. package/dist/planmagnet/pages/ProductsProductsPagePage.js.map +1 -1
  37. package/dist/planmagnet/pages/ProductsRoadmapAnalyticsPagePage.d.ts.map +1 -1
  38. package/dist/planmagnet/pages/ProductsRoadmapAnalyticsPagePage.js +116 -114
  39. package/dist/planmagnet/pages/ProductsRoadmapAnalyticsPagePage.js.map +1 -1
  40. package/dist/planmagnet/pages/ProductsRoadmapDetailPagePage.d.ts.map +1 -1
  41. package/dist/planmagnet/pages/ProductsRoadmapDetailPagePage.js +82 -88
  42. package/dist/planmagnet/pages/ProductsRoadmapDetailPagePage.js.map +1 -1
  43. package/dist/planmagnet/pages/ProductsRoadmapsPagePage.d.ts.map +1 -1
  44. package/dist/planmagnet/pages/ProductsRoadmapsPagePage.js +79 -79
  45. package/dist/planmagnet/pages/ProductsRoadmapsPagePage.js.map +1 -1
  46. package/dist/planmagnet/pages/ProjectsCreateProjectPagePage.d.ts.map +1 -1
  47. package/dist/planmagnet/pages/ProjectsCreateProjectPagePage.js +111 -91
  48. package/dist/planmagnet/pages/ProjectsCreateProjectPagePage.js.map +1 -1
  49. package/dist/planmagnet/pages/ProjectsEditProjectPagePage.d.ts.map +1 -1
  50. package/dist/planmagnet/pages/ProjectsEditProjectPagePage.js +117 -97
  51. package/dist/planmagnet/pages/ProjectsEditProjectPagePage.js.map +1 -1
  52. package/dist/planmagnet/pages/ProjectsPlanningDetailPagePage.d.ts.map +1 -1
  53. package/dist/planmagnet/pages/ProjectsPlanningDetailPagePage.js +150 -154
  54. package/dist/planmagnet/pages/ProjectsPlanningDetailPagePage.js.map +1 -1
  55. package/dist/planmagnet/pages/ProjectsPlanningEditPagePage.d.ts.map +1 -1
  56. package/dist/planmagnet/pages/ProjectsPlanningEditPagePage.js +154 -117
  57. package/dist/planmagnet/pages/ProjectsPlanningEditPagePage.js.map +1 -1
  58. package/dist/planmagnet/pages/SprintsStartSprintPagePage.d.ts.map +1 -1
  59. package/dist/planmagnet/pages/SprintsStartSprintPagePage.js +75 -31
  60. package/dist/planmagnet/pages/SprintsStartSprintPagePage.js.map +1 -1
  61. package/dist/planmagnet/pages/TasksCreateTaskPagePage.d.ts.map +1 -1
  62. package/dist/planmagnet/pages/TasksCreateTaskPagePage.js +161 -115
  63. package/dist/planmagnet/pages/TasksCreateTaskPagePage.js.map +1 -1
  64. package/dist/planmagnet/pages/TasksEditTaskPagePage.d.ts.map +1 -1
  65. package/dist/planmagnet/pages/TasksEditTaskPagePage.js +117 -102
  66. package/dist/planmagnet/pages/TasksEditTaskPagePage.js.map +1 -1
  67. package/dist/planmagnet/pages/TeamInviteMemberPagePage.d.ts.map +1 -1
  68. package/dist/planmagnet/pages/TeamInviteMemberPagePage.js +147 -147
  69. package/dist/planmagnet/pages/TeamInviteMemberPagePage.js.map +1 -1
  70. package/dist/planmagnet/pages/TeamTeamPagePage.d.ts.map +1 -1
  71. package/dist/planmagnet/pages/TeamTeamPagePage.js +77 -67
  72. package/dist/planmagnet/pages/TeamTeamPagePage.js.map +1 -1
  73. package/dist/planmagnet/roadmaps/planningFocus.d.ts +23 -0
  74. package/dist/planmagnet/roadmaps/planningFocus.d.ts.map +1 -0
  75. package/dist/planmagnet/roadmaps/planningFocus.js +76 -0
  76. package/dist/planmagnet/roadmaps/planningFocus.js.map +1 -0
  77. package/dist/planmagnet/sprints/SprintControls.d.ts.map +1 -1
  78. package/dist/planmagnet/sprints/SprintControls.js +48 -29
  79. package/dist/planmagnet/sprints/SprintControls.js.map +1 -1
  80. package/dist/planmagnet/sprints/SprintDates.d.ts +37 -0
  81. package/dist/planmagnet/sprints/SprintDates.d.ts.map +1 -0
  82. package/dist/planmagnet/sprints/SprintDates.js +114 -0
  83. package/dist/planmagnet/sprints/SprintDates.js.map +1 -0
  84. package/dist/planmagnet/utils/email.d.ts +9 -0
  85. package/dist/planmagnet/utils/email.d.ts.map +1 -0
  86. package/dist/planmagnet/utils/email.js +15 -0
  87. package/dist/planmagnet/utils/email.js.map +1 -0
  88. package/dist/planmagnet/utils/formValidation.d.ts +18 -0
  89. package/dist/planmagnet/utils/formValidation.d.ts.map +1 -0
  90. package/dist/planmagnet/utils/formValidation.js +17 -0
  91. package/dist/planmagnet/utils/formValidation.js.map +1 -0
  92. 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 { 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 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\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. Link docs from a task&apos;s “Linked Docs” panel.\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 </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 { 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 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\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. A doc created from a task&apos;s “Linked Docs” panel is\n linked to that task; linking an existing doc is not available yet.\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 </CardContent>\n </Card>\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Unknown PlanMagnet routes (H5, BOFF-7833). The old catch-all `<Navigate to="" />` resolved to the current URL,
3
+ * so an unknown path "redirected" to itself and rendered an empty content area with no way back.
4
+ */
5
+ export declare function PlanMagnetNotFoundPage(): import("react/jsx-runtime").JSX.Element;
6
+ //# sourceMappingURL=NotFoundPage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"NotFoundPage.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/pages/NotFoundPage.tsx"],"names":[],"mappings":"AAIA;;;GAGG;AACH,wBAAgB,sBAAsB,4CAcrC"}
@@ -0,0 +1,18 @@
1
+ import { usePlanMagnetNavigate as e } from "../providers/PlanMagnetProvider.js";
2
+ import { jsx as t } from "react/jsx-runtime";
3
+ import { useI18n as n } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
4
+ import { NotFoundPage as r } from "@burdenoff/fe-libs/shared/pages";
5
+ //#region src/planmagnet/pages/NotFoundPage.tsx
6
+ function i() {
7
+ let { t: i } = n(), a = e();
8
+ return /* @__PURE__ */ t(r, {
9
+ title: i("planmagnet.notFound.title", "Page not found"),
10
+ description: i("planmagnet.notFound.description", "This PlanMagnet page doesn't exist, or the link is broken."),
11
+ homeLabel: i("planmagnet.notFound.backToDashboard", "Back to Dashboard"),
12
+ onGoHome: () => a("/dashboard/dashboard-page")
13
+ });
14
+ }
15
+ //#endregion
16
+ export { i as PlanMagnetNotFoundPage };
17
+
18
+ //# sourceMappingURL=NotFoundPage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"NotFoundPage.js","names":[],"sources":["../../../src/planmagnet/pages/NotFoundPage.tsx"],"sourcesContent":["import { NotFoundPage } from '@burdenoff/fe-libs/shared/pages';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { usePlanMagnetNavigate } from '../providers/PlanMagnetProvider';\n\n/**\n * Unknown PlanMagnet routes (H5, BOFF-7833). The old catch-all `<Navigate to=\"\" />` resolved to the current URL,\n * so an unknown path \"redirected\" to itself and rendered an empty content area with no way back.\n */\nexport function PlanMagnetNotFoundPage() {\n const { t } = useI18n();\n const navigate = usePlanMagnetNavigate();\n return (\n <NotFoundPage\n title={t('planmagnet.notFound.title', 'Page not found')}\n description={t(\n 'planmagnet.notFound.description',\n \"This PlanMagnet page doesn't exist, or the link is broken.\"\n )}\n homeLabel={t('planmagnet.notFound.backToDashboard', 'Back to Dashboard')}\n onGoHome={() => navigate('/dashboard/dashboard-page')}\n />\n );\n}\n"],"mappings":";;;;;AAQA,SAAgB,IAAyB;CACvC,IAAM,EAAE,SAAM,GAAS,EACjB,IAAW,GAAuB;AACxC,QACE,kBAAC,GAAD;EACE,OAAO,EAAE,6BAA6B,iBAAiB;EACvD,aAAa,EACX,mCACA,6DACD;EACD,WAAW,EAAE,uCAAuC,oBAAoB;EACxE,gBAAgB,EAAS,4BAA4B;EACrD,CAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"ProductsCreateProductPagePage.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/pages/ProductsCreateProductPagePage.tsx"],"names":[],"mappings":"AAgCA,wBAAgB,6BAA6B,4CAqK5C"}
1
+ {"version":3,"file":"ProductsCreateProductPagePage.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/pages/ProductsCreateProductPagePage.tsx"],"names":[],"mappings":"AAwCA,wBAAgB,6BAA6B,4CAmK5C"}
@@ -1,181 +1,176 @@
1
1
  import { usePlanMagnet as e, usePlanMagnetNavigate as t } from "../providers/PlanMagnetProvider.js";
2
- import { useState as n } from "react";
3
- import { Eye as r, Loader2 as i, Package as a, Save as o, X as s } from "lucide-react";
4
- import { gql as c } from "@apollo/client";
5
- import { useMutation as l } from "@apollo/client/react";
6
- import { jsx as u, jsxs as d } from "react/jsx-runtime";
7
- import { Badge as f, Button as p, Card as m, CardContent as h, CardHeader as g, CardTitle as _, Input as v } from "@burdenoff/fe-libs/ui";
2
+ import { throwOnError as n } from "../utils/mutationResult.js";
3
+ import { PlanningFocusBadge as r, PlanningFocusSelect as i, ROADMAP_DISPLAY as a } from "../roadmaps/planningFocus.js";
4
+ import { useState as o } from "react";
5
+ import { Eye as s, Loader2 as c, Package as l, Save as u, X as d } from "lucide-react";
6
+ import { gql as f } from "@apollo/client";
7
+ import { useMutation as p } from "@apollo/client/react";
8
+ import { jsx as m, jsxs as h } from "react/jsx-runtime";
9
+ import { Badge as g, Button as _, Card as v, CardContent as y, CardHeader as b, CardTitle as x, Input as S } from "@burdenoff/fe-libs/ui";
8
10
  //#region src/planmagnet/pages/ProductsCreateProductPagePage.tsx
9
- var y = c`
11
+ var C = f`
10
12
  mutation CreateRoadmap($input: PlanmagnetCreateRoadmapInput!) {
11
13
  planmagnetCreateRoadmap(input: $input) {
12
14
  id
13
15
  name
14
16
  description
15
17
  type
18
+ planningFocus
16
19
  isPublic
17
20
  ownerId
18
21
  createdAt
19
22
  }
20
23
  }
21
24
  `;
22
- function b() {
23
- let { workspaceId: c, currentUser: b } = e(), x = t(), [S, C] = n({
25
+ function w() {
26
+ let { workspaceId: f, currentUser: w } = e(), T = t(), [E, D] = o({
24
27
  name: "",
25
28
  description: "",
26
- type: "TIMELINE",
29
+ planningFocus: null,
27
30
  isPublic: !1
28
- }), [w, { loading: T, error: E }] = l(y, {
29
- refetchQueries: ["ListRoadmaps"],
30
- onCompleted: () => {
31
- x("/products/products-page");
32
- }
33
- });
34
- return /* @__PURE__ */ d("div", {
31
+ }), [O, k] = o(null), [A, { loading: j }] = p(C, { refetchQueries: ["ListRoadmaps"] });
32
+ return /* @__PURE__ */ h("div", {
35
33
  className: "space-y-6 p-6",
36
34
  children: [
37
- /* @__PURE__ */ d("div", {
35
+ /* @__PURE__ */ h("div", {
38
36
  className: "flex items-center justify-between",
39
- children: [/* @__PURE__ */ d("div", { children: [/* @__PURE__ */ u("h1", {
37
+ children: [/* @__PURE__ */ h("div", { children: [/* @__PURE__ */ m("h1", {
40
38
  className: "text-2xl font-semibold",
41
39
  children: "Create Product"
42
- }), /* @__PURE__ */ u("p", {
40
+ }), /* @__PURE__ */ m("p", {
43
41
  className: "text-sm text-muted-foreground",
44
42
  children: "Define a new product with its vision and strategy."
45
- })] }), /* @__PURE__ */ d("div", {
43
+ })] }), /* @__PURE__ */ h("div", {
46
44
  className: "flex items-center gap-2",
47
- children: [/* @__PURE__ */ d(p, {
45
+ children: [/* @__PURE__ */ h(_, {
48
46
  variant: "outline",
49
47
  size: "sm",
50
- onClick: () => x("/products/products-page"),
51
- children: [/* @__PURE__ */ u(s, { className: "mr-1 h-4 w-4" }), "Cancel"]
52
- }), /* @__PURE__ */ d(p, {
48
+ onClick: () => T("/products/products-page"),
49
+ children: [/* @__PURE__ */ m(d, { className: "mr-1 h-4 w-4" }), "Cancel"]
50
+ }), /* @__PURE__ */ h(_, {
53
51
  size: "sm",
54
- onClick: () => {
55
- S.name.trim() && w({ variables: { input: {
56
- name: S.name,
57
- description: S.description || void 0,
58
- type: S.type,
59
- isPublic: S.isPublic,
60
- ownerId: b?.id ?? ""
61
- } } });
52
+ onClick: async () => {
53
+ if (E.name.trim()) {
54
+ k(null);
55
+ try {
56
+ await n(A({ variables: { input: {
57
+ name: E.name,
58
+ description: E.description || void 0,
59
+ type: a,
60
+ ...E.planningFocus ? { planningFocus: E.planningFocus } : {},
61
+ isPublic: E.isPublic,
62
+ ownerId: w?.id ?? ""
63
+ } } })), T("/products/products-page");
64
+ } catch (e) {
65
+ k(e instanceof Error ? e.message : "The product could not be created.");
66
+ }
67
+ }
62
68
  },
63
- disabled: T || !S.name.trim(),
64
- children: [T ? /* @__PURE__ */ u(i, { className: "mr-1 h-4 w-4 animate-spin" }) : /* @__PURE__ */ u(o, { className: "mr-1 h-4 w-4" }), "Create Product"]
69
+ disabled: j || !E.name.trim(),
70
+ children: [j ? /* @__PURE__ */ m(c, { className: "mr-1 h-4 w-4 animate-spin" }) : /* @__PURE__ */ m(u, { className: "mr-1 h-4 w-4" }), "Create Product"]
65
71
  })]
66
72
  })]
67
73
  }),
68
- E && /* @__PURE__ */ d("div", {
74
+ O && /* @__PURE__ */ h("div", {
75
+ role: "alert",
69
76
  className: "p-3 rounded-md bg-destructive/10 text-destructive text-sm",
70
- children: ["Failed to create product: ", E.message]
77
+ children: ["Failed to create product: ", O]
71
78
  }),
72
- /* @__PURE__ */ d("div", {
79
+ /* @__PURE__ */ h("div", {
73
80
  className: "grid grid-cols-1 lg:grid-cols-3 gap-6",
74
- children: [/* @__PURE__ */ u("div", {
81
+ children: [/* @__PURE__ */ m("div", {
75
82
  className: "lg:col-span-2 space-y-6",
76
- children: /* @__PURE__ */ d(m, { children: [/* @__PURE__ */ u(g, { children: /* @__PURE__ */ u(_, { children: "Basic Information" }) }), /* @__PURE__ */ d(h, {
83
+ children: /* @__PURE__ */ h(v, { children: [/* @__PURE__ */ m(b, { children: /* @__PURE__ */ m(x, { children: "Basic Information" }) }), /* @__PURE__ */ h(y, {
77
84
  className: "space-y-4",
78
85
  children: [
79
- /* @__PURE__ */ d("div", { children: [/* @__PURE__ */ u("label", {
86
+ /* @__PURE__ */ h("div", { children: [/* @__PURE__ */ m("label", {
80
87
  className: "text-sm font-medium",
81
88
  children: "Product Name"
82
- }), /* @__PURE__ */ u(v, {
89
+ }), /* @__PURE__ */ m(S, {
83
90
  placeholder: "e.g. PlanMagnet Analytics",
84
- value: S.name,
85
- onChange: (e) => C({
86
- ...S,
91
+ value: E.name,
92
+ onChange: (e) => D({
93
+ ...E,
87
94
  name: e.target.value
88
95
  }),
89
96
  className: "mt-1"
90
97
  })] }),
91
- /* @__PURE__ */ d("div", { children: [/* @__PURE__ */ u("label", {
98
+ /* @__PURE__ */ h("div", { children: [/* @__PURE__ */ m("label", {
92
99
  className: "text-sm font-medium",
93
100
  children: "Description"
94
- }), /* @__PURE__ */ u("textarea", {
101
+ }), /* @__PURE__ */ m("textarea", {
95
102
  className: "mt-1 w-full rounded-md border border-input bg-background px-3 py-2 text-sm min-h-[80px]",
96
103
  placeholder: "Describe what this product does...",
97
- value: S.description,
98
- onChange: (e) => C({
99
- ...S,
104
+ value: E.description,
105
+ onChange: (e) => D({
106
+ ...E,
100
107
  description: e.target.value
101
108
  })
102
109
  })] }),
103
- /* @__PURE__ */ d("div", { children: [/* @__PURE__ */ u("label", {
104
- className: "text-sm font-medium",
105
- children: "Type"
106
- }), /* @__PURE__ */ u("div", {
107
- className: "mt-1 flex gap-2",
108
- children: [
109
- "TIMELINE",
110
- "GANTT",
111
- "SWIMLANE",
112
- "CUSTOM"
113
- ].map((e) => /* @__PURE__ */ u("button", {
114
- onClick: () => C({
115
- ...S,
116
- type: e
117
- }),
118
- className: `rounded-md px-3 py-1.5 text-sm capitalize ${S.type === e ? "bg-primary text-primary-foreground" : "bg-muted text-muted-foreground"}`,
119
- children: e.toLowerCase()
120
- }, e))
121
- })] }),
122
- /* @__PURE__ */ d("div", { children: [/* @__PURE__ */ u("label", {
110
+ /* @__PURE__ */ m(i, {
111
+ value: E.planningFocus,
112
+ onChange: (e) => D({
113
+ ...E,
114
+ planningFocus: e
115
+ })
116
+ }),
117
+ /* @__PURE__ */ h("div", { children: [/* @__PURE__ */ m("label", {
123
118
  className: "text-sm font-medium",
124
119
  children: "Visibility"
125
- }), /* @__PURE__ */ u("div", {
120
+ }), /* @__PURE__ */ m("div", {
126
121
  className: "mt-1 flex gap-2",
127
- children: [!1, !0].map((e) => /* @__PURE__ */ u("button", {
128
- onClick: () => C({
129
- ...S,
122
+ children: [!1, !0].map((e) => /* @__PURE__ */ m("button", {
123
+ onClick: () => D({
124
+ ...E,
130
125
  isPublic: e
131
126
  }),
132
- className: `rounded-md px-3 py-1.5 text-sm ${S.isPublic === e ? "bg-primary text-primary-foreground" : "bg-muted text-muted-foreground"}`,
127
+ className: `rounded-md px-3 py-1.5 text-sm ${E.isPublic === e ? "bg-primary text-primary-foreground" : "bg-muted text-muted-foreground"}`,
133
128
  children: e ? "Public" : "Private"
134
129
  }, String(e)))
135
130
  })] })
136
131
  ]
137
132
  })] })
138
- }), /* @__PURE__ */ d("div", {
133
+ }), /* @__PURE__ */ h("div", {
139
134
  className: "space-y-6",
140
- children: [/* @__PURE__ */ d(m, { children: [/* @__PURE__ */ u(g, { children: /* @__PURE__ */ d(_, {
135
+ children: [/* @__PURE__ */ h(v, { children: [/* @__PURE__ */ m(b, { children: /* @__PURE__ */ h(x, {
141
136
  className: "flex items-center gap-2",
142
- children: [/* @__PURE__ */ u(r, { className: "h-4 w-4" }), "Preview"]
143
- }) }), /* @__PURE__ */ d(h, {
137
+ children: [/* @__PURE__ */ m(s, { className: "h-4 w-4" }), "Preview"]
138
+ }) }), /* @__PURE__ */ h(y, {
144
139
  className: "space-y-3",
145
140
  children: [
146
- /* @__PURE__ */ d("div", {
141
+ /* @__PURE__ */ h("div", {
147
142
  className: "flex items-center gap-2",
148
- children: [/* @__PURE__ */ u(a, { className: "h-5 w-5 text-primary" }), /* @__PURE__ */ u("span", {
143
+ children: [/* @__PURE__ */ m(l, { className: "h-5 w-5 text-primary" }), /* @__PURE__ */ m("span", {
149
144
  className: "font-medium",
150
- children: S.name || "Product Name"
145
+ children: E.name || "Product Name"
151
146
  })]
152
147
  }),
153
- /* @__PURE__ */ u("p", {
148
+ /* @__PURE__ */ m("p", {
154
149
  className: "text-sm text-muted-foreground",
155
- children: S.description || "No description yet"
150
+ children: E.description || "No description yet"
156
151
  }),
157
- /* @__PURE__ */ u(f, {
158
- variant: S.isPublic ? "default" : "secondary",
152
+ /* @__PURE__ */ m(g, {
153
+ variant: E.isPublic ? "default" : "secondary",
159
154
  className: "capitalize",
160
- children: S.isPublic ? "Public" : "Private"
155
+ children: E.isPublic ? "Public" : "Private"
161
156
  }),
162
- /* @__PURE__ */ d("div", {
157
+ /* @__PURE__ */ h("div", {
163
158
  className: "pt-2 border-t",
164
- children: [/* @__PURE__ */ u("p", {
159
+ children: [/* @__PURE__ */ m("p", {
165
160
  className: "text-xs font-medium text-muted-foreground",
166
- children: "TYPE"
167
- }), /* @__PURE__ */ u("p", {
168
- className: "text-sm mt-1 capitalize",
169
- children: S.type.toLowerCase()
161
+ children: "PLANNING FOCUS"
162
+ }), /* @__PURE__ */ m("div", {
163
+ className: "mt-1",
164
+ children: /* @__PURE__ */ m(r, { focus: E.planningFocus })
170
165
  })]
171
166
  })
172
167
  ]
173
- })] }), /* @__PURE__ */ d(m, { children: [/* @__PURE__ */ u(g, { children: /* @__PURE__ */ u(_, { children: "Quick Tips" }) }), /* @__PURE__ */ d(h, {
168
+ })] }), /* @__PURE__ */ h(v, { children: [/* @__PURE__ */ m(b, { children: /* @__PURE__ */ m(x, { children: "Quick Tips" }) }), /* @__PURE__ */ h(y, {
174
169
  className: "text-sm text-muted-foreground space-y-2",
175
170
  children: [
176
- /* @__PURE__ */ u("p", { children: "A strong product vision guides all feature decisions." }),
177
- /* @__PURE__ */ u("p", { children: "Keep the strategy focused on 2-3 key differentiators." }),
178
- /* @__PURE__ */ u("p", { children: "Products start in \"planning\" and move to \"active\" when launched." })
171
+ /* @__PURE__ */ m("p", { children: "A strong product vision guides all feature decisions." }),
172
+ /* @__PURE__ */ m("p", { children: "Keep the strategy focused on 2-3 key differentiators." }),
173
+ /* @__PURE__ */ m("p", { children: "Products start in \"planning\" and move to \"active\" when launched." })
179
174
  ]
180
175
  })] })]
181
176
  })]
@@ -184,6 +179,6 @@ function b() {
184
179
  });
185
180
  }
186
181
  //#endregion
187
- export { b as ProductsCreateProductPagePage };
182
+ export { w as ProductsCreateProductPagePage };
188
183
 
189
184
  //# sourceMappingURL=ProductsCreateProductPagePage.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"ProductsCreateProductPagePage.js","names":[],"sources":["../../../src/planmagnet/pages/ProductsCreateProductPagePage.tsx"],"sourcesContent":["// eslint-disable-next-line @typescript-eslint/ban-ts-comment\n// @ts-nocheck\nimport { useState } from 'react';\nimport { gql } from '@apollo/client';\nimport { useMutation } from '@apollo/client/react';\nimport {\n Card,\n CardContent,\n CardHeader,\n CardTitle,\n Button,\n Input,\n Badge,\n} from '@burdenoff/fe-libs/ui';\nimport { Save, X, Package, Eye, Loader2 } from 'lucide-react';\nimport { usePlanMagnet } from '../providers/PlanMagnetProvider';\nimport { usePlanMagnetNavigate } from '../providers/PlanMagnetProvider';\n\nconst CREATE_ROADMAP = gql`\n mutation CreateRoadmap($input: PlanmagnetCreateRoadmapInput!) {\n planmagnetCreateRoadmap(input: $input) {\n id\n name\n description\n type\n isPublic\n ownerId\n createdAt\n }\n }\n`;\n\nexport function ProductsCreateProductPagePage() {\n const { workspaceId, currentUser } = usePlanMagnet();\n const navigate = usePlanMagnetNavigate();\n\n const [form, setForm] = useState({\n name: '',\n description: '',\n type: 'TIMELINE' as 'TIMELINE' | 'GANTT' | 'SWIMLANE' | 'CUSTOM',\n isPublic: false,\n });\n\n const [createRoadmap, { loading: saving, error: saveError }] = useMutation(CREATE_ROADMAP, {\n refetchQueries: ['ListRoadmaps'],\n onCompleted: () => {\n navigate('/products/products-page');\n },\n });\n\n const typeOptions = ['TIMELINE', 'GANTT', 'SWIMLANE', 'CUSTOM'] as const;\n\n const handleCreate = () => {\n if (!form.name.trim()) return;\n createRoadmap({\n variables: {\n input: {\n name: form.name,\n description: form.description || undefined,\n type: form.type,\n isPublic: form.isPublic,\n ownerId: currentUser?.id ?? '',\n },\n },\n });\n };\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\">Create Product</h1>\n <p className=\"text-sm text-muted-foreground\">\n Define a new product with its vision and strategy.\n </p>\n </div>\n <div className=\"flex items-center gap-2\">\n <Button variant=\"outline\" size=\"sm\" onClick={() => navigate('/products/products-page')}>\n <X className=\"mr-1 h-4 w-4\" />\n Cancel\n </Button>\n <Button size=\"sm\" onClick={handleCreate} disabled={saving || !form.name.trim()}>\n {saving ? (\n <Loader2 className=\"mr-1 h-4 w-4 animate-spin\" />\n ) : (\n <Save className=\"mr-1 h-4 w-4\" />\n )}\n Create Product\n </Button>\n </div>\n </div>\n\n {saveError && (\n <div className=\"p-3 rounded-md bg-destructive/10 text-destructive text-sm\">\n Failed to create product: {saveError.message}\n </div>\n )}\n\n <div className=\"grid grid-cols-1 lg:grid-cols-3 gap-6\">\n <div className=\"lg:col-span-2 space-y-6\">\n <Card>\n <CardHeader>\n <CardTitle>Basic Information</CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-4\">\n <div>\n <label className=\"text-sm font-medium\">Product Name</label>\n <Input\n placeholder=\"e.g. PlanMagnet Analytics\"\n value={form.name}\n onChange={(e) => setForm({ ...form, name: e.target.value })}\n className=\"mt-1\"\n />\n </div>\n <div>\n <label className=\"text-sm font-medium\">Description</label>\n <textarea\n className=\"mt-1 w-full rounded-md border border-input bg-background px-3 py-2 text-sm min-h-[80px]\"\n placeholder=\"Describe what this product does...\"\n value={form.description}\n onChange={(e) => setForm({ ...form, description: e.target.value })}\n />\n </div>\n <div>\n <label className=\"text-sm font-medium\">Type</label>\n <div className=\"mt-1 flex gap-2\">\n {typeOptions.map((t) => (\n <button\n key={t}\n onClick={() => setForm({ ...form, type: t })}\n className={`rounded-md px-3 py-1.5 text-sm capitalize ${form.type === t ? 'bg-primary text-primary-foreground' : 'bg-muted text-muted-foreground'}`}\n >\n {t.toLowerCase()}\n </button>\n ))}\n </div>\n </div>\n <div>\n <label className=\"text-sm font-medium\">Visibility</label>\n <div className=\"mt-1 flex gap-2\">\n {[false, true].map((pub) => (\n <button\n key={String(pub)}\n onClick={() => setForm({ ...form, isPublic: pub })}\n className={`rounded-md px-3 py-1.5 text-sm ${form.isPublic === pub ? 'bg-primary text-primary-foreground' : 'bg-muted text-muted-foreground'}`}\n >\n {pub ? 'Public' : 'Private'}\n </button>\n ))}\n </div>\n </div>\n </CardContent>\n </Card>\n </div>\n\n <div className=\"space-y-6\">\n <Card>\n <CardHeader>\n <CardTitle className=\"flex items-center gap-2\">\n <Eye className=\"h-4 w-4\" />\n Preview\n </CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-3\">\n <div className=\"flex items-center gap-2\">\n <Package className=\"h-5 w-5 text-primary\" />\n <span className=\"font-medium\">{form.name || 'Product Name'}</span>\n </div>\n <p className=\"text-sm text-muted-foreground\">\n {form.description || 'No description yet'}\n </p>\n <Badge variant={form.isPublic ? 'default' : 'secondary'} className=\"capitalize\">\n {form.isPublic ? 'Public' : 'Private'}\n </Badge>\n <div className=\"pt-2 border-t\">\n <p className=\"text-xs font-medium text-muted-foreground\">TYPE</p>\n <p className=\"text-sm mt-1 capitalize\">{form.type.toLowerCase()}</p>\n </div>\n </CardContent>\n </Card>\n\n <Card>\n <CardHeader>\n <CardTitle>Quick Tips</CardTitle>\n </CardHeader>\n <CardContent className=\"text-sm text-muted-foreground space-y-2\">\n <p>A strong product vision guides all feature decisions.</p>\n <p>Keep the strategy focused on 2-3 key differentiators.</p>\n <p>\n Products start in &quot;planning&quot; and move to &quot;active&quot; when launched.\n </p>\n </CardContent>\n </Card>\n </div>\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"ProductsCreateProductPagePage.js","names":[],"sources":["../../../src/planmagnet/pages/ProductsCreateProductPagePage.tsx"],"sourcesContent":["// eslint-disable-next-line @typescript-eslint/ban-ts-comment\n// @ts-nocheck\nimport { useState } from 'react';\nimport { gql } from '@apollo/client';\nimport { useMutation } from '@apollo/client/react';\nimport {\n Card,\n CardContent,\n CardHeader,\n CardTitle,\n Button,\n Input,\n Badge,\n} from '@burdenoff/fe-libs/ui';\nimport { Save, X, Package, Eye, Loader2 } from 'lucide-react';\nimport { usePlanMagnet } from '../providers/PlanMagnetProvider';\nimport { usePlanMagnetNavigate } from '../providers/PlanMagnetProvider';\nimport { throwOnError } from '../utils/mutationResult';\nimport {\n PlanningFocusBadge,\n PlanningFocusSelect,\n ROADMAP_DISPLAY,\n type PlanningFocus,\n} from '../roadmaps/planningFocus';\n\nconst CREATE_ROADMAP = gql`\n mutation CreateRoadmap($input: PlanmagnetCreateRoadmapInput!) {\n planmagnetCreateRoadmap(input: $input) {\n id\n name\n description\n type\n planningFocus\n isPublic\n ownerId\n createdAt\n }\n }\n`;\n\nexport function ProductsCreateProductPagePage() {\n const { workspaceId, currentUser } = usePlanMagnet();\n const navigate = usePlanMagnetNavigate();\n\n const [form, setForm] = useState({\n name: '',\n description: '',\n planningFocus: null as PlanningFocus | null,\n isPublic: false,\n });\n\n const [saveError, setSaveError] = useState<string | null>(null);\n const [createRoadmap, { loading: saving }] = useMutation(CREATE_ROADMAP, {\n refetchQueries: ['ListRoadmaps'],\n });\n\n // Navigate only after the service ACCEPTED it (see ProductsCreateRoadmapPage).\n const handleCreate = async () => {\n if (!form.name.trim()) return;\n setSaveError(null);\n try {\n await throwOnError(\n createRoadmap({\n variables: {\n input: {\n name: form.name,\n description: form.description || undefined,\n type: ROADMAP_DISPLAY,\n ...(form.planningFocus ? { planningFocus: form.planningFocus } : {}),\n isPublic: form.isPublic,\n ownerId: currentUser?.id ?? '',\n },\n },\n })\n );\n navigate('/products/products-page');\n } catch (e) {\n setSaveError(e instanceof Error ? e.message : 'The product could not be created.');\n }\n };\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\">Create Product</h1>\n <p className=\"text-sm text-muted-foreground\">\n Define a new product with its vision and strategy.\n </p>\n </div>\n <div className=\"flex items-center gap-2\">\n <Button variant=\"outline\" size=\"sm\" onClick={() => navigate('/products/products-page')}>\n <X className=\"mr-1 h-4 w-4\" />\n Cancel\n </Button>\n <Button size=\"sm\" onClick={handleCreate} disabled={saving || !form.name.trim()}>\n {saving ? (\n <Loader2 className=\"mr-1 h-4 w-4 animate-spin\" />\n ) : (\n <Save className=\"mr-1 h-4 w-4\" />\n )}\n Create Product\n </Button>\n </div>\n </div>\n\n {saveError && (\n <div role=\"alert\" className=\"p-3 rounded-md bg-destructive/10 text-destructive text-sm\">\n Failed to create product: {saveError}\n </div>\n )}\n\n <div className=\"grid grid-cols-1 lg:grid-cols-3 gap-6\">\n <div className=\"lg:col-span-2 space-y-6\">\n <Card>\n <CardHeader>\n <CardTitle>Basic Information</CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-4\">\n <div>\n <label className=\"text-sm font-medium\">Product Name</label>\n <Input\n placeholder=\"e.g. PlanMagnet Analytics\"\n value={form.name}\n onChange={(e) => setForm({ ...form, name: e.target.value })}\n className=\"mt-1\"\n />\n </div>\n <div>\n <label className=\"text-sm font-medium\">Description</label>\n <textarea\n className=\"mt-1 w-full rounded-md border border-input bg-background px-3 py-2 text-sm min-h-[80px]\"\n placeholder=\"Describe what this product does...\"\n value={form.description}\n onChange={(e) => setForm({ ...form, description: e.target.value })}\n />\n </div>\n <PlanningFocusSelect\n value={form.planningFocus}\n onChange={(planningFocus) => setForm({ ...form, planningFocus })}\n />\n <div>\n <label className=\"text-sm font-medium\">Visibility</label>\n <div className=\"mt-1 flex gap-2\">\n {[false, true].map((pub) => (\n <button\n key={String(pub)}\n onClick={() => setForm({ ...form, isPublic: pub })}\n className={`rounded-md px-3 py-1.5 text-sm ${form.isPublic === pub ? 'bg-primary text-primary-foreground' : 'bg-muted text-muted-foreground'}`}\n >\n {pub ? 'Public' : 'Private'}\n </button>\n ))}\n </div>\n </div>\n </CardContent>\n </Card>\n </div>\n\n <div className=\"space-y-6\">\n <Card>\n <CardHeader>\n <CardTitle className=\"flex items-center gap-2\">\n <Eye className=\"h-4 w-4\" />\n Preview\n </CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-3\">\n <div className=\"flex items-center gap-2\">\n <Package className=\"h-5 w-5 text-primary\" />\n <span className=\"font-medium\">{form.name || 'Product Name'}</span>\n </div>\n <p className=\"text-sm text-muted-foreground\">\n {form.description || 'No description yet'}\n </p>\n <Badge variant={form.isPublic ? 'default' : 'secondary'} className=\"capitalize\">\n {form.isPublic ? 'Public' : 'Private'}\n </Badge>\n <div className=\"pt-2 border-t\">\n <p className=\"text-xs font-medium text-muted-foreground\">PLANNING FOCUS</p>\n <div className=\"mt-1\">\n <PlanningFocusBadge focus={form.planningFocus} />\n </div>\n </div>\n </CardContent>\n </Card>\n\n <Card>\n <CardHeader>\n <CardTitle>Quick Tips</CardTitle>\n </CardHeader>\n <CardContent className=\"text-sm text-muted-foreground space-y-2\">\n <p>A strong product vision guides all feature decisions.</p>\n <p>Keep the strategy focused on 2-3 key differentiators.</p>\n <p>\n Products start in &quot;planning&quot; and move to &quot;active&quot; when launched.\n </p>\n </CardContent>\n </Card>\n </div>\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"ProductsCreateRoadmapPagePage.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/pages/ProductsCreateRoadmapPagePage.tsx"],"names":[],"mappings":"AAuBA,wBAAgB,6BAA6B,4CA2H5C"}
1
+ {"version":3,"file":"ProductsCreateRoadmapPagePage.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/pages/ProductsCreateRoadmapPagePage.tsx"],"names":[],"mappings":"AA8BA,wBAAgB,6BAA6B,4CA0H5C"}