@elytracms/studio 0.0.97 → 0.0.99

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 (107) hide show
  1. package/convex/assets.ts +16 -2
  2. package/convex/ingest-attempt.test.ts +161 -0
  3. package/convex/ingest.test.ts +269 -16
  4. package/convex/ingest.ts +238 -47
  5. package/convex/path-segment-refusal.test.ts +201 -0
  6. package/convex/prototype-key.test.ts +92 -0
  7. package/convex/records-column-transform.convex.test.ts +147 -0
  8. package/convex/records-zod-refinements.convex.test.ts +94 -0
  9. package/convex/schema.ts +1 -1
  10. package/dist/addon.d.ts +5 -3
  11. package/dist/addon.js +4 -3
  12. package/dist/addon.js.map +1 -1
  13. package/dist/components/app-views/default-modules.d.ts +10 -3
  14. package/dist/components/app-views/default-modules.js +5 -1
  15. package/dist/components/app-views/default-modules.js.map +1 -1
  16. package/dist/components/app-views/module.d.ts +15 -8
  17. package/dist/components/app-views/module.js.map +1 -1
  18. package/dist/components/builder/reuse-context.d.ts +29 -1
  19. package/dist/components/builder/reuse-context.js +58 -12
  20. package/dist/components/builder/reuse-context.js.map +1 -1
  21. package/dist/components/cms/cms-store.d.ts +5 -6
  22. package/dist/components/cms/cms-store.js +12 -24
  23. package/dist/components/cms/cms-store.js.map +1 -1
  24. package/dist/components/cms/field-control.d.ts +12 -1
  25. package/dist/components/cms/field-control.js +31 -6
  26. package/dist/components/cms/field-control.js.map +1 -1
  27. package/dist/components/cms/import-batch.d.ts +23 -0
  28. package/dist/components/cms/import-batch.js +43 -0
  29. package/dist/components/cms/import-batch.js.map +1 -0
  30. package/dist/components/cms/rich-text/editor.js +13 -10
  31. package/dist/components/cms/rich-text/editor.js.map +1 -1
  32. package/dist/components/cms/rich-text/value.d.ts +9 -0
  33. package/dist/components/cms/rich-text/value.js +13 -0
  34. package/dist/components/cms/rich-text/value.js.map +1 -1
  35. package/dist/components/cms/use-document-draft.d.ts +39 -3
  36. package/dist/components/cms/use-document-draft.js +46 -16
  37. package/dist/components/cms/use-document-draft.js.map +1 -1
  38. package/dist/components/cms/use-publish-controller.d.ts +7 -0
  39. package/dist/components/cms/use-publish-controller.js +17 -3
  40. package/dist/components/cms/use-publish-controller.js.map +1 -1
  41. package/dist/components/cms/versions-pane.js +8 -5
  42. package/dist/components/cms/versions-pane.js.map +1 -1
  43. package/dist/components/delivery-health-banner.d.ts +34 -16
  44. package/dist/components/delivery-health-banner.js +59 -27
  45. package/dist/components/delivery-health-banner.js.map +1 -1
  46. package/dist/components/issue-severity.d.ts +3 -2
  47. package/dist/components/issue-severity.js.map +1 -1
  48. package/dist/components/nav-issues-registry.d.ts +39 -24
  49. package/dist/components/nav-issues-registry.js +50 -21
  50. package/dist/components/nav-issues-registry.js.map +1 -1
  51. package/dist/components/nav-issues.d.ts +23 -50
  52. package/dist/components/nav-issues.js +31 -59
  53. package/dist/components/nav-issues.js.map +1 -1
  54. package/dist/components/notifications/notice-center.d.ts +61 -0
  55. package/dist/components/notifications/notice-center.js +119 -0
  56. package/dist/components/notifications/notice-center.js.map +1 -0
  57. package/dist/components/notifications/notice-model.d.ts +137 -0
  58. package/dist/components/notifications/notice-model.js +107 -0
  59. package/dist/components/notifications/notice-model.js.map +1 -0
  60. package/dist/components/notifications/outcome-notice.d.ts +16 -0
  61. package/dist/components/notifications/outcome-notice.js +34 -0
  62. package/dist/components/notifications/outcome-notice.js.map +1 -0
  63. package/dist/components/notifications/shell-findings.d.ts +36 -0
  64. package/dist/components/notifications/shell-findings.js +140 -0
  65. package/dist/components/notifications/shell-findings.js.map +1 -0
  66. package/dist/components/rail-view-source.js +5 -0
  67. package/dist/components/rail-view-source.js.map +1 -1
  68. package/dist/components/records/record-lens.js.map +1 -1
  69. package/dist/components/records/record-rail.d.ts +10 -2
  70. package/dist/components/records/record-rail.js +74 -4
  71. package/dist/components/records/record-rail.js.map +1 -1
  72. package/dist/components/section-nav.d.ts +15 -3
  73. package/dist/components/section-nav.js +39 -147
  74. package/dist/components/section-nav.js.map +1 -1
  75. package/dist/components/studio-project-screen.js.map +1 -1
  76. package/dist/components/studio-shell.js +8 -3
  77. package/dist/components/studio-shell.js.map +1 -1
  78. package/dist/components/use-narrow-viewport.d.ts +11 -0
  79. package/dist/components/use-narrow-viewport.js +25 -0
  80. package/dist/components/use-narrow-viewport.js.map +1 -0
  81. package/dist/components/users/role-policy-drift-issues.d.ts +14 -7
  82. package/dist/components/users/role-policy-drift-issues.js +17 -8
  83. package/dist/components/users/role-policy-drift-issues.js.map +1 -1
  84. package/dist/lib/collection-view/collection-view-source.d.ts +4 -2
  85. package/dist/lib/collection-view/collection-view-source.js.map +1 -1
  86. package/dist/lib/engine-version.d.ts +1 -1
  87. package/dist/lib/engine-version.js +1 -1
  88. package/dist/lib/engine-version.js.map +1 -1
  89. package/dist/lib/i18n/messages.de.js +24 -3
  90. package/dist/lib/i18n/messages.de.js.map +1 -1
  91. package/dist/lib/i18n/messages.en.d.ts +22 -2
  92. package/dist/lib/i18n/messages.en.js +27 -4
  93. package/dist/lib/i18n/messages.en.js.map +1 -1
  94. package/dist/lib/notices/toast-store.d.ts +61 -0
  95. package/dist/lib/notices/toast-store.js +116 -0
  96. package/dist/lib/notices/toast-store.js.map +1 -0
  97. package/dist/lib/operations/cms-persistence.d.ts +32 -7
  98. package/dist/lib/operations/cms-persistence.js +511 -71
  99. package/dist/lib/operations/cms-persistence.js.map +1 -1
  100. package/dist/lib/persistence/convex-adapter.js +61 -30
  101. package/dist/lib/persistence/convex-adapter.js.map +1 -1
  102. package/dist/lib/persona/labels.js +1 -0
  103. package/dist/lib/persona/labels.js.map +1 -1
  104. package/dist/ui/toast.d.ts +24 -16
  105. package/dist/ui/toast.js +23 -32
  106. package/dist/ui/toast.js.map +1 -1
  107. package/package.json +4 -3
@@ -1 +1 @@
1
- {"version":3,"file":"value.js","sourceRoot":"","sources":["../../../../src/components/cms/rich-text/value.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n createBlockSequenceValue,\n isBlockSequenceValueShape,\n componentBlockNode,\n richTextDocsEquivalent,\n} from '@elytracms/core/rich-text'\nimport type {\n BlockNode,\n BlockSequenceValue,\n RichTextDoc,\n RichTextNode,\n TypographyNode,\n} from '@elytracms/core/rich-text'\nimport { isComponentBlockNode, isComponentNode } from '@elytracms/core/project-graph'\nimport type { JsonValue } from '@elytracms/core/project-graph'\nimport { COMPONENT_EMBED_NODE_TYPE } from './component-embed'\nimport { UNKNOWN_NODE_CARRIER_TYPE } from './unknown-node'\n\n/**\n * Storage ⟷ editor projection for the CMS TipTap flow editor (EC-314, converged\n * by EC-547 per the EC-500 design).\n *\n * Storage is the converged block sequence `{ version: 2, content: BlockNode[] }`\n * — typography nodes byte-identical to the historical rich-text shape plus\n * first-class `elytra/component` nodes. TipTap requires ITS grammar (a\n * `{ type: 'doc', content }` document over a CLOSED schema), so this module is\n * the just-in-time projection between the two:\n *\n * - **Way in** ({@link toEditorDoc}): typography passes near-identically; an\n * `elytra/component` node becomes the existing ComponentEmbed atom (the\n * original node riding verbatim in `attrs.node`); any node type the editor\n * does not natively edit is wrapped into the opaque\n * {@link UNKNOWN_NODE_CARRIER_TYPE} atom so the closed ProseMirror schema\n * cannot drop it.\n * - **Way out** ({@link blockSequenceValueFromDoc}): embed atoms convert back to\n * tagged `elytra/component` nodes, carriers restore their carried JSON\n * byte-for-byte, typography is re-emitted with {@link normalizeEditorDoc}'s\n * lean-attrs canonicalisation applied.\n *\n * The round-trip law (EC-500 D3): the projection may only rewrite nodes the flow\n * editor natively edits; every other node survives byte-for-byte.\n *\n * Two editor-ergonomics concerns carry over unchanged from EC-314:\n *\n * 1. **Lean canonicalisation.** ProseMirror stamps every declared attribute onto a\n * node/mark, so TipTap emits `null` defaults the lean storage convention omits\n * (`link` → `ref: null`, `codeBlock` → `language: null`, `image` → `title: null`).\n * {@link normalizeEditorDoc} prunes those so an untouched document round-trips to\n * the same shape the extractor/renderer already read.\n *\n * 2. **No false dirty.** Opening a field must not mark it changed, and typing then\n * undoing back to the original must clear dirty. {@link isEquivalentToStored}\n * compares semantically in editor-projection space so the editor can re-emit the\n * *exact stored value* when nothing really changed.\n */\n\n/**\n * The storage node types the flow editor natively edits — the projection's ONE\n * wrap/pass decision: anything outside this set rides through the editor inside\n * the opaque carrier atom. Everything listed here MUST be declared by\n * `buildRichTextExtensions`' ProseMirror schema (an undeclared type would be\n * silently dropped); `extensions.test.ts` pins the two sets equal, so drift\n * between this declaration and the schema is loud.\n *\n * The ProseMirror schema declares TWO more types than this set: the\n * ComponentEmbed atom and the carrier — both editor-only projection vehicles\n * that are never valid STORAGE vocabulary (EC-548 retired `componentEmbed` at\n * rest). Excluding them here is deliberate and load-bearing: a STORED node\n * reusing either name is a foreigner and rides the carrier byte-for-byte,\n * instead of being misread as an editor atom (which for a stored\n * `componentEmbed` would silently rewrite it to `elytra/component` on save —\n * a compat migration the owner ruled out).\n */\nexport const EDITOR_NATIVE_NODE_TYPES: ReadonlySet<string> = new Set([\n 'doc',\n 'paragraph',\n 'text',\n 'heading',\n 'bulletList',\n 'orderedList',\n 'listItem',\n 'blockquote',\n 'codeBlock',\n 'hardBreak',\n 'horizontalRule',\n 'image',\n])\n\n/** ProseMirror node types that carry no reference/atom payload and render as prose. */\nconst EMPTY_TOLERANT_BLOCKS = new Set(['paragraph', 'heading'])\n\n/**\n * Project whatever a body-capable field currently holds into an editor document.\n *\n * Accepts the canonical v2 `{ version: 2, content }` sequence (object or JSON\n * string) and projects it per the module contract. Anything unparseable becomes\n * an empty document (never throws; validation is first-class elsewhere). The\n * projection is deliberately TOTAL and shape-driven, not schema-validating: a\n * v2 value with one malformed node must still open (the bad node rides in a\n * carrier, visibly labelled) rather than blanking the whole field — the editor\n * is not the validator.\n */\nexport function toEditorDoc(value: unknown): RichTextDoc {\n const sequence = asBlockSequenceShape(value)\n if (sequence) {\n return { type: 'doc', content: sequence.content.map(projectStoredNode) }\n }\n // Anything that is not the v2 sequence (a pre-convergence envelope, a bare\n // doc, legacy plaintext, junk) opens EMPTY — deliberately not projected:\n // validation names those shapes with the explicit \"re-seed\" issue (EC-548\n // deleted the staging acceptance), and quietly editing over them here would\n // hide it.\n return { type: 'doc', content: [] }\n}\n\n/** Structural v2 test, tolerating a JSON-string form of the same value. */\nfunction asBlockSequenceShape(value: unknown): { version: 2; content: unknown[] } | undefined {\n if (isBlockSequenceValueShape(value)) return value\n if (typeof value === 'string') {\n try {\n const parsed: unknown = JSON.parse(value)\n if (isBlockSequenceValueShape(parsed)) return parsed\n } catch {\n /* not JSON — fall through */\n }\n }\n return undefined\n}\n\n/**\n * One stored node → its editor projection. Total over arbitrary JSON:\n *\n * - a tagged component node → the ComponentEmbed atom, the node itself riding\n * verbatim (tag included) in `attrs.node`;\n * - a node type the editor natively edits → passed through, `content` recursed\n * (so a component nested in a blockquote still projects to an embed);\n * - EVERYTHING else — foreign types, a tagged-but-malformed component node,\n * even non-node JSON in the content array — → the opaque carrier atom, the\n * original payload riding verbatim in `attrs.node`.\n */\nfunction projectStoredNode(node: unknown): RichTextNode {\n if (isComponentBlockNode(node)) {\n return { type: COMPONENT_EMBED_NODE_TYPE, attrs: { node: node as unknown as JsonValue } }\n }\n if (isEditorNativeNode(node)) {\n const out: RichTextNode = { ...node }\n if (node.content) out.content = node.content.map(projectStoredNode)\n return out\n }\n return { type: UNKNOWN_NODE_CARRIER_TYPE, attrs: { node: node as JsonValue } }\n}\n\nfunction isEditorNativeNode(node: unknown): node is RichTextNode {\n return (\n typeof node === 'object' &&\n node !== null &&\n !Array.isArray(node) &&\n typeof (node as { type?: unknown }).type === 'string' &&\n EDITOR_NATIVE_NODE_TYPES.has((node as { type: string }).type)\n )\n}\n\n/**\n * Project a (normalized) editor document back into the stored v2 sequence —\n * the way-out half of the module contract: embed atoms → tagged\n * `elytra/component` nodes, carriers → their carried JSON byte-for-byte,\n * typography re-emitted as-is (lean canonicalisation is\n * {@link normalizeEditorDoc}'s job, upstream).\n */\nexport function blockSequenceValueFromDoc(doc: RichTextDoc): BlockSequenceValue {\n return createBlockSequenceValue((doc.content ?? []).map(restoreStoredNode))\n}\n\nfunction restoreStoredNode(node: RichTextNode): BlockNode {\n if (node.type === COMPONENT_EMBED_NODE_TYPE) {\n const embedded: unknown = node.attrs?.node\n if (isComponentNode(embedded)) {\n // Re-tag through the one constructor (identity on already-tagged nodes),\n // so an atom payload the node factory built untagged leaves the editor\n // as a first-class tagged citizen — \"write current\".\n const { type: _tag, ...rest } = embedded\n return componentBlockNode(rest)\n }\n // An embed whose payload is missing/malformed: keep the node verbatim as\n // typography — never a silent drop; validation reports it elsewhere.\n return node as TypographyNode\n }\n if (node.type === UNKNOWN_NODE_CARRIER_TYPE) {\n const carried: unknown = node.attrs?.node\n // Byte-for-byte restore of the carried original (the D3 round-trip law).\n // An empty carrier (nothing to restore — not constructible via the\n // projection) degrades to the carrier node itself staying visible.\n if (carried !== null && carried !== undefined) return carried as BlockNode\n return node as TypographyNode\n }\n const out: TypographyNode = { ...(node as TypographyNode) }\n if (node.content) out.content = node.content.map(restoreStoredNode)\n return out\n}\n\n/**\n * Prune the `null` attribute values ProseMirror stamps from declared-but-unset\n * attributes, and drop the resulting empty `attrs` bags, so the editor's output\n * matches the lean storage convention. Recurses `content`; leaves `marks` attrs\n * pruned the same way. Pure; returns a fresh tree. (Embed and carrier payloads\n * are single non-null `attrs` entries, so pruning passes them through by\n * reference — their bytes are never rewritten here.)\n */\nexport function normalizeEditorDoc(doc: RichTextDoc): RichTextDoc {\n return { type: 'doc', content: (doc.content ?? []).map(pruneNode) }\n}\n\nfunction pruneNode(node: RichTextNode): RichTextNode {\n const out: RichTextNode = { type: node.type }\n const attrs = pruneAttrs(node.attrs)\n if (attrs) out.attrs = attrs\n if (typeof node.text === 'string') out.text = node.text\n if (node.marks && node.marks.length > 0) {\n out.marks = node.marks.map((mark) => {\n const markAttrs = pruneAttrs(mark.attrs)\n return markAttrs ? { type: mark.type, attrs: markAttrs } : { type: mark.type }\n })\n }\n if (node.content) out.content = node.content.map(pruneNode)\n return out\n}\n\nfunction pruneAttrs(\n attrs: Record<string, unknown> | undefined,\n): Record<string, never> | undefined {\n if (!attrs) return undefined\n const entries = Object.entries(attrs).filter(([, v]) => v !== null && v !== undefined)\n return entries.length > 0 ? (Object.fromEntries(entries) as Record<string, never>) : undefined\n}\n\n/**\n * Whether a document carries no author-visible content — only empty paragraphs or\n * headings (TipTap keeps a mandatory trailing block even when the field is blank).\n * Any text, atom (image/embed/carrier/hardBreak/hr), list, or blockquote makes it\n * non-empty.\n */\nexport function isEffectivelyEmpty(doc: RichTextDoc): boolean {\n return (doc.content ?? []).every(isEmptyBlock)\n}\n\nfunction isEmptyBlock(node: RichTextNode): boolean {\n if (!EMPTY_TOLERANT_BLOCKS.has(node.type)) return false\n return (node.content ?? []).every(\n (child) => child.type === 'text' && (child.text ?? '').length === 0,\n )\n}\n\n/**\n * Whether the (already normalized) editor document is semantically the stored one —\n * so the editor can re-emit the exact stored value instead of a re-serialization,\n * keeping the field un-dirtied. Both sides are compared in editor-projection space\n * ({@link toEditorDoc} is deterministic), so a stored v2 component/carrier and its\n * on-screen atom compare equal. Two empty documents are always equivalent (the\n * trailing-paragraph difference between `content: []` and `[{ paragraph }]` is not\n * a change); otherwise defers to the shared structural comparison.\n */\nexport function isEquivalentToStored(current: RichTextDoc, storedValue: unknown): boolean {\n const stored = toEditorDoc(storedValue)\n if (isEffectivelyEmpty(current) && isEffectivelyEmpty(stored)) return true\n return richTextDocsEquivalent(current, stored)\n}\n"]}
1
+ {"version":3,"file":"value.js","sourceRoot":"","sources":["../../../../src/components/cms/rich-text/value.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n createBlockSequenceValue,\n isBlockSequenceValueShape,\n componentBlockNode,\n richTextDocsEquivalent,\n} from '@elytracms/core/rich-text'\nimport type {\n BlockNode,\n BlockSequenceValue,\n RichTextDoc,\n RichTextNode,\n TypographyNode,\n} from '@elytracms/core/rich-text'\nimport { isComponentBlockNode, isComponentNode } from '@elytracms/core/project-graph'\nimport type { JsonValue } from '@elytracms/core/project-graph'\nimport { COMPONENT_EMBED_NODE_TYPE } from './component-embed'\nimport { UNKNOWN_NODE_CARRIER_TYPE } from './unknown-node'\n\n/**\n * Storage ⟷ editor projection for the CMS TipTap flow editor (EC-314, converged\n * by EC-547 per the EC-500 design).\n *\n * Storage is the converged block sequence `{ version: 2, content: BlockNode[] }`\n * — typography nodes byte-identical to the historical rich-text shape plus\n * first-class `elytra/component` nodes. TipTap requires ITS grammar (a\n * `{ type: 'doc', content }` document over a CLOSED schema), so this module is\n * the just-in-time projection between the two:\n *\n * - **Way in** ({@link toEditorDoc}): typography passes near-identically; an\n * `elytra/component` node becomes the existing ComponentEmbed atom (the\n * original node riding verbatim in `attrs.node`); any node type the editor\n * does not natively edit is wrapped into the opaque\n * {@link UNKNOWN_NODE_CARRIER_TYPE} atom so the closed ProseMirror schema\n * cannot drop it.\n * - **Way out** ({@link blockSequenceValueFromDoc}): embed atoms convert back to\n * tagged `elytra/component` nodes, carriers restore their carried JSON\n * byte-for-byte, typography is re-emitted with {@link normalizeEditorDoc}'s\n * lean-attrs canonicalisation applied.\n *\n * The round-trip law (EC-500 D3): the projection may only rewrite nodes the flow\n * editor natively edits; every other node survives byte-for-byte.\n *\n * Two editor-ergonomics concerns carry over unchanged from EC-314:\n *\n * 1. **Lean canonicalisation.** ProseMirror stamps every declared attribute onto a\n * node/mark, so TipTap emits `null` defaults the lean storage convention omits\n * (`link` → `ref: null`, `codeBlock` → `language: null`, `image` → `title: null`).\n * {@link normalizeEditorDoc} prunes those so an untouched document round-trips to\n * the same shape the extractor/renderer already read.\n *\n * 2. **No false dirty.** Opening a field must not mark it changed, and typing then\n * undoing back to the original must clear dirty. {@link isEquivalentToStored}\n * compares semantically in editor-projection space so the editor can re-emit the\n * *exact stored value* when nothing really changed.\n */\n\n/**\n * The storage node types the flow editor natively edits — the projection's ONE\n * wrap/pass decision: anything outside this set rides through the editor inside\n * the opaque carrier atom. Everything listed here MUST be declared by\n * `buildRichTextExtensions`' ProseMirror schema (an undeclared type would be\n * silently dropped); `extensions.test.ts` pins the two sets equal, so drift\n * between this declaration and the schema is loud.\n *\n * The ProseMirror schema declares TWO more types than this set: the\n * ComponentEmbed atom and the carrier — both editor-only projection vehicles\n * that are never valid STORAGE vocabulary (EC-548 retired `componentEmbed` at\n * rest). Excluding them here is deliberate and load-bearing: a STORED node\n * reusing either name is a foreigner and rides the carrier byte-for-byte,\n * instead of being misread as an editor atom (which for a stored\n * `componentEmbed` would silently rewrite it to `elytra/component` on save —\n * a compat migration the owner ruled out).\n */\nexport const EDITOR_NATIVE_NODE_TYPES: ReadonlySet<string> = new Set([\n 'doc',\n 'paragraph',\n 'text',\n 'heading',\n 'bulletList',\n 'orderedList',\n 'listItem',\n 'blockquote',\n 'codeBlock',\n 'hardBreak',\n 'horizontalRule',\n 'image',\n])\n\n/** ProseMirror node types that carry no reference/atom payload and render as prose. */\nconst EMPTY_TOLERANT_BLOCKS = new Set(['paragraph', 'heading'])\n\n/**\n * Project whatever a body-capable field currently holds into an editor document.\n *\n * Accepts the canonical v2 `{ version: 2, content }` sequence (object or JSON\n * string) and projects it per the module contract. Anything unparseable becomes\n * an empty document (never throws; validation is first-class elsewhere). The\n * projection is deliberately TOTAL and shape-driven, not schema-validating: a\n * v2 value with one malformed node must still open (the bad node rides in a\n * carrier, visibly labelled) rather than blanking the whole field — the editor\n * is not the validator.\n */\nexport function toEditorDoc(value: unknown): RichTextDoc {\n const sequence = asBlockSequenceShape(value)\n if (sequence) {\n return { type: 'doc', content: sequence.content.map(projectStoredNode) }\n }\n // Anything that is not the v2 sequence (a pre-convergence envelope, a bare\n // doc, legacy plaintext, junk) opens EMPTY — deliberately not projected:\n // validation names those shapes with the explicit \"re-seed\" issue (EC-548\n // deleted the staging acceptance), and quietly editing over them here would\n // hide it.\n return { type: 'doc', content: [] }\n}\n\n/** Structural v2 test, tolerating a JSON-string form of the same value. */\nfunction asBlockSequenceShape(value: unknown): { version: 2; content: unknown[] } | undefined {\n if (isBlockSequenceValueShape(value)) return value\n if (typeof value === 'string') {\n try {\n const parsed: unknown = JSON.parse(value)\n if (isBlockSequenceValueShape(parsed)) return parsed\n } catch {\n /* not JSON — fall through */\n }\n }\n return undefined\n}\n\n/**\n * One stored node → its editor projection. Total over arbitrary JSON:\n *\n * - a tagged component node → the ComponentEmbed atom, the node itself riding\n * verbatim (tag included) in `attrs.node`;\n * - a node type the editor natively edits → passed through, `content` recursed\n * (so a component nested in a blockquote still projects to an embed);\n * - EVERYTHING else — foreign types, a tagged-but-malformed component node,\n * even non-node JSON in the content array — → the opaque carrier atom, the\n * original payload riding verbatim in `attrs.node`.\n */\nfunction projectStoredNode(node: unknown): RichTextNode {\n if (isComponentBlockNode(node)) {\n return { type: COMPONENT_EMBED_NODE_TYPE, attrs: { node: node as unknown as JsonValue } }\n }\n if (isEditorNativeNode(node)) {\n const out: RichTextNode = { ...node }\n if (node.content) out.content = node.content.map(projectStoredNode)\n return out\n }\n return { type: UNKNOWN_NODE_CARRIER_TYPE, attrs: { node: node as JsonValue } }\n}\n\nfunction isEditorNativeNode(node: unknown): node is RichTextNode {\n return (\n typeof node === 'object' &&\n node !== null &&\n !Array.isArray(node) &&\n typeof (node as { type?: unknown }).type === 'string' &&\n EDITOR_NATIVE_NODE_TYPES.has((node as { type: string }).type)\n )\n}\n\n/**\n * Project a (normalized) editor document back into the stored v2 sequence —\n * the way-out half of the module contract: embed atoms → tagged\n * `elytra/component` nodes, carriers → their carried JSON byte-for-byte,\n * typography re-emitted as-is (lean canonicalisation is\n * {@link normalizeEditorDoc}'s job, upstream).\n */\nexport function blockSequenceValueFromDoc(doc: RichTextDoc): BlockSequenceValue {\n return createBlockSequenceValue((doc.content ?? []).map(restoreStoredNode))\n}\n\nfunction restoreStoredNode(node: RichTextNode): BlockNode {\n if (node.type === COMPONENT_EMBED_NODE_TYPE) {\n const embedded: unknown = node.attrs?.node\n if (isComponentNode(embedded)) {\n // Re-tag through the one constructor (identity on already-tagged nodes),\n // so an atom payload the node factory built untagged leaves the editor\n // as a first-class tagged citizen — \"write current\".\n const { type: _tag, ...rest } = embedded\n return componentBlockNode(rest)\n }\n // An embed whose payload is missing/malformed: keep the node verbatim as\n // typography — never a silent drop; validation reports it elsewhere.\n return node as TypographyNode\n }\n if (node.type === UNKNOWN_NODE_CARRIER_TYPE) {\n const carried: unknown = node.attrs?.node\n // Byte-for-byte restore of the carried original (the D3 round-trip law).\n // An empty carrier (nothing to restore — not constructible via the\n // projection) degrades to the carrier node itself staying visible.\n if (carried !== null && carried !== undefined) return carried as BlockNode\n return node as TypographyNode\n }\n const out: TypographyNode = { ...(node as TypographyNode) }\n if (node.content) out.content = node.content.map(restoreStoredNode)\n return out\n}\n\n/**\n * Prune the `null` attribute values ProseMirror stamps from declared-but-unset\n * attributes, and drop the resulting empty `attrs` bags, so the editor's output\n * matches the lean storage convention. Recurses `content`; leaves `marks` attrs\n * pruned the same way. Pure; returns a fresh tree. (Embed and carrier payloads\n * are single non-null `attrs` entries, so pruning passes them through by\n * reference — their bytes are never rewritten here.)\n */\nexport function normalizeEditorDoc(doc: RichTextDoc): RichTextDoc {\n return { type: 'doc', content: (doc.content ?? []).map(pruneNode) }\n}\n\nfunction pruneNode(node: RichTextNode): RichTextNode {\n const out: RichTextNode = { type: node.type }\n const attrs = pruneAttrs(node.attrs)\n if (attrs) out.attrs = attrs\n if (typeof node.text === 'string') out.text = node.text\n if (node.marks && node.marks.length > 0) {\n out.marks = node.marks.map((mark) => {\n const markAttrs = pruneAttrs(mark.attrs)\n return markAttrs ? { type: mark.type, attrs: markAttrs } : { type: mark.type }\n })\n }\n if (node.content) out.content = node.content.map(pruneNode)\n return out\n}\n\nfunction pruneAttrs(\n attrs: Record<string, unknown> | undefined,\n): Record<string, never> | undefined {\n if (!attrs) return undefined\n const entries = Object.entries(attrs).filter(([, v]) => v !== null && v !== undefined)\n return entries.length > 0 ? (Object.fromEntries(entries) as Record<string, never>) : undefined\n}\n\n/**\n * Whether a document carries no author-visible content — only empty paragraphs or\n * headings (TipTap keeps a mandatory trailing block even when the field is blank).\n * Any text, atom (image/embed/carrier/hardBreak/hr), list, or blockquote makes it\n * non-empty.\n */\nexport function isEffectivelyEmpty(doc: RichTextDoc): boolean {\n return (doc.content ?? []).every(isEmptyBlock)\n}\n\nfunction isEmptyBlock(node: RichTextNode): boolean {\n if (!EMPTY_TOLERANT_BLOCKS.has(node.type)) return false\n return (node.content ?? []).every(\n (child) => child.type === 'text' && (child.text ?? '').length === 0,\n )\n}\n\n/**\n * Whether the (already normalized) editor document is semantically the stored one —\n * so the editor can re-emit the exact stored value instead of a re-serialization,\n * keeping the field un-dirtied. Both sides are compared in editor-projection space\n * ({@link toEditorDoc} is deterministic), so a stored v2 component/carrier and its\n * on-screen atom compare equal. Two empty documents are always equivalent (the\n * trailing-paragraph difference between `content: []` and `[{ paragraph }]` is not\n * a change); otherwise defers to the shared structural comparison.\n */\nexport function isEquivalentToStored(current: RichTextDoc, storedValue: unknown): boolean {\n const stored = toEditorDoc(storedValue)\n if (isEffectivelyEmpty(current) && isEffectivelyEmpty(stored)) return true\n return richTextDocsEquivalent(current, stored)\n}\n\n/**\n * EC-679 — the value the editor REPORTS for the (normalized) document on screen (pure;\n * the editor's `onUpdate` reports exactly this). Three outcomes: unchanged → the stored\n * value verbatim (never a re-serialization, so the field is not dirtied), emptied →\n * `undefined` (the field is cleared), otherwise the new stamped value. An empty editor\n * over an ABSENT body therefore reports `undefined` — the value the draft already holds,\n * which the form's writer judges as no edit ({@link fieldEdit} in `use-document-draft.ts`).\n */\nexport function reportedValue(current: RichTextDoc, stored: unknown): unknown {\n if (isEquivalentToStored(current, stored)) return stored\n return isEffectivelyEmpty(current) ? undefined : blockSequenceValueFromDoc(current)\n}\n"]}
@@ -89,9 +89,12 @@ export declare function rebaseDraftView(doc: CmsDocument, previous: DraftView, u
89
89
  * a colleague's change the draft has not absorbed (Codex F10). There is no identity-
90
90
  * metadata operation for the alias (the value-position grammar has no position for it,
91
91
  * measured on the base), so it stays a whole-document `replace` with its precondition.
92
+ *
93
+ * EC-686: made from the draft, so the draft is what a refusal decomposes it against — the
94
+ * slot is its only change (the draft's uncommitted positions are its committer's to send).
92
95
  */
93
96
  export declare function aliasWrite(draft: DraftView, alias: string | undefined, writes: {
94
- replace(doc: CmsDocument, revision: number | undefined): void;
97
+ replace(doc: CmsDocument, revision: number | undefined, authoredOn?: CmsDocument): void;
95
98
  }): CmsDocument;
96
99
  /** The patch that writes the value `doc` holds at `path` (unset when absent). */
97
100
  export declare function positionPatch(doc: CmsDocument, path: ValuePath): DocumentPatch;
@@ -100,6 +103,36 @@ export declare function positionPatch(doc: CmsDocument, path: ValuePath): Docume
100
103
  * over it, values from the editor's current draft.
101
104
  */
102
105
  export declare function rebaseDraft(sessionDoc: CmsDocument, draft: CmsDocument, uncommitted: readonly UncommittedPosition[]): CmsDocument;
106
+ /**
107
+ * THE form writers' core (pure; what {@link DocumentDraft.setFieldValue} and {@link
108
+ * DocumentDraft.editField} run): `update` transforms the draft at ONE field position;
109
+ * the draft takes the result (`apply`) and, with a revisioned write model, the position
110
+ * is the committer's, authored at its token read BEFORE the draft moves (EC-667).
111
+ *
112
+ * **EC-679 — an editor that opens a document never writes.** The position is EDITED only
113
+ * when the value AT THE POSITION differs from what the DRAFT held there before the call,
114
+ * by the write path's own comparison: {@link semanticValueEqual} over the value the
115
+ * update stores (`writeField` canonicalises, so an emptied value that stores as absent
116
+ * equals absent). A control that re-reports what the draft already holds — the rich-text
117
+ * editor on mount, on a remount (a tab or Draft/Published switch) or on a read-only flip,
118
+ * measured on the base as an `unset` of an absent body or a same-value `set` per open —
119
+ * moves nothing and commits nothing (`undefined`). Compared against the draft, never the
120
+ * session's base: typing a value and typing it away again are two edits, and the revert
121
+ * writes too. Every field type is covered here by construction; the canvas writes through
122
+ * {@link canvasCommit}, not this.
123
+ */
124
+ export declare function fieldEdit(input: {
125
+ collection: CollectionDef;
126
+ draft: DraftView;
127
+ field: string;
128
+ locale: Locale;
129
+ update: (current: CmsDocument) => CmsDocument;
130
+ /** The write model's view tokens (absent: no revisioned write model). */
131
+ tokens: ViewTokens | undefined;
132
+ /** Absent: no revisioned write model (the draft moves, nothing is committed). */
133
+ committer: Pick<DraftCommitter, 'edited'> | undefined;
134
+ apply: (next: CmsDocument) => void;
135
+ }): 'edited' | 'draft' | undefined;
103
136
  /**
104
137
  * EC-630 — the canvas's write into the draft (pure). The canvas writer's node patches,
105
138
  * keyed by node id, are the canvas's OWN delta: from `base`, the value it last agreed on
@@ -186,9 +219,12 @@ export interface DocumentDraft {
186
219
  dirty: boolean;
187
220
  /** The record model: the editor renders a Save button + navigation guard. */
188
221
  explicitSave: boolean;
189
- /** A field's control wrote `value` (the active locale for a localized field). */
222
+ /**
223
+ * A field's control wrote `value` (the active locale for a localized field). EC-679: a
224
+ * value the draft already holds at the position is no edit ({@link fieldEdit}).
225
+ */
190
226
  setFieldValue(field: FieldDef, value: unknown): void;
191
- /** A writer that transforms ONE field position (fill / reset from base). */
227
+ /** A writer that transforms ONE field position (fill / reset from base) — the same no-op rule. */
192
228
  editField(field: FieldDef, update: (draft: CmsDocument) => CmsDocument): void;
193
229
  /**
194
230
  * The canvas emitted a new value for its field (node patches, committed at once). The
@@ -82,6 +82,9 @@ export function rebaseDraftView(doc, previous, uncommitted, tokens, canvasPath)
82
82
  * a colleague's change the draft has not absorbed (Codex F10). There is no identity-
83
83
  * metadata operation for the alias (the value-position grammar has no position for it,
84
84
  * measured on the base), so it stays a whole-document `replace` with its precondition.
85
+ *
86
+ * EC-686: made from the draft, so the draft is what a refusal decomposes it against — the
87
+ * slot is its only change (the draft's uncommitted positions are its committer's to send).
85
88
  */
86
89
  export function aliasWrite(draft, alias, writes) {
87
90
  const next = { ...draft.document };
@@ -89,7 +92,7 @@ export function aliasWrite(draft, alias, writes) {
89
92
  delete next.alias;
90
93
  else
91
94
  next.alias = alias;
92
- writes.replace(next, draft.revision);
95
+ writes.replace(next, draft.revision, draft.document);
93
96
  return next;
94
97
  }
95
98
  /** The patch that writes the value `doc` holds at `path` (unset when absent). */
@@ -107,6 +110,37 @@ export function rebaseDraft(sessionDoc, draft, uncommitted) {
107
110
  const patches = uncommitted.map((position) => positionPatch(draft, position.path));
108
111
  return applyDocumentPatches({ document: sessionDoc, patches, since: [] }).document;
109
112
  }
113
+ /**
114
+ * THE form writers' core (pure; what {@link DocumentDraft.setFieldValue} and {@link
115
+ * DocumentDraft.editField} run): `update` transforms the draft at ONE field position;
116
+ * the draft takes the result (`apply`) and, with a revisioned write model, the position
117
+ * is the committer's, authored at its token read BEFORE the draft moves (EC-667).
118
+ *
119
+ * **EC-679 — an editor that opens a document never writes.** The position is EDITED only
120
+ * when the value AT THE POSITION differs from what the DRAFT held there before the call,
121
+ * by the write path's own comparison: {@link semanticValueEqual} over the value the
122
+ * update stores (`writeField` canonicalises, so an emptied value that stores as absent
123
+ * equals absent). A control that re-reports what the draft already holds — the rich-text
124
+ * editor on mount, on a remount (a tab or Draft/Published switch) or on a read-only flip,
125
+ * measured on the base as an `unset` of an absent body or a same-value `set` per open —
126
+ * moves nothing and commits nothing (`undefined`). Compared against the draft, never the
127
+ * session's base: typing a value and typing it away again are two edits, and the revert
128
+ * writes too. Every field type is covered here by construction; the canvas writes through
129
+ * {@link canvasCommit}, not this.
130
+ */
131
+ export function fieldEdit(input) {
132
+ const { draft, field, committer } = input;
133
+ const path = fieldPositionPath(input.collection, field, input.locale);
134
+ const authored = { base: positionBase(draft, path, input.tokens) };
135
+ const next = input.update(draft.document);
136
+ if (semanticValueEqual(readValueAt(draft.document, path), readValueAt(next, path)))
137
+ return undefined;
138
+ input.apply(next);
139
+ if (!committer)
140
+ return 'draft';
141
+ committer.edited(field, path, authored);
142
+ return 'edited';
143
+ }
110
144
  /**
111
145
  * EC-630 — the canvas's write into the draft (pure). The canvas writer's node patches,
112
146
  * keyed by node id, are the canvas's OWN delta: from `base`, the value it last agreed on
@@ -298,23 +332,19 @@ export function useDocumentDraft(input) {
298
332
  setCanvasChange((last) => canvasFieldChange(previous, next.document, canvasPath, last, canvasRevision) ?? last);
299
333
  }, [doc]);
300
334
  // --- Writers ----------------------------------------------------------------------------
301
- const setFieldValue = useCallback((field, value) => {
302
- // EC-667: the position's token is read BEFORE the draft moves — from the view this
303
- // keystroke was made on (a re-edit keeps its first keystroke's either way).
304
- const path = fieldPositionPath(collection, field.name, locale);
305
- const authored = { base: positionBase(draftRef.current, path, documentModel) };
306
- const next = writeField(collection, draftRef.current.document, field.name, locale, value, { lookupManifest });
307
- setDraft(next);
308
- if (documentModel)
309
- committer.edited(field.name, path, authored);
310
- }, [collection, locale, lookupManifest, setDraft, documentModel, committer]);
311
335
  const editField = useCallback((field, update) => {
312
- const path = fieldPositionPath(collection, field.name, locale);
313
- const authored = { base: positionBase(draftRef.current, path, documentModel) };
314
- setDraft(update(draftRef.current.document));
315
- if (documentModel)
316
- committer.edited(field.name, path, authored);
336
+ fieldEdit({
337
+ collection,
338
+ draft: draftRef.current,
339
+ field: field.name,
340
+ locale,
341
+ update,
342
+ tokens: documentModel,
343
+ committer: documentModel ? committer : undefined,
344
+ apply: setDraft,
345
+ });
317
346
  }, [collection, locale, setDraft, documentModel, committer]);
347
+ const setFieldValue = useCallback((field, value) => editField(field, (current) => writeField(collection, current, field.name, locale, value, { lookupManifest })), [collection, locale, lookupManifest, editField]);
318
348
  const setCanvasValue = useCallback((field, lift) => {
319
349
  const focus = committer.focusedField();
320
350
  canvasCommit({
@@ -1 +1 @@
1
- {"version":3,"file":"use-document-draft.js","sourceRoot":"","sources":["../../../src/components/cms/use-document-draft.ts"],"names":[],"mappings":"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","sourcesContent":["import { useCallback, useEffect, useMemo, useRef, useState } from 'react'\nimport {\n PRESENCE_TTL_MS,\n livePresence,\n} from '@elytracms/core/persistence'\nimport type { PresenceEntry } from '@elytracms/core/persistence'\nimport {\n applyDocumentPatches,\n formatValuePath,\n readValueAt,\n semanticValueEqual,\n writeField,\n} from '@elytracms/core/cms-core'\nimport type {\n CmsDocument,\n CollectionDef,\n DocumentPatch,\n DocumentRef,\n FieldDef,\n Locale,\n PropFieldLookup,\n ValuePath,\n} from '@elytracms/core/cms-core'\nimport type { CollectionViewSource, DocumentWrites } from '../../lib/collection-view/collection-view-source'\nimport { fieldPositionPath } from '../../lib/cms/field-patch'\nimport { compositionPatches } from '../builder/composition-patches'\nimport type { CanvasFieldChange, CanvasLift } from '../builder/canvas-rebase'\nimport { commitPolicyOf } from './commit-policy'\nimport type { CommitPolicy } from './commit-policy'\nimport { canvasControl, routeCanvasWrite } from './canvas-commit'\nimport type { CanvasControl, CanvasControlPosition } from './canvas-commit'\nimport { browserScheduler, createDraftCommitter } from './draft-commit'\nimport type { DraftCommitter, Scheduler, UncommittedPosition } from './draft-commit'\n\n/**\n * EC-595 A2 — the detail editor's draft over the revisioned document (and, for the\n * explicit-save rails, over a record).\n *\n * **Document write model** (the CMS): the draft changes per keystroke (live validation\n * reads it); a field's edit is COMMITTED per its control kind's policy\n * ({@link commitPolicyOf} / {@link createDraftCommitter}) as the patch of the position it\n * wrote — never a document. The canvas commits each action's node patches at once\n * (`compositionPatches`); a burst of typing in one of its inspector controls is that\n * control's uncommitted position, committed on its kind's policy like a form field\n * (EC-630, `canvas-commit.ts`). The draft REBASES whenever the session's document changes\n * (a write result, a colleague's change by subscription, a resolved conflict): it is the\n * session's document with this editor's uncommitted positions laid over it — local wins\n * until committed, then the per-path check decides (EC-595 point 7).\n *\n * **Forced flushes** (the ruling: before unmount, tab hide, publish, preview, an AI\n * proposal, duplicate, transfer): {@link DocumentDraft.flush} commits every uncommitted\n * position and awaits the persister. Unmount and tab hide run it here; the editor's\n * publish, duplicate and the AI tap call it.\n *\n * **Thin presence** (Stage A): a heartbeat naming the focused field's path on focus\n * change and every {@link PRESENCE_HEARTBEAT_MS} (under the 30 s TTL), cleared on\n * unmount; the other sessions' live heartbeats arrive with the subscription.\n *\n * **Record write model** (users, redirects, assets, the account): the draft is edited\n * in place and written whole on the editor's Save button (EC-240) — unchanged.\n */\n\n/** The presence heartbeat interval — under {@link PRESENCE_TTL_MS} so a present editor never lapses. */\nexport const PRESENCE_HEARTBEAT_MS = 10_000\n\n/** An injectable visibility source (the tab-hide flush; tests drive it). */\nexport interface VisibilitySource {\n /** Call `onHidden` whenever the tab becomes hidden; returns the unsubscribe. */\n onHidden(onHidden: () => void): () => void\n}\n\nexport const documentVisibility: VisibilitySource = {\n onHidden(onHidden) {\n if (typeof document === 'undefined') return () => {}\n const listener = () => {\n if (document.visibilityState === 'hidden') onHidden()\n }\n document.addEventListener('visibilitychange', listener)\n return () => document.removeEventListener('visibilitychange', listener)\n },\n}\n\n/**\n * EC-647 — the editor's draft and the revision of the session view it was last rebased\n * onto, as ONE value: every edit made on the draft was authored against that revision (an\n * uncommitted position keeps the revision its first keystroke saw). `undefined`: the view\n * had no revision (a fixture session, a record rail, a document still being created).\n *\n * EC-667 — that session view is a COMPOSITE while it shows a pending write (Codex F18):\n * `revision` is its whole-document token (the EARLIEST authoring revision of any position\n * — what a whole-document writer, the slot alias or the translation, writes at), and\n * `shown` is the view itself, the token a position's edit reads ITS provenance from at\n * the first keystroke ({@link positionBase}).\n */\nexport interface DraftView {\n readonly document: CmsDocument\n readonly revision: number | undefined\n /** EC-667 — the session view the draft was last rebased onto (absent: none — a fixture, a record rail). */\n readonly shown?: CmsDocument\n}\n\n/** What a draft reads its tokens from: the write model's view registry (EC-647 / EC-667). */\nexport type ViewTokens = Pick<DocumentWrites, 'revisionOfView'>\n\n/** EC-667 — the draft of a session view as it is shown (pure): its value, its whole-document token, the view itself. */\nexport function sessionDraftView(doc: CmsDocument, tokens: ViewTokens | undefined): DraftView {\n return { document: doc, revision: tokens?.revisionOfView(doc), shown: doc }\n}\n\n/**\n * EC-667 — the base an edit of `path` that STARTS now on `draft` is authored against\n * (pure; the committer captures it at the first keystroke): the revision the content at\n * that position of the shown session view was authored against — under a pending\n * whole-document snapshot the snapshot's revision, under a pending own write that write's\n * base — never the revision of a row adopted underneath it. Captured at the start, it\n * survives the pending write's refusal or failure (looking it up when the commit arrives\n * would be too late). Without a view token: the draft's whole-document revision.\n */\nexport function positionBase(draft: DraftView, path: ValuePath, tokens: ViewTokens | undefined): number | undefined {\n return tokens && draft.shown ? tokens.revisionOfView(draft.shown, path) : draft.revision\n}\n\n/**\n * THE draft rebase as one value (pure — what the editor runs when the session's document\n * changes): the session view with the uncommitted positions laid over it ({@link\n * rebaseDraft}), its whole-document token, the view itself (EC-647 → EC-667: the rebase\n * moves the value AND its tokens), and — when the screen has a canvas — the token of the\n * canvas field's position, which the canvas's next edit is authored at.\n */\nexport function rebaseDraftView(\n doc: CmsDocument,\n previous: DraftView,\n uncommitted: readonly UncommittedPosition[],\n tokens: ViewTokens,\n canvasPath?: ValuePath,\n): { view: DraftView; canvasRevision: number | undefined } {\n return {\n view: { document: rebaseDraft(doc, previous.document, uncommitted), revision: tokens.revisionOfView(doc), shown: doc },\n canvasRevision: canvasPath ? tokens.revisionOfView(doc, canvasPath) : undefined,\n }\n}\n\n/**\n * EC-647 — the slot alias write (pure): the draft with `alias` set (or removed), written\n * whole AT THE DRAFT'S REVISION — never the session's tracked one, which may already hold\n * a colleague's change the draft has not absorbed (Codex F10). There is no identity-\n * metadata operation for the alias (the value-position grammar has no position for it,\n * measured on the base), so it stays a whole-document `replace` with its precondition.\n */\nexport function aliasWrite(\n draft: DraftView,\n alias: string | undefined,\n writes: { replace(doc: CmsDocument, revision: number | undefined): void },\n): CmsDocument {\n const next = { ...draft.document }\n if (alias === undefined) delete next.alias\n else next.alias = alias\n writes.replace(next, draft.revision)\n return next\n}\n\n/** The patch that writes the value `doc` holds at `path` (unset when absent). */\nexport function positionPatch(doc: CmsDocument, path: ValuePath): DocumentPatch {\n const value = readValueAt(doc, path)\n return value === undefined ? { op: 'unset', path } : { op: 'set', path, value }\n}\n\n/**\n * THE rebase (pure): the session's document with the editor's uncommitted positions laid\n * over it, values from the editor's current draft.\n */\nexport function rebaseDraft(\n sessionDoc: CmsDocument,\n draft: CmsDocument,\n uncommitted: readonly UncommittedPosition[],\n): CmsDocument {\n if (uncommitted.length === 0) return sessionDoc\n const patches = uncommitted.map((position) => positionPatch(draft, position.path))\n return applyDocumentPatches({ document: sessionDoc, patches, since: [] }).document\n}\n\n/**\n * EC-630 — the canvas's write into the draft (pure). The canvas writer's node patches,\n * keyed by node id, are the canvas's OWN delta: from `base`, the value it last agreed on\n * with the draft, to `value` — never a document diff, and never measured against the\n * draft's CURRENT value (a colleague's change that reached the draft a moment before the\n * edit lifted would read as part of the edit, and its patches would revert it). The draft\n * takes the delta over whatever it holds now (normally `base` itself) and canonicalises the\n * field through the write lens. `undefined` when the canvas changed nothing.\n */\nexport function canvasWrite(input: {\n collection: CollectionDef\n draft: CmsDocument\n field: string\n locale: Locale\n value: unknown\n base: unknown\n lookupManifest: PropFieldLookup\n}): { next: CmsDocument; patches: DocumentPatch[] } | undefined {\n const { collection, draft, field, locale, lookupManifest } = input\n const path = fieldPositionPath(collection, field, locale)\n const localized = path.length > 1 ? locale : undefined\n const patches = compositionPatches(input.base, input.value, field, localized)\n if (patches.length === 0) return undefined\n const applied = applyDocumentPatches({ document: draft, patches, since: [] }).document\n return { next: writeField(collection, draft, field, locale, readValueAt(applied, path), { lookupManifest }), patches }\n}\n\n/**\n * EC-630 — the change of the canvas field made UNDERNEATH the canvas when the draft\n * rebased from `previous` to `next` (pure), numbered after `last`; `undefined` when the\n * field did not move. The canvas's own edits are in both values (the session's view holds\n * every patch it committed, the draft overlays every position it has not), so the change\n * is someone else's: a colleague's patch, a write's canonical row, a resolved conflict.\n *\n * EC-647: it carries `revision`, the revision the draft is at after the rebase, and is\n * reported when only THAT moved too (`from` ≡ `to`): the canvas's agreed value is valid\n * at the newer revision, so its next edit is authored there — the revision follows the\n * value, never runs ahead of it.\n *\n * EC-667: `revision` is the token of the canvas FIELD's position in the shown view\n * ({@link rebaseDraftView}) — under a pending snapshot the snapshot's revision. The canvas\n * mounts at the draft's whole-document token (the earliest, possibly earlier than its\n * field's), so the first rebase with a token reports it even when nothing moved.\n */\nexport function canvasFieldChange(\n previous: CmsDocument,\n next: CmsDocument,\n path: ValuePath,\n last: CanvasFieldChange | undefined,\n revision: number | undefined,\n): CanvasFieldChange | undefined {\n const from = readValueAt(previous, path)\n const to = readValueAt(next, path)\n const tokenHeld = last === undefined ? revision === undefined : last.revision === revision\n if (semanticValueEqual(from, to) && tokenHeld) return undefined\n return { from, to, seq: (last?.seq ?? 0) + 1, revision }\n}\n\n/**\n * EC-647 — the canvas's write path (pure; what {@link DocumentDraft.setCanvasValue} runs):\n * the canvas writer's delta into the draft ({@link canvasWrite}, handed to `apply`), then\n * routed ({@link routeCanvasWrite}) — committed at once, or held as a focused inspector\n * control's uncommitted position — and in both cases paired with the revision of the\n * canvas value the edit was authored on (`lift.revision`), never the draft's: the draft\n * may already hold a colleague's change the canvas has not been rebased onto (Codex F12),\n * and the store must check the edit from the canvas's revision to refuse it where it\n * overlaps that change. `commit` absent → a draft without a revisioned write model (the\n * draft moves, nothing is committed). `undefined` when the canvas changed nothing.\n */\nexport function canvasCommit(input: {\n collection: CollectionDef\n draft: CmsDocument\n field: string\n locale: Locale\n lift: CanvasLift\n lookupManifest: PropFieldLookup\n /** The focused canvas inspector control, if focus is on one. */\n focused: CanvasControl | undefined\n committer: DraftCommitter\n /** The draft takes the canvas's write (before it is routed — a forced commit reads it). */\n apply: (next: CmsDocument) => void\n commit?: (patches: readonly DocumentPatch[], bases: readonly (number | undefined)[]) => void\n}): 'deferred' | 'committed' | 'draft' | undefined {\n const { lift, commit } = input\n const write = canvasWrite({\n collection: input.collection,\n draft: input.draft,\n field: input.field,\n locale: input.locale,\n value: lift.value,\n base: lift.base,\n lookupManifest: input.lookupManifest,\n })\n if (!write) return undefined\n input.apply(write.next)\n if (!commit) return 'draft'\n // EC-630: a burst of typing in a focused inspector control is that control's\n // uncommitted position (its policy commits it); a canvas action commits now.\n return routeCanvasWrite({\n patches: write.patches,\n focused: input.focused,\n committer: input.committer,\n revision: lift.revision,\n commitNow: (patches) => commit(patches, patches.map(() => lift.revision)),\n })\n}\n\n/**\n * The reader's \"now\" for presence liveness: THIS session's own latest heartbeat `at`\n * (written by the same store clock as everyone else's — the playground store runs a\n * deterministic sequential clock, Convex the writers' wall clocks), falling back to the\n * injected clock before the first heartbeat lands. Comparing others' `at` against a\n * different time base would call every colleague stale (measured live in the\n * playground: the store's clock is months behind the browser's).\n */\nexport function presenceReaderNow(\n entries: readonly PresenceEntry[],\n sessionId: string,\n fallback: string,\n): string {\n const own = entries.filter((entry) => entry.sessionId === sessionId).map((entry) => entry.at)\n return own.length > 0 ? own.sort().at(-1)! : fallback\n}\n\nexport interface DocumentDraft {\n draft: CmsDocument\n /**\n * EC-647 — the revision of the session view the draft was last rebased onto (see\n * {@link DraftView}): a writer that authors from {@link draft} (the translation) writes\n * at this revision. EC-667: the view's WHOLE-DOCUMENT token — the earliest authoring\n * revision of any position it shows (a field's own edit reads its position's token).\n */\n revision: number | undefined\n /** Uncommitted (document model) / unsaved (record model) edits exist. */\n dirty: boolean\n /** The record model: the editor renders a Save button + navigation guard. */\n explicitSave: boolean\n /** A field's control wrote `value` (the active locale for a localized field). */\n setFieldValue(field: FieldDef, value: unknown): void\n /** A writer that transforms ONE field position (fill / reset from base). */\n editField(field: FieldDef, update: (draft: CmsDocument) => CmsDocument): void\n /**\n * The canvas emitted a new value for its field (node patches, committed at once). The\n * lift's `base` is the value the canvas last agreed on with the draft: the canvas\n * writer's delta is `base` → `value`, applied over whatever the draft holds now (EC-630 —\n * a change that landed underneath meanwhile is never reverted by the canvas's own\n * write); its `revision` is the revision that value was agreed at — the revision the\n * edit is committed at (EC-647, {@link canvasCommit}).\n */\n setCanvasValue(field: FieldDef, lift: CanvasLift): void\n /** The slot alias (a document-level key: a whole-document write at the held revision). */\n setAlias(alias: string | undefined): void\n /** Field boundary events (focus / blur / Enter) — the commit policy's triggers. */\n focused(field: FieldDef): void\n blurred(field: FieldDef): void\n entered(field: FieldDef): void\n /**\n * EC-630 — the canvas inspector's boundary events: a control writing `position` of the\n * canvas field `field`, committing on `policy` (THE declaration for its kind). A burst of\n * typing in it is its uncommitted position until blur / Enter / the idle pause.\n */\n canvasFocused(field: FieldDef, position: CanvasControlPosition, policy: CommitPolicy): void\n canvasBlurred(field: FieldDef, position: CanvasControlPosition): void\n canvasEntered(field: FieldDef, position: CanvasControlPosition): void\n /** The forced commit (sync; the persister serialises what follows) / the record model's Save. */\n save(): void\n /** The FORCED FLUSH: commit everything uncommitted and await persistence. */\n flush(): Promise<void>\n /** Adopt a draft from outside (a restored version). */\n replaceDraft(next: CmsDocument): void\n /**\n * Bumped when the canvas must remount from the draft: a restored version replaced the\n * document, or a locale switch pointed the canvas at another bucket. (A change made\n * underneath the canvas is NOT a remount — {@link canvasChange}.)\n */\n canvasNonce: number\n /**\n * EC-630 — the latest change of the canvas field's value made UNDERNEATH the canvas (a\n * colleague's patch by subscription, a write's canonical row, a resolved conflict): the\n * draft's value before and after it. The canvas REBASES onto it — the change lands, its\n * undo history and selection survive — where it used to remount and lose both.\n */\n canvasChange: CanvasFieldChange | undefined\n /** The OTHER sessions present on this document, live by the reader's clock. */\n presence: readonly PresenceEntry[]\n}\n\nexport interface UseDocumentDraftInput {\n source: CollectionViewSource\n collection: CollectionDef\n doc: CmsDocument\n locale: Locale\n lookupManifest: PropFieldLookup\n /** The composition field the canvas edits on this screen, if any. */\n canvasField?: FieldDef\n scheduler?: Scheduler\n visibility?: VisibilitySource\n /** The reader's clock for presence liveness (ISO); `new Date().toISOString()` by default. */\n now?: () => string\n}\n\n/**\n * EC-616 — the base a record save is diffed against: the source's value whenever the draft\n * equals it (the record as opened, or as a save came back), else the previous base (the\n * editor has unsaved changes; a change from another writer meanwhile is not theirs). Pure.\n */\nexport function recordSaveBase(previous: CmsDocument, draft: CmsDocument, doc: CmsDocument): CmsDocument {\n return semanticValueEqual(draft, doc) ? doc : previous\n}\n\nexport function useDocumentDraft(input: UseDocumentDraftInput): DocumentDraft {\n const { source, collection, doc, locale, lookupManifest, canvasField } = input\n const writes = source.writes\n const documentModel = writes.kind === 'document' ? writes : undefined\n const ref: DocumentRef = useMemo(() => ({ collection: doc.collection, id: doc.id }), [doc.collection, doc.id])\n // EC-647: the draft and the revision of the view it rebased onto travel as ONE value\n // (EC-667: with the view itself — the per-position token).\n const [view, setViewState] = useState<DraftView>(() => sessionDraftView(doc, documentModel))\n const draft = view.document\n const draftRef = useRef(view)\n // EC-616: the record as the editor opened it (or as of their last successful save) — the\n // BASE a record rail diffs a save against, so it writes only the editor's changes. It moves\n // only while the draft equals the source's value (at open, and once a save is reflected\n // back): another writer's change arriving mid-edit never becomes the editor's base.\n const baseRef = useRef<CmsDocument>(doc)\n baseRef.current = recordSaveBase(baseRef.current, draftRef.current.document, doc)\n const setView = useCallback((next: DraftView) => {\n draftRef.current = next\n setViewState(next)\n }, [])\n /** An edit of the draft: its value moves, the tokens it is authored against do not. */\n const setDraft = useCallback(\n (next: CmsDocument) => setView({ ...draftRef.current, document: next }),\n [setView],\n )\n const scheduler = input.scheduler ?? browserScheduler\n const now = input.now ?? (() => new Date().toISOString())\n const fieldsByName = useMemo(() => new Map(collection.fields.map((f) => [f.name, f])), [collection])\n\n // --- Committing --------------------------------------------------------------------\n const writesRef = useRef(documentModel)\n writesRef.current = documentModel\n /** EC-630 — the canvas inspector controls that took focus, by committer key (their position + policy). */\n const canvasControls = useRef(new Map<string, CanvasControl>())\n const committer = useMemo<DraftCommitter>(\n () =>\n createDraftCommitter({\n policyOf: (name) => {\n const field = fieldsByName.get(name)\n if (field) return commitPolicyOf(field)\n return canvasControls.current.get(name)?.policy ?? 'blur-or-idle'\n },\n commit: (positions) => {\n const target = writesRef.current\n if (!target) return\n void target.commit(\n ref,\n positions.map((position) => positionPatch(draftRef.current.document, position.path)),\n positions.map((position) => position.base),\n )\n },\n // EC-647: a position's base is the revision of the DRAFT its first keystroke was\n // made on — never the session's tracked revision, which may already hold a\n // colleague's change the draft has not rebased onto yet. EC-667: the form's\n // writers name the POSITION's token ({@link positionBase}, passed to `edited`);\n // this whole-document token (the earliest) is the fallback.\n baseOf: () => draftRef.current.revision,\n scheduler,\n }),\n // One committer per open document (the editor is keyed by it).\n [ref],\n )\n\n // --- The canvas's value: its remount and its rebase ------------------------------------\n const canvasPath = canvasField ? fieldPositionPath(collection, canvasField.name, locale) : undefined\n const canvasPathKey = canvasPath ? formatValuePath(canvasPath) : ''\n const [canvasNonce, setCanvasNonce] = useState(0)\n const [canvasChange, setCanvasChange] = useState<CanvasFieldChange | undefined>(undefined)\n // A locale switch points the canvas at another bucket: remount from it.\n const lastCanvasPathKey = useRef(canvasPathKey)\n useEffect(() => {\n if (lastCanvasPathKey.current === canvasPathKey) return\n lastCanvasPathKey.current = canvasPathKey\n setCanvasNonce((n) => n + 1)\n }, [canvasPathKey])\n\n // --- The rebase (document model) -------------------------------------------------------\n const firstDoc = useRef(true)\n useEffect(() => {\n if (firstDoc.current) {\n firstDoc.current = false\n return\n }\n if (!documentModel) return\n const previous = draftRef.current.document\n // EC-647: the rebase moves the value AND the revision — the session view's own\n // (EC-667: its tokens, whole-document and per position).\n const { view: next, canvasRevision } = rebaseDraftView(doc, draftRef.current, committer.uncommitted(), documentModel, canvasPath)\n setView(next)\n // EC-630: the canvas field moved underneath the canvas — the canvas rebases onto the\n // change (its own edits are in both values: the session's view holds every patch it\n // committed, and the draft overlays every position it has not). EC-647: the change\n // carries the revision — EC-667: the canvas FIELD's token — so the canvas's next edit\n // is authored where its value is.\n if (!canvasPath) return\n setCanvasChange((last) => canvasFieldChange(previous, next.document, canvasPath, last, canvasRevision) ?? last)\n }, [doc])\n\n // --- Writers ----------------------------------------------------------------------------\n const setFieldValue = useCallback(\n (field: FieldDef, value: unknown) => {\n // EC-667: the position's token is read BEFORE the draft moves — from the view this\n // keystroke was made on (a re-edit keeps its first keystroke's either way).\n const path = fieldPositionPath(collection, field.name, locale)\n const authored = { base: positionBase(draftRef.current, path, documentModel) }\n const next = writeField(collection, draftRef.current.document, field.name, locale, value, { lookupManifest })\n setDraft(next)\n if (documentModel) committer.edited(field.name, path, authored)\n },\n [collection, locale, lookupManifest, setDraft, documentModel, committer],\n )\n const editField = useCallback(\n (field: FieldDef, update: (current: CmsDocument) => CmsDocument) => {\n const path = fieldPositionPath(collection, field.name, locale)\n const authored = { base: positionBase(draftRef.current, path, documentModel) }\n setDraft(update(draftRef.current.document))\n if (documentModel) committer.edited(field.name, path, authored)\n },\n [collection, locale, setDraft, documentModel, committer],\n )\n const setCanvasValue = useCallback(\n (field: FieldDef, lift: CanvasLift) => {\n const focus = committer.focusedField()\n canvasCommit({\n collection,\n draft: draftRef.current.document,\n field: field.name,\n locale,\n lift,\n lookupManifest,\n focused: focus === undefined ? undefined : canvasControls.current.get(focus),\n committer,\n apply: setDraft,\n ...(documentModel\n ? { commit: (patches: readonly DocumentPatch[], bases: readonly (number | undefined)[]) => void documentModel.commit(ref, patches, bases) }\n : {}),\n })\n },\n [collection, locale, lookupManifest, setDraft, documentModel, ref, committer],\n )\n const setAlias = useCallback(\n (alias: string | undefined) => {\n if (!documentModel) {\n aliasWrite(draftRef.current, alias, { replace: (next) => setDraft(next) })\n return\n }\n // The uncommitted positions first (the snapshot holds them), then the snapshot AT\n // THE DRAFT'S REVISION (EC-647 — Codex F10).\n committer.commitAll()\n setDraft(aliasWrite(draftRef.current, alias, documentModel))\n },\n [setDraft, documentModel, committer],\n )\n\n // --- Forced flush ----------------------------------------------------------------------\n const save = useCallback(() => {\n if (documentModel) committer.commitAll()\n else if (writes.kind === 'record') writes.save(draftRef.current.document, baseRef.current)\n }, [documentModel, committer, writes])\n const flush = useCallback(async () => {\n save()\n if (documentModel) await documentModel.flush(ref)\n }, [save, documentModel, ref])\n\n // Tab hide: commit and flush. Unmount: the same, and leave the document.\n const visibility = input.visibility ?? documentVisibility\n useEffect(() => {\n if (!documentModel) return\n return visibility.onHidden(() => {\n committer.commitAll()\n void documentModel.flush(ref)\n })\n }, [ref])\n useEffect(\n () => () => {\n const target = writesRef.current\n if (!target) return\n committer.commitAll()\n committer.dispose()\n void target.flush(ref)\n target.clearPresence(ref)\n },\n [ref],\n )\n\n // --- Subscription + presence ------------------------------------------------------------\n const [presenceEntries, setPresenceEntries] = useState<readonly PresenceEntry[]>([])\n const [clock, setClock] = useState(now)\n useEffect(() => {\n if (!documentModel) return\n return documentModel.subscribe(ref, setPresenceEntries)\n }, [ref])\n const focusedPathRef = useRef<ValuePath | undefined>(undefined)\n const heartbeat = useCallback(() => {\n writesRef.current?.reportPresence(ref, focusedPathRef.current)\n setClock(now())\n }, [ref])\n useEffect(() => {\n if (!documentModel) return\n heartbeat()\n let handle: unknown\n const tick = () => {\n heartbeat()\n handle = scheduler.setTimeout(tick, PRESENCE_HEARTBEAT_MS)\n }\n handle = scheduler.setTimeout(tick, PRESENCE_HEARTBEAT_MS)\n return () => scheduler.clearTimeout(handle)\n }, [ref])\n const presence = useMemo(\n () =>\n documentModel\n ? livePresence(\n presenceEntries,\n presenceReaderNow(presenceEntries, documentModel.presenceSessionId, clock),\n PRESENCE_TTL_MS,\n ).filter((entry) => entry.sessionId !== documentModel.presenceSessionId)\n : [],\n [presenceEntries, clock, documentModel],\n )\n\n const focused = useCallback(\n (field: FieldDef) => {\n if (!documentModel) return\n committer.focused(field.name)\n focusedPathRef.current = fieldPositionPath(collection, field.name, locale)\n heartbeat()\n },\n [documentModel, committer, collection, locale, heartbeat],\n )\n const blurred = useCallback(\n (field: FieldDef) => {\n if (!documentModel) return\n committer.blurred(field.name)\n focusedPathRef.current = undefined\n heartbeat()\n },\n [documentModel, committer, heartbeat],\n )\n const entered = useCallback(\n (field: FieldDef) => {\n if (documentModel) committer.entered(field.name)\n },\n [documentModel, committer],\n )\n\n // EC-630 — the canvas inspector's commit boundary: a control at a position of the canvas\n // field gained focus, lost it, or saw Enter. Its policy is THE declaration's for its kind.\n const canvasControlOf = useCallback(\n (field: FieldDef, position: CanvasControlPosition, policy: CommitPolicy) =>\n canvasControl(fieldPositionPath(collection, field.name, locale), position, policy),\n [collection, locale],\n )\n const canvasFocused = useCallback(\n (field: FieldDef, position: CanvasControlPosition, policy: CommitPolicy) => {\n if (!documentModel) return\n const control = canvasControlOf(field, position, policy)\n canvasControls.current.set(control.key, control)\n committer.focused(control.key)\n focusedPathRef.current = control.path\n heartbeat()\n },\n [documentModel, committer, canvasControlOf, heartbeat],\n )\n const canvasBlurred = useCallback(\n (field: FieldDef, position: CanvasControlPosition) => {\n if (!documentModel) return\n const key = canvasControlOf(field, position, 'change').key\n committer.blurred(key)\n canvasControls.current.delete(key)\n focusedPathRef.current = undefined\n heartbeat()\n },\n [documentModel, committer, canvasControlOf, heartbeat],\n )\n const canvasEntered = useCallback(\n (field: FieldDef, position: CanvasControlPosition) => {\n if (documentModel) committer.entered(canvasControlOf(field, position, 'change').key)\n },\n [documentModel, committer, canvasControlOf],\n )\n\n const replaceDraft = useCallback(\n (next: CmsDocument) => {\n // A restored version replaces the document: when it moved the canvas field, the\n // canvas remounts from it (its history belonged to the replaced value).\n const moved = canvasPath && !semanticValueEqual(readValueAt(draftRef.current.document, canvasPath), readValueAt(next, canvasPath))\n // EC-647: the restored value comes with the revision the session showed it at.\n setView(sessionDraftView(next, documentModel))\n if (moved) setCanvasNonce((n) => n + 1)\n },\n // The path is keyed by its string form.\n [setView, documentModel, canvasPathKey],\n )\n\n const dirty = useMemo(() => !semanticValueEqual(draft, doc), [draft, doc])\n\n return {\n draft,\n revision: view.revision,\n dirty,\n explicitSave: writes.kind === 'record',\n setFieldValue,\n editField,\n setCanvasValue,\n setAlias,\n focused,\n blurred,\n entered,\n canvasFocused,\n canvasBlurred,\n canvasEntered,\n save,\n flush,\n replaceDraft,\n canvasNonce,\n canvasChange,\n presence,\n }\n}\n"]}
1
+ {"version":3,"file":"use-document-draft.js","sourceRoot":"","sources":["../../../src/components/cms/use-document-draft.ts"],"names":[],"mappings":"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","sourcesContent":["import { useCallback, useEffect, useMemo, useRef, useState } from 'react'\nimport {\n PRESENCE_TTL_MS,\n livePresence,\n} from '@elytracms/core/persistence'\nimport type { PresenceEntry } from '@elytracms/core/persistence'\nimport {\n applyDocumentPatches,\n formatValuePath,\n readValueAt,\n semanticValueEqual,\n writeField,\n} from '@elytracms/core/cms-core'\nimport type {\n CmsDocument,\n CollectionDef,\n DocumentPatch,\n DocumentRef,\n FieldDef,\n Locale,\n PropFieldLookup,\n ValuePath,\n} from '@elytracms/core/cms-core'\nimport type { CollectionViewSource, DocumentWrites } from '../../lib/collection-view/collection-view-source'\nimport { fieldPositionPath } from '../../lib/cms/field-patch'\nimport { compositionPatches } from '../builder/composition-patches'\nimport type { CanvasFieldChange, CanvasLift } from '../builder/canvas-rebase'\nimport { commitPolicyOf } from './commit-policy'\nimport type { CommitPolicy } from './commit-policy'\nimport { canvasControl, routeCanvasWrite } from './canvas-commit'\nimport type { CanvasControl, CanvasControlPosition } from './canvas-commit'\nimport { browserScheduler, createDraftCommitter } from './draft-commit'\nimport type { DraftCommitter, Scheduler, UncommittedPosition } from './draft-commit'\n\n/**\n * EC-595 A2 — the detail editor's draft over the revisioned document (and, for the\n * explicit-save rails, over a record).\n *\n * **Document write model** (the CMS): the draft changes per keystroke (live validation\n * reads it); a field's edit is COMMITTED per its control kind's policy\n * ({@link commitPolicyOf} / {@link createDraftCommitter}) as the patch of the position it\n * wrote — never a document. The canvas commits each action's node patches at once\n * (`compositionPatches`); a burst of typing in one of its inspector controls is that\n * control's uncommitted position, committed on its kind's policy like a form field\n * (EC-630, `canvas-commit.ts`). The draft REBASES whenever the session's document changes\n * (a write result, a colleague's change by subscription, a resolved conflict): it is the\n * session's document with this editor's uncommitted positions laid over it — local wins\n * until committed, then the per-path check decides (EC-595 point 7).\n *\n * **Forced flushes** (the ruling: before unmount, tab hide, publish, preview, an AI\n * proposal, duplicate, transfer): {@link DocumentDraft.flush} commits every uncommitted\n * position and awaits the persister. Unmount and tab hide run it here; the editor's\n * publish, duplicate and the AI tap call it.\n *\n * **Thin presence** (Stage A): a heartbeat naming the focused field's path on focus\n * change and every {@link PRESENCE_HEARTBEAT_MS} (under the 30 s TTL), cleared on\n * unmount; the other sessions' live heartbeats arrive with the subscription.\n *\n * **Record write model** (users, redirects, assets, the account): the draft is edited\n * in place and written whole on the editor's Save button (EC-240) — unchanged.\n */\n\n/** The presence heartbeat interval — under {@link PRESENCE_TTL_MS} so a present editor never lapses. */\nexport const PRESENCE_HEARTBEAT_MS = 10_000\n\n/** An injectable visibility source (the tab-hide flush; tests drive it). */\nexport interface VisibilitySource {\n /** Call `onHidden` whenever the tab becomes hidden; returns the unsubscribe. */\n onHidden(onHidden: () => void): () => void\n}\n\nexport const documentVisibility: VisibilitySource = {\n onHidden(onHidden) {\n if (typeof document === 'undefined') return () => {}\n const listener = () => {\n if (document.visibilityState === 'hidden') onHidden()\n }\n document.addEventListener('visibilitychange', listener)\n return () => document.removeEventListener('visibilitychange', listener)\n },\n}\n\n/**\n * EC-647 — the editor's draft and the revision of the session view it was last rebased\n * onto, as ONE value: every edit made on the draft was authored against that revision (an\n * uncommitted position keeps the revision its first keystroke saw). `undefined`: the view\n * had no revision (a fixture session, a record rail, a document still being created).\n *\n * EC-667 — that session view is a COMPOSITE while it shows a pending write (Codex F18):\n * `revision` is its whole-document token (the EARLIEST authoring revision of any position\n * — what a whole-document writer, the slot alias or the translation, writes at), and\n * `shown` is the view itself, the token a position's edit reads ITS provenance from at\n * the first keystroke ({@link positionBase}).\n */\nexport interface DraftView {\n readonly document: CmsDocument\n readonly revision: number | undefined\n /** EC-667 — the session view the draft was last rebased onto (absent: none — a fixture, a record rail). */\n readonly shown?: CmsDocument\n}\n\n/** What a draft reads its tokens from: the write model's view registry (EC-647 / EC-667). */\nexport type ViewTokens = Pick<DocumentWrites, 'revisionOfView'>\n\n/** EC-667 — the draft of a session view as it is shown (pure): its value, its whole-document token, the view itself. */\nexport function sessionDraftView(doc: CmsDocument, tokens: ViewTokens | undefined): DraftView {\n return { document: doc, revision: tokens?.revisionOfView(doc), shown: doc }\n}\n\n/**\n * EC-667 — the base an edit of `path` that STARTS now on `draft` is authored against\n * (pure; the committer captures it at the first keystroke): the revision the content at\n * that position of the shown session view was authored against — under a pending\n * whole-document snapshot the snapshot's revision, under a pending own write that write's\n * base — never the revision of a row adopted underneath it. Captured at the start, it\n * survives the pending write's refusal or failure (looking it up when the commit arrives\n * would be too late). Without a view token: the draft's whole-document revision.\n */\nexport function positionBase(draft: DraftView, path: ValuePath, tokens: ViewTokens | undefined): number | undefined {\n return tokens && draft.shown ? tokens.revisionOfView(draft.shown, path) : draft.revision\n}\n\n/**\n * THE draft rebase as one value (pure — what the editor runs when the session's document\n * changes): the session view with the uncommitted positions laid over it ({@link\n * rebaseDraft}), its whole-document token, the view itself (EC-647 → EC-667: the rebase\n * moves the value AND its tokens), and — when the screen has a canvas — the token of the\n * canvas field's position, which the canvas's next edit is authored at.\n */\nexport function rebaseDraftView(\n doc: CmsDocument,\n previous: DraftView,\n uncommitted: readonly UncommittedPosition[],\n tokens: ViewTokens,\n canvasPath?: ValuePath,\n): { view: DraftView; canvasRevision: number | undefined } {\n return {\n view: { document: rebaseDraft(doc, previous.document, uncommitted), revision: tokens.revisionOfView(doc), shown: doc },\n canvasRevision: canvasPath ? tokens.revisionOfView(doc, canvasPath) : undefined,\n }\n}\n\n/**\n * EC-647 — the slot alias write (pure): the draft with `alias` set (or removed), written\n * whole AT THE DRAFT'S REVISION — never the session's tracked one, which may already hold\n * a colleague's change the draft has not absorbed (Codex F10). There is no identity-\n * metadata operation for the alias (the value-position grammar has no position for it,\n * measured on the base), so it stays a whole-document `replace` with its precondition.\n *\n * EC-686: made from the draft, so the draft is what a refusal decomposes it against — the\n * slot is its only change (the draft's uncommitted positions are its committer's to send).\n */\nexport function aliasWrite(\n draft: DraftView,\n alias: string | undefined,\n writes: { replace(doc: CmsDocument, revision: number | undefined, authoredOn?: CmsDocument): void },\n): CmsDocument {\n const next = { ...draft.document }\n if (alias === undefined) delete next.alias\n else next.alias = alias\n writes.replace(next, draft.revision, draft.document)\n return next\n}\n\n/** The patch that writes the value `doc` holds at `path` (unset when absent). */\nexport function positionPatch(doc: CmsDocument, path: ValuePath): DocumentPatch {\n const value = readValueAt(doc, path)\n return value === undefined ? { op: 'unset', path } : { op: 'set', path, value }\n}\n\n/**\n * THE rebase (pure): the session's document with the editor's uncommitted positions laid\n * over it, values from the editor's current draft.\n */\nexport function rebaseDraft(\n sessionDoc: CmsDocument,\n draft: CmsDocument,\n uncommitted: readonly UncommittedPosition[],\n): CmsDocument {\n if (uncommitted.length === 0) return sessionDoc\n const patches = uncommitted.map((position) => positionPatch(draft, position.path))\n return applyDocumentPatches({ document: sessionDoc, patches, since: [] }).document\n}\n\n/**\n * THE form writers' core (pure; what {@link DocumentDraft.setFieldValue} and {@link\n * DocumentDraft.editField} run): `update` transforms the draft at ONE field position;\n * the draft takes the result (`apply`) and, with a revisioned write model, the position\n * is the committer's, authored at its token read BEFORE the draft moves (EC-667).\n *\n * **EC-679 — an editor that opens a document never writes.** The position is EDITED only\n * when the value AT THE POSITION differs from what the DRAFT held there before the call,\n * by the write path's own comparison: {@link semanticValueEqual} over the value the\n * update stores (`writeField` canonicalises, so an emptied value that stores as absent\n * equals absent). A control that re-reports what the draft already holds — the rich-text\n * editor on mount, on a remount (a tab or Draft/Published switch) or on a read-only flip,\n * measured on the base as an `unset` of an absent body or a same-value `set` per open —\n * moves nothing and commits nothing (`undefined`). Compared against the draft, never the\n * session's base: typing a value and typing it away again are two edits, and the revert\n * writes too. Every field type is covered here by construction; the canvas writes through\n * {@link canvasCommit}, not this.\n */\nexport function fieldEdit(input: {\n collection: CollectionDef\n draft: DraftView\n field: string\n locale: Locale\n update: (current: CmsDocument) => CmsDocument\n /** The write model's view tokens (absent: no revisioned write model). */\n tokens: ViewTokens | undefined\n /** Absent: no revisioned write model (the draft moves, nothing is committed). */\n committer: Pick<DraftCommitter, 'edited'> | undefined\n apply: (next: CmsDocument) => void\n}): 'edited' | 'draft' | undefined {\n const { draft, field, committer } = input\n const path = fieldPositionPath(input.collection, field, input.locale)\n const authored = { base: positionBase(draft, path, input.tokens) }\n const next = input.update(draft.document)\n if (semanticValueEqual(readValueAt(draft.document, path), readValueAt(next, path))) return undefined\n input.apply(next)\n if (!committer) return 'draft'\n committer.edited(field, path, authored)\n return 'edited'\n}\n\n/**\n * EC-630 — the canvas's write into the draft (pure). The canvas writer's node patches,\n * keyed by node id, are the canvas's OWN delta: from `base`, the value it last agreed on\n * with the draft, to `value` — never a document diff, and never measured against the\n * draft's CURRENT value (a colleague's change that reached the draft a moment before the\n * edit lifted would read as part of the edit, and its patches would revert it). The draft\n * takes the delta over whatever it holds now (normally `base` itself) and canonicalises the\n * field through the write lens. `undefined` when the canvas changed nothing.\n */\nexport function canvasWrite(input: {\n collection: CollectionDef\n draft: CmsDocument\n field: string\n locale: Locale\n value: unknown\n base: unknown\n lookupManifest: PropFieldLookup\n}): { next: CmsDocument; patches: DocumentPatch[] } | undefined {\n const { collection, draft, field, locale, lookupManifest } = input\n const path = fieldPositionPath(collection, field, locale)\n const localized = path.length > 1 ? locale : undefined\n const patches = compositionPatches(input.base, input.value, field, localized)\n if (patches.length === 0) return undefined\n const applied = applyDocumentPatches({ document: draft, patches, since: [] }).document\n return { next: writeField(collection, draft, field, locale, readValueAt(applied, path), { lookupManifest }), patches }\n}\n\n/**\n * EC-630 — the change of the canvas field made UNDERNEATH the canvas when the draft\n * rebased from `previous` to `next` (pure), numbered after `last`; `undefined` when the\n * field did not move. The canvas's own edits are in both values (the session's view holds\n * every patch it committed, the draft overlays every position it has not), so the change\n * is someone else's: a colleague's patch, a write's canonical row, a resolved conflict.\n *\n * EC-647: it carries `revision`, the revision the draft is at after the rebase, and is\n * reported when only THAT moved too (`from` ≡ `to`): the canvas's agreed value is valid\n * at the newer revision, so its next edit is authored there — the revision follows the\n * value, never runs ahead of it.\n *\n * EC-667: `revision` is the token of the canvas FIELD's position in the shown view\n * ({@link rebaseDraftView}) — under a pending snapshot the snapshot's revision. The canvas\n * mounts at the draft's whole-document token (the earliest, possibly earlier than its\n * field's), so the first rebase with a token reports it even when nothing moved.\n */\nexport function canvasFieldChange(\n previous: CmsDocument,\n next: CmsDocument,\n path: ValuePath,\n last: CanvasFieldChange | undefined,\n revision: number | undefined,\n): CanvasFieldChange | undefined {\n const from = readValueAt(previous, path)\n const to = readValueAt(next, path)\n const tokenHeld = last === undefined ? revision === undefined : last.revision === revision\n if (semanticValueEqual(from, to) && tokenHeld) return undefined\n return { from, to, seq: (last?.seq ?? 0) + 1, revision }\n}\n\n/**\n * EC-647 — the canvas's write path (pure; what {@link DocumentDraft.setCanvasValue} runs):\n * the canvas writer's delta into the draft ({@link canvasWrite}, handed to `apply`), then\n * routed ({@link routeCanvasWrite}) — committed at once, or held as a focused inspector\n * control's uncommitted position — and in both cases paired with the revision of the\n * canvas value the edit was authored on (`lift.revision`), never the draft's: the draft\n * may already hold a colleague's change the canvas has not been rebased onto (Codex F12),\n * and the store must check the edit from the canvas's revision to refuse it where it\n * overlaps that change. `commit` absent → a draft without a revisioned write model (the\n * draft moves, nothing is committed). `undefined` when the canvas changed nothing.\n */\nexport function canvasCommit(input: {\n collection: CollectionDef\n draft: CmsDocument\n field: string\n locale: Locale\n lift: CanvasLift\n lookupManifest: PropFieldLookup\n /** The focused canvas inspector control, if focus is on one. */\n focused: CanvasControl | undefined\n committer: DraftCommitter\n /** The draft takes the canvas's write (before it is routed — a forced commit reads it). */\n apply: (next: CmsDocument) => void\n commit?: (patches: readonly DocumentPatch[], bases: readonly (number | undefined)[]) => void\n}): 'deferred' | 'committed' | 'draft' | undefined {\n const { lift, commit } = input\n const write = canvasWrite({\n collection: input.collection,\n draft: input.draft,\n field: input.field,\n locale: input.locale,\n value: lift.value,\n base: lift.base,\n lookupManifest: input.lookupManifest,\n })\n if (!write) return undefined\n input.apply(write.next)\n if (!commit) return 'draft'\n // EC-630: a burst of typing in a focused inspector control is that control's\n // uncommitted position (its policy commits it); a canvas action commits now.\n return routeCanvasWrite({\n patches: write.patches,\n focused: input.focused,\n committer: input.committer,\n revision: lift.revision,\n commitNow: (patches) => commit(patches, patches.map(() => lift.revision)),\n })\n}\n\n/**\n * The reader's \"now\" for presence liveness: THIS session's own latest heartbeat `at`\n * (written by the same store clock as everyone else's — the playground store runs a\n * deterministic sequential clock, Convex the writers' wall clocks), falling back to the\n * injected clock before the first heartbeat lands. Comparing others' `at` against a\n * different time base would call every colleague stale (measured live in the\n * playground: the store's clock is months behind the browser's).\n */\nexport function presenceReaderNow(\n entries: readonly PresenceEntry[],\n sessionId: string,\n fallback: string,\n): string {\n const own = entries.filter((entry) => entry.sessionId === sessionId).map((entry) => entry.at)\n return own.length > 0 ? own.sort().at(-1)! : fallback\n}\n\nexport interface DocumentDraft {\n draft: CmsDocument\n /**\n * EC-647 — the revision of the session view the draft was last rebased onto (see\n * {@link DraftView}): a writer that authors from {@link draft} (the translation) writes\n * at this revision. EC-667: the view's WHOLE-DOCUMENT token — the earliest authoring\n * revision of any position it shows (a field's own edit reads its position's token).\n */\n revision: number | undefined\n /** Uncommitted (document model) / unsaved (record model) edits exist. */\n dirty: boolean\n /** The record model: the editor renders a Save button + navigation guard. */\n explicitSave: boolean\n /**\n * A field's control wrote `value` (the active locale for a localized field). EC-679: a\n * value the draft already holds at the position is no edit ({@link fieldEdit}).\n */\n setFieldValue(field: FieldDef, value: unknown): void\n /** A writer that transforms ONE field position (fill / reset from base) — the same no-op rule. */\n editField(field: FieldDef, update: (draft: CmsDocument) => CmsDocument): void\n /**\n * The canvas emitted a new value for its field (node patches, committed at once). The\n * lift's `base` is the value the canvas last agreed on with the draft: the canvas\n * writer's delta is `base` → `value`, applied over whatever the draft holds now (EC-630 —\n * a change that landed underneath meanwhile is never reverted by the canvas's own\n * write); its `revision` is the revision that value was agreed at — the revision the\n * edit is committed at (EC-647, {@link canvasCommit}).\n */\n setCanvasValue(field: FieldDef, lift: CanvasLift): void\n /** The slot alias (a document-level key: a whole-document write at the held revision). */\n setAlias(alias: string | undefined): void\n /** Field boundary events (focus / blur / Enter) — the commit policy's triggers. */\n focused(field: FieldDef): void\n blurred(field: FieldDef): void\n entered(field: FieldDef): void\n /**\n * EC-630 — the canvas inspector's boundary events: a control writing `position` of the\n * canvas field `field`, committing on `policy` (THE declaration for its kind). A burst of\n * typing in it is its uncommitted position until blur / Enter / the idle pause.\n */\n canvasFocused(field: FieldDef, position: CanvasControlPosition, policy: CommitPolicy): void\n canvasBlurred(field: FieldDef, position: CanvasControlPosition): void\n canvasEntered(field: FieldDef, position: CanvasControlPosition): void\n /** The forced commit (sync; the persister serialises what follows) / the record model's Save. */\n save(): void\n /** The FORCED FLUSH: commit everything uncommitted and await persistence. */\n flush(): Promise<void>\n /** Adopt a draft from outside (a restored version). */\n replaceDraft(next: CmsDocument): void\n /**\n * Bumped when the canvas must remount from the draft: a restored version replaced the\n * document, or a locale switch pointed the canvas at another bucket. (A change made\n * underneath the canvas is NOT a remount — {@link canvasChange}.)\n */\n canvasNonce: number\n /**\n * EC-630 — the latest change of the canvas field's value made UNDERNEATH the canvas (a\n * colleague's patch by subscription, a write's canonical row, a resolved conflict): the\n * draft's value before and after it. The canvas REBASES onto it — the change lands, its\n * undo history and selection survive — where it used to remount and lose both.\n */\n canvasChange: CanvasFieldChange | undefined\n /** The OTHER sessions present on this document, live by the reader's clock. */\n presence: readonly PresenceEntry[]\n}\n\nexport interface UseDocumentDraftInput {\n source: CollectionViewSource\n collection: CollectionDef\n doc: CmsDocument\n locale: Locale\n lookupManifest: PropFieldLookup\n /** The composition field the canvas edits on this screen, if any. */\n canvasField?: FieldDef\n scheduler?: Scheduler\n visibility?: VisibilitySource\n /** The reader's clock for presence liveness (ISO); `new Date().toISOString()` by default. */\n now?: () => string\n}\n\n/**\n * EC-616 — the base a record save is diffed against: the source's value whenever the draft\n * equals it (the record as opened, or as a save came back), else the previous base (the\n * editor has unsaved changes; a change from another writer meanwhile is not theirs). Pure.\n */\nexport function recordSaveBase(previous: CmsDocument, draft: CmsDocument, doc: CmsDocument): CmsDocument {\n return semanticValueEqual(draft, doc) ? doc : previous\n}\n\nexport function useDocumentDraft(input: UseDocumentDraftInput): DocumentDraft {\n const { source, collection, doc, locale, lookupManifest, canvasField } = input\n const writes = source.writes\n const documentModel = writes.kind === 'document' ? writes : undefined\n const ref: DocumentRef = useMemo(() => ({ collection: doc.collection, id: doc.id }), [doc.collection, doc.id])\n // EC-647: the draft and the revision of the view it rebased onto travel as ONE value\n // (EC-667: with the view itself — the per-position token).\n const [view, setViewState] = useState<DraftView>(() => sessionDraftView(doc, documentModel))\n const draft = view.document\n const draftRef = useRef(view)\n // EC-616: the record as the editor opened it (or as of their last successful save) — the\n // BASE a record rail diffs a save against, so it writes only the editor's changes. It moves\n // only while the draft equals the source's value (at open, and once a save is reflected\n // back): another writer's change arriving mid-edit never becomes the editor's base.\n const baseRef = useRef<CmsDocument>(doc)\n baseRef.current = recordSaveBase(baseRef.current, draftRef.current.document, doc)\n const setView = useCallback((next: DraftView) => {\n draftRef.current = next\n setViewState(next)\n }, [])\n /** An edit of the draft: its value moves, the tokens it is authored against do not. */\n const setDraft = useCallback(\n (next: CmsDocument) => setView({ ...draftRef.current, document: next }),\n [setView],\n )\n const scheduler = input.scheduler ?? browserScheduler\n const now = input.now ?? (() => new Date().toISOString())\n const fieldsByName = useMemo(() => new Map(collection.fields.map((f) => [f.name, f])), [collection])\n\n // --- Committing --------------------------------------------------------------------\n const writesRef = useRef(documentModel)\n writesRef.current = documentModel\n /** EC-630 — the canvas inspector controls that took focus, by committer key (their position + policy). */\n const canvasControls = useRef(new Map<string, CanvasControl>())\n const committer = useMemo<DraftCommitter>(\n () =>\n createDraftCommitter({\n policyOf: (name) => {\n const field = fieldsByName.get(name)\n if (field) return commitPolicyOf(field)\n return canvasControls.current.get(name)?.policy ?? 'blur-or-idle'\n },\n commit: (positions) => {\n const target = writesRef.current\n if (!target) return\n void target.commit(\n ref,\n positions.map((position) => positionPatch(draftRef.current.document, position.path)),\n positions.map((position) => position.base),\n )\n },\n // EC-647: a position's base is the revision of the DRAFT its first keystroke was\n // made on — never the session's tracked revision, which may already hold a\n // colleague's change the draft has not rebased onto yet. EC-667: the form's\n // writers name the POSITION's token ({@link positionBase}, passed to `edited`);\n // this whole-document token (the earliest) is the fallback.\n baseOf: () => draftRef.current.revision,\n scheduler,\n }),\n // One committer per open document (the editor is keyed by it).\n [ref],\n )\n\n // --- The canvas's value: its remount and its rebase ------------------------------------\n const canvasPath = canvasField ? fieldPositionPath(collection, canvasField.name, locale) : undefined\n const canvasPathKey = canvasPath ? formatValuePath(canvasPath) : ''\n const [canvasNonce, setCanvasNonce] = useState(0)\n const [canvasChange, setCanvasChange] = useState<CanvasFieldChange | undefined>(undefined)\n // A locale switch points the canvas at another bucket: remount from it.\n const lastCanvasPathKey = useRef(canvasPathKey)\n useEffect(() => {\n if (lastCanvasPathKey.current === canvasPathKey) return\n lastCanvasPathKey.current = canvasPathKey\n setCanvasNonce((n) => n + 1)\n }, [canvasPathKey])\n\n // --- The rebase (document model) -------------------------------------------------------\n const firstDoc = useRef(true)\n useEffect(() => {\n if (firstDoc.current) {\n firstDoc.current = false\n return\n }\n if (!documentModel) return\n const previous = draftRef.current.document\n // EC-647: the rebase moves the value AND the revision — the session view's own\n // (EC-667: its tokens, whole-document and per position).\n const { view: next, canvasRevision } = rebaseDraftView(doc, draftRef.current, committer.uncommitted(), documentModel, canvasPath)\n setView(next)\n // EC-630: the canvas field moved underneath the canvas — the canvas rebases onto the\n // change (its own edits are in both values: the session's view holds every patch it\n // committed, and the draft overlays every position it has not). EC-647: the change\n // carries the revision — EC-667: the canvas FIELD's token — so the canvas's next edit\n // is authored where its value is.\n if (!canvasPath) return\n setCanvasChange((last) => canvasFieldChange(previous, next.document, canvasPath, last, canvasRevision) ?? last)\n }, [doc])\n\n // --- Writers ----------------------------------------------------------------------------\n const editField = useCallback(\n (field: FieldDef, update: (current: CmsDocument) => CmsDocument) => {\n fieldEdit({\n collection,\n draft: draftRef.current,\n field: field.name,\n locale,\n update,\n tokens: documentModel,\n committer: documentModel ? committer : undefined,\n apply: setDraft,\n })\n },\n [collection, locale, setDraft, documentModel, committer],\n )\n const setFieldValue = useCallback(\n (field: FieldDef, value: unknown) =>\n editField(field, (current) => writeField(collection, current, field.name, locale, value, { lookupManifest })),\n [collection, locale, lookupManifest, editField],\n )\n const setCanvasValue = useCallback(\n (field: FieldDef, lift: CanvasLift) => {\n const focus = committer.focusedField()\n canvasCommit({\n collection,\n draft: draftRef.current.document,\n field: field.name,\n locale,\n lift,\n lookupManifest,\n focused: focus === undefined ? undefined : canvasControls.current.get(focus),\n committer,\n apply: setDraft,\n ...(documentModel\n ? { commit: (patches: readonly DocumentPatch[], bases: readonly (number | undefined)[]) => void documentModel.commit(ref, patches, bases) }\n : {}),\n })\n },\n [collection, locale, lookupManifest, setDraft, documentModel, ref, committer],\n )\n const setAlias = useCallback(\n (alias: string | undefined) => {\n if (!documentModel) {\n aliasWrite(draftRef.current, alias, { replace: (next) => setDraft(next) })\n return\n }\n // The uncommitted positions first (the snapshot holds them), then the snapshot AT\n // THE DRAFT'S REVISION (EC-647 — Codex F10).\n committer.commitAll()\n setDraft(aliasWrite(draftRef.current, alias, documentModel))\n },\n [setDraft, documentModel, committer],\n )\n\n // --- Forced flush ----------------------------------------------------------------------\n const save = useCallback(() => {\n if (documentModel) committer.commitAll()\n else if (writes.kind === 'record') writes.save(draftRef.current.document, baseRef.current)\n }, [documentModel, committer, writes])\n const flush = useCallback(async () => {\n save()\n if (documentModel) await documentModel.flush(ref)\n }, [save, documentModel, ref])\n\n // Tab hide: commit and flush. Unmount: the same, and leave the document.\n const visibility = input.visibility ?? documentVisibility\n useEffect(() => {\n if (!documentModel) return\n return visibility.onHidden(() => {\n committer.commitAll()\n void documentModel.flush(ref)\n })\n }, [ref])\n useEffect(\n () => () => {\n const target = writesRef.current\n if (!target) return\n committer.commitAll()\n committer.dispose()\n void target.flush(ref)\n target.clearPresence(ref)\n },\n [ref],\n )\n\n // --- Subscription + presence ------------------------------------------------------------\n const [presenceEntries, setPresenceEntries] = useState<readonly PresenceEntry[]>([])\n const [clock, setClock] = useState(now)\n useEffect(() => {\n if (!documentModel) return\n return documentModel.subscribe(ref, setPresenceEntries)\n }, [ref])\n const focusedPathRef = useRef<ValuePath | undefined>(undefined)\n const heartbeat = useCallback(() => {\n writesRef.current?.reportPresence(ref, focusedPathRef.current)\n setClock(now())\n }, [ref])\n useEffect(() => {\n if (!documentModel) return\n heartbeat()\n let handle: unknown\n const tick = () => {\n heartbeat()\n handle = scheduler.setTimeout(tick, PRESENCE_HEARTBEAT_MS)\n }\n handle = scheduler.setTimeout(tick, PRESENCE_HEARTBEAT_MS)\n return () => scheduler.clearTimeout(handle)\n }, [ref])\n const presence = useMemo(\n () =>\n documentModel\n ? livePresence(\n presenceEntries,\n presenceReaderNow(presenceEntries, documentModel.presenceSessionId, clock),\n PRESENCE_TTL_MS,\n ).filter((entry) => entry.sessionId !== documentModel.presenceSessionId)\n : [],\n [presenceEntries, clock, documentModel],\n )\n\n const focused = useCallback(\n (field: FieldDef) => {\n if (!documentModel) return\n committer.focused(field.name)\n focusedPathRef.current = fieldPositionPath(collection, field.name, locale)\n heartbeat()\n },\n [documentModel, committer, collection, locale, heartbeat],\n )\n const blurred = useCallback(\n (field: FieldDef) => {\n if (!documentModel) return\n committer.blurred(field.name)\n focusedPathRef.current = undefined\n heartbeat()\n },\n [documentModel, committer, heartbeat],\n )\n const entered = useCallback(\n (field: FieldDef) => {\n if (documentModel) committer.entered(field.name)\n },\n [documentModel, committer],\n )\n\n // EC-630 — the canvas inspector's commit boundary: a control at a position of the canvas\n // field gained focus, lost it, or saw Enter. Its policy is THE declaration's for its kind.\n const canvasControlOf = useCallback(\n (field: FieldDef, position: CanvasControlPosition, policy: CommitPolicy) =>\n canvasControl(fieldPositionPath(collection, field.name, locale), position, policy),\n [collection, locale],\n )\n const canvasFocused = useCallback(\n (field: FieldDef, position: CanvasControlPosition, policy: CommitPolicy) => {\n if (!documentModel) return\n const control = canvasControlOf(field, position, policy)\n canvasControls.current.set(control.key, control)\n committer.focused(control.key)\n focusedPathRef.current = control.path\n heartbeat()\n },\n [documentModel, committer, canvasControlOf, heartbeat],\n )\n const canvasBlurred = useCallback(\n (field: FieldDef, position: CanvasControlPosition) => {\n if (!documentModel) return\n const key = canvasControlOf(field, position, 'change').key\n committer.blurred(key)\n canvasControls.current.delete(key)\n focusedPathRef.current = undefined\n heartbeat()\n },\n [documentModel, committer, canvasControlOf, heartbeat],\n )\n const canvasEntered = useCallback(\n (field: FieldDef, position: CanvasControlPosition) => {\n if (documentModel) committer.entered(canvasControlOf(field, position, 'change').key)\n },\n [documentModel, committer, canvasControlOf],\n )\n\n const replaceDraft = useCallback(\n (next: CmsDocument) => {\n // A restored version replaces the document: when it moved the canvas field, the\n // canvas remounts from it (its history belonged to the replaced value).\n const moved = canvasPath && !semanticValueEqual(readValueAt(draftRef.current.document, canvasPath), readValueAt(next, canvasPath))\n // EC-647: the restored value comes with the revision the session showed it at.\n setView(sessionDraftView(next, documentModel))\n if (moved) setCanvasNonce((n) => n + 1)\n },\n // The path is keyed by its string form.\n [setView, documentModel, canvasPathKey],\n )\n\n const dirty = useMemo(() => !semanticValueEqual(draft, doc), [draft, doc])\n\n return {\n draft,\n revision: view.revision,\n dirty,\n explicitSave: writes.kind === 'record',\n setFieldValue,\n editField,\n setCanvasValue,\n setAlias,\n focused,\n blurred,\n entered,\n canvasFocused,\n canvasBlurred,\n canvasEntered,\n save,\n flush,\n replaceDraft,\n canvasNonce,\n canvasChange,\n presence,\n }\n}\n"]}
@@ -1,5 +1,12 @@
1
1
  import type { DocumentRef } from '@elytracms/core/cms-core';
2
2
  import type { CollectionViewSource } from '../../lib/collection-view/collection-view-source.js';
3
+ /**
4
+ * EC-690 — the toast a failed publish raises, or `null` when its owner already reported it.
5
+ * A REFUSAL (`PublishRefusedError`) is the session's `persistError` — an error notice of the
6
+ * issues tray that stays until dismissed — so a toast would list it twice. A failure nobody
7
+ * reported (a throw before or outside the operation) is still toasted.
8
+ */
9
+ export declare function publishFailureToast(error: unknown): string | null;
3
10
  export type PublishState = 'idle' | 'pending' | 'success';
4
11
  /**
5
12
  * EC-268 / 0.0.39 — the split Publish button's shared gate, computed once so the
@@ -2,6 +2,20 @@ import { useCallback, useRef, useState } from 'react';
2
2
  import { STUDIO_PROJECT_ROUTE, studioPath, useNavigate } from '@elytracms/studio/router';
3
3
  import { toast } from '@elytracms/studio/ui';
4
4
  import { translate } from '../../lib/i18n/index.js';
5
+ import { PublishRefusedError } from '../../lib/operations/publish-with-revision.js';
6
+ /**
7
+ * EC-690 — the toast a failed publish raises, or `null` when its owner already reported it.
8
+ * A REFUSAL (`PublishRefusedError`) is the session's `persistError` — an error notice of the
9
+ * issues tray that stays until dismissed — so a toast would list it twice. A failure nobody
10
+ * reported (a throw before or outside the operation) is still toasted.
11
+ */
12
+ export function publishFailureToast(error) {
13
+ if (error instanceof PublishRefusedError)
14
+ return null;
15
+ return error instanceof Error
16
+ ? translate('cms.editor.publishFailedReason', { error: error.message })
17
+ : translate('cms.editor.publishFailed');
18
+ }
5
19
  /**
6
20
  * EC-268 / 0.0.39 — the split Publish button's shared gate, computed once so the
7
21
  * primary "Publish" and the "Publish & close" item can never diverge. A pure
@@ -65,9 +79,9 @@ export function usePublishController(input) {
65
79
  catch (error) {
66
80
  closeAfterPublishRef.current = false;
67
81
  setPublishState('idle');
68
- toast.error(error instanceof Error
69
- ? translate('cms.editor.publishFailedReason', { error: error.message })
70
- : translate('cms.editor.publishFailed'));
82
+ const message = publishFailureToast(error);
83
+ if (message !== null)
84
+ toast.error(message);
71
85
  }
72
86
  }, [flushDraft, source, docRef, navigate, projectId]);
73
87
  // Schema errors (strict-target errors included, EC-589) disable the button, so they
@@ -1 +1 @@
1
- {"version":3,"file":"use-publish-controller.js","sourceRoot":"","sources":["../../../src/components/cms/use-publish-controller.ts"],"names":[],"mappings":"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","sourcesContent":["import { useCallback, useRef, useState } from 'react'\nimport { STUDIO_PROJECT_ROUTE, studioPath, useNavigate } from '@elytracms/studio/router'\nimport type { DocumentRef } from '@elytracms/core/cms-core'\nimport type { CollectionViewSource } from '../../lib/collection-view/collection-view-source'\nimport { toast } from '@elytracms/studio/ui'\nimport { translate } from '../../lib/i18n'\n\nexport type PublishState = 'idle' | 'pending' | 'success'\n\n/**\n * EC-268 / 0.0.39 — the split Publish button's shared gate, computed once so the\n * primary \"Publish\" and the \"Publish & close\" item can never diverge. A pure\n * decision (directly testable): the Published perspective is read-only (switch to\n * Draft to publish), schema errors block (with a count-aware hint), and an\n * in-flight publish disables re-entry.\n */\nexport function publishGate(input: {\n hasErrors: boolean\n viewReadOnly: boolean\n publishState: PublishState\n errorCount: number\n}): { disabled: boolean; title: string | undefined } {\n const disabled = input.hasErrors || input.viewReadOnly || input.publishState === 'pending'\n const title = input.viewReadOnly\n ? translate('cms.editor.switchToDraftToPublish')\n : input.hasErrors\n ? translate(\n input.errorCount === 1\n ? 'cms.editor.fixIssuesToPublish.one'\n : 'cms.editor.fixIssuesToPublish.other',\n { count: input.errorCount },\n )\n : undefined\n return { disabled, title }\n}\n\nexport interface PublishController {\n publishState: PublishState\n /** Publish the draft. */\n handlePublish: () => void\n /** Publish and, on success, return to the rail's list (0.0.39 \"Publish & close\"). */\n handlePublishAndClose: () => void\n}\n\n/**\n * EC-268 / 0.0.39 — the publish controller. Owns the publish indicator state and the\n * 0.0.39 \"Publish & close\" navigation (a successful publish returns to the rail-owned\n * list).\n *\n * EC-589: the EC-300 confirm-through for unpublished reuse embeds is gone. Integrity is\n * two-state and declared: a STRICT relation to an unpublished target is a blocking\n * validation error (the reuse embed declares `strict: true` on its relation prop), so\n * the publish button is disabled by the same issues that gate everything else — and\n * `documents.publish` refuses it for any client; a weak one publishes with a warning.\n */\nexport function usePublishController(input: {\n source: CollectionViewSource\n docRef: DocumentRef\n projectId: string\n /** EC-595 A2 — the editor's FORCED FLUSH (commit everything uncommitted, await persistence). */\n flushDraft: () => Promise<void>\n}): PublishController {\n const { source, docRef, projectId, flushDraft } = input\n const navigate = useNavigate()\n const [publishState, setPublishState] = useState<PublishState>('idle')\n // The split-button \"Publish & close\" intent, so a successful publish returns to the\n // list. Cleared on failure.\n const closeAfterPublishRef = useRef(false)\n\n const doPublish = useCallback(async () => {\n setPublishState('pending')\n try {\n // EC-595 A2: the FORCED FLUSH — publish validates the COMMITTED draft, so every\n // uncommitted field commits and lands first.\n await flushDraft()\n // EC-585: the hidden-clear (field values, object sub-fields, component props —\n // into the draft AND the snapshot, judged in the project's locale views) runs\n // INSIDE `documents.publish`, for this and every other publish path. A refusal\n // rejects here (`PublishRefusedError`), so the indicator never says \"Published\"\n // for a publish that did not land.\n await source.publishing?.publish(docRef)\n setPublishState('success')\n setTimeout(() => setPublishState('idle'), 1800)\n // \"Publish & close\": return to the list once the publish landed (never on a\n // failure). Rail-owned `listPath`, falling back to the project index (AD-19).\n if (closeAfterPublishRef.current) {\n closeAfterPublishRef.current = false\n void navigate({\n to: STUDIO_PROJECT_ROUTE,\n params: { projectId, _splat: source.listPath ?? studioPath.index() },\n })\n }\n } catch (error) {\n closeAfterPublishRef.current = false\n setPublishState('idle')\n toast.error(\n error instanceof Error\n ? translate('cms.editor.publishFailedReason', { error: error.message })\n : translate('cms.editor.publishFailed'),\n )\n }\n }, [flushDraft, source, docRef, navigate, projectId])\n\n // Schema errors (strict-target errors included, EC-589) disable the button, so they\n // never reach here.\n const handlePublish = useCallback(() => {\n void doPublish()\n }, [doPublish])\n\n const handlePublishAndClose = useCallback(() => {\n closeAfterPublishRef.current = true\n handlePublish()\n }, [handlePublish])\n\n return {\n publishState,\n handlePublish,\n handlePublishAndClose,\n }\n}\n"]}
1
+ {"version":3,"file":"use-publish-controller.js","sourceRoot":"","sources":["../../../src/components/cms/use-publish-controller.ts"],"names":[],"mappings":"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","sourcesContent":["import { useCallback, useRef, useState } from 'react'\nimport { STUDIO_PROJECT_ROUTE, studioPath, useNavigate } from '@elytracms/studio/router'\nimport type { DocumentRef } from '@elytracms/core/cms-core'\nimport type { CollectionViewSource } from '../../lib/collection-view/collection-view-source'\nimport { toast } from '@elytracms/studio/ui'\nimport { translate } from '../../lib/i18n'\nimport { PublishRefusedError } from '../../lib/operations/publish-with-revision'\n\n/**\n * EC-690 — the toast a failed publish raises, or `null` when its owner already reported it.\n * A REFUSAL (`PublishRefusedError`) is the session's `persistError` — an error notice of the\n * issues tray that stays until dismissed — so a toast would list it twice. A failure nobody\n * reported (a throw before or outside the operation) is still toasted.\n */\nexport function publishFailureToast(error: unknown): string | null {\n if (error instanceof PublishRefusedError) return null\n return error instanceof Error\n ? translate('cms.editor.publishFailedReason', { error: error.message })\n : translate('cms.editor.publishFailed')\n}\n\nexport type PublishState = 'idle' | 'pending' | 'success'\n\n/**\n * EC-268 / 0.0.39 — the split Publish button's shared gate, computed once so the\n * primary \"Publish\" and the \"Publish & close\" item can never diverge. A pure\n * decision (directly testable): the Published perspective is read-only (switch to\n * Draft to publish), schema errors block (with a count-aware hint), and an\n * in-flight publish disables re-entry.\n */\nexport function publishGate(input: {\n hasErrors: boolean\n viewReadOnly: boolean\n publishState: PublishState\n errorCount: number\n}): { disabled: boolean; title: string | undefined } {\n const disabled = input.hasErrors || input.viewReadOnly || input.publishState === 'pending'\n const title = input.viewReadOnly\n ? translate('cms.editor.switchToDraftToPublish')\n : input.hasErrors\n ? translate(\n input.errorCount === 1\n ? 'cms.editor.fixIssuesToPublish.one'\n : 'cms.editor.fixIssuesToPublish.other',\n { count: input.errorCount },\n )\n : undefined\n return { disabled, title }\n}\n\nexport interface PublishController {\n publishState: PublishState\n /** Publish the draft. */\n handlePublish: () => void\n /** Publish and, on success, return to the rail's list (0.0.39 \"Publish & close\"). */\n handlePublishAndClose: () => void\n}\n\n/**\n * EC-268 / 0.0.39 — the publish controller. Owns the publish indicator state and the\n * 0.0.39 \"Publish & close\" navigation (a successful publish returns to the rail-owned\n * list).\n *\n * EC-589: the EC-300 confirm-through for unpublished reuse embeds is gone. Integrity is\n * two-state and declared: a STRICT relation to an unpublished target is a blocking\n * validation error (the reuse embed declares `strict: true` on its relation prop), so\n * the publish button is disabled by the same issues that gate everything else — and\n * `documents.publish` refuses it for any client; a weak one publishes with a warning.\n */\nexport function usePublishController(input: {\n source: CollectionViewSource\n docRef: DocumentRef\n projectId: string\n /** EC-595 A2 — the editor's FORCED FLUSH (commit everything uncommitted, await persistence). */\n flushDraft: () => Promise<void>\n}): PublishController {\n const { source, docRef, projectId, flushDraft } = input\n const navigate = useNavigate()\n const [publishState, setPublishState] = useState<PublishState>('idle')\n // The split-button \"Publish & close\" intent, so a successful publish returns to the\n // list. Cleared on failure.\n const closeAfterPublishRef = useRef(false)\n\n const doPublish = useCallback(async () => {\n setPublishState('pending')\n try {\n // EC-595 A2: the FORCED FLUSH — publish validates the COMMITTED draft, so every\n // uncommitted field commits and lands first.\n await flushDraft()\n // EC-585: the hidden-clear (field values, object sub-fields, component props —\n // into the draft AND the snapshot, judged in the project's locale views) runs\n // INSIDE `documents.publish`, for this and every other publish path. A refusal\n // rejects here (`PublishRefusedError`), so the indicator never says \"Published\"\n // for a publish that did not land.\n await source.publishing?.publish(docRef)\n setPublishState('success')\n setTimeout(() => setPublishState('idle'), 1800)\n // \"Publish & close\": return to the list once the publish landed (never on a\n // failure). Rail-owned `listPath`, falling back to the project index (AD-19).\n if (closeAfterPublishRef.current) {\n closeAfterPublishRef.current = false\n void navigate({\n to: STUDIO_PROJECT_ROUTE,\n params: { projectId, _splat: source.listPath ?? studioPath.index() },\n })\n }\n } catch (error) {\n closeAfterPublishRef.current = false\n setPublishState('idle')\n const message = publishFailureToast(error)\n if (message !== null) toast.error(message)\n }\n }, [flushDraft, source, docRef, navigate, projectId])\n\n // Schema errors (strict-target errors included, EC-589) disable the button, so they\n // never reach here.\n const handlePublish = useCallback(() => {\n void doPublish()\n }, [doPublish])\n\n const handlePublishAndClose = useCallback(() => {\n closeAfterPublishRef.current = true\n handlePublish()\n }, [handlePublish])\n\n return {\n publishState,\n handlePublish,\n handlePublishAndClose,\n }\n}\n"]}