@elytracms/studio 0.0.94 → 0.0.96

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 (164) hide show
  1. package/convex/asset-alt-migration.test.ts +2 -2
  2. package/convex/asset-meta-patch.test.ts +218 -0
  3. package/convex/asset-replace-delete-race.test.ts +127 -0
  4. package/convex/assets.ts +27 -2
  5. package/convex/cms.ts +124 -8
  6. package/convex/delivery-redirects.test.ts +167 -0
  7. package/convex/delivery.ts +15 -5
  8. package/convex/rail-write-parity.test.ts +6 -6
  9. package/convex/redirects.ts +1 -1
  10. package/convex/schema.ts +6 -0
  11. package/convex/strict-reference-race.test.ts +139 -0
  12. package/dist/addon.d.ts +1 -1
  13. package/dist/addon.js.map +1 -1
  14. package/dist/components/ai/ai-chat-panel.js +15 -2
  15. package/dist/components/ai/ai-chat-panel.js.map +1 -1
  16. package/dist/components/ai/translate-document-action.d.ts +6 -0
  17. package/dist/components/ai/translate-document-action.js.map +1 -1
  18. package/dist/components/ai/translate-document-dialog.d.ts +1 -1
  19. package/dist/components/ai/translate-document-dialog.js +5 -5
  20. package/dist/components/ai/translate-document-dialog.js.map +1 -1
  21. package/dist/components/assets/asset-detail.js +29 -11
  22. package/dist/components/assets/asset-detail.js.map +1 -1
  23. package/dist/components/assets/asset-locale.d.ts +16 -0
  24. package/dist/components/assets/asset-locale.js +30 -0
  25. package/dist/components/assets/asset-locale.js.map +1 -0
  26. package/dist/components/assets/asset-meta-drift-notice.d.ts +13 -0
  27. package/dist/components/assets/asset-meta-drift-notice.js +22 -0
  28. package/dist/components/assets/asset-meta-drift-notice.js.map +1 -0
  29. package/dist/components/assets/asset-meta-label.d.ts +13 -0
  30. package/dist/components/assets/asset-meta-label.js +17 -0
  31. package/dist/components/assets/asset-meta-label.js.map +1 -0
  32. package/dist/components/assets/asset-metadata.d.ts +89 -15
  33. package/dist/components/assets/asset-metadata.js +123 -11
  34. package/dist/components/assets/asset-metadata.js.map +1 -1
  35. package/dist/components/assets/asset-search.d.ts +6 -2
  36. package/dist/components/assets/asset-search.js +19 -3
  37. package/dist/components/assets/asset-search.js.map +1 -1
  38. package/dist/components/assets/asset-thumb.js +6 -2
  39. package/dist/components/assets/asset-thumb.js.map +1 -1
  40. package/dist/components/assets/assets-collection-view-source.d.ts +25 -9
  41. package/dist/components/assets/assets-collection-view-source.js +80 -26
  42. package/dist/components/assets/assets-collection-view-source.js.map +1 -1
  43. package/dist/components/assets/assets-store.js +17 -4
  44. package/dist/components/assets/assets-store.js.map +1 -1
  45. package/dist/components/builder/builder-store.d.ts +21 -0
  46. package/dist/components/builder/builder-store.js +22 -0
  47. package/dist/components/builder/builder-store.js.map +1 -1
  48. package/dist/components/builder/canvas-commit-boundary.d.ts +22 -0
  49. package/dist/components/builder/canvas-commit-boundary.js +27 -0
  50. package/dist/components/builder/canvas-commit-boundary.js.map +1 -0
  51. package/dist/components/builder/canvas-rebase.d.ts +101 -0
  52. package/dist/components/builder/canvas-rebase.js +179 -0
  53. package/dist/components/builder/canvas-rebase.js.map +1 -0
  54. package/dist/components/builder/canvas-render-context.d.ts +6 -3
  55. package/dist/components/builder/canvas-render-context.js +17 -8
  56. package/dist/components/builder/canvas-render-context.js.map +1 -1
  57. package/dist/components/builder/composition-field-editor.d.ts +26 -2
  58. package/dist/components/builder/composition-field-editor.js +46 -27
  59. package/dist/components/builder/composition-field-editor.js.map +1 -1
  60. package/dist/components/builder/node-factory.js +15 -3
  61. package/dist/components/builder/node-factory.js.map +1 -1
  62. package/dist/components/builder/property-inspector.js +3 -1
  63. package/dist/components/builder/property-inspector.js.map +1 -1
  64. package/dist/components/builder/run-inspector.js +10 -6
  65. package/dist/components/builder/run-inspector.js.map +1 -1
  66. package/dist/components/cms/asset-field.d.ts +5 -2
  67. package/dist/components/cms/asset-field.js +34 -17
  68. package/dist/components/cms/asset-field.js.map +1 -1
  69. package/dist/components/cms/asset-picker-core.d.ts +2 -2
  70. package/dist/components/cms/asset-picker-core.js +2 -2
  71. package/dist/components/cms/asset-picker-core.js.map +1 -1
  72. package/dist/components/cms/asset-picker.js +6 -2
  73. package/dist/components/cms/asset-picker.js.map +1 -1
  74. package/dist/components/cms/canvas-commit.d.ts +62 -0
  75. package/dist/components/cms/canvas-commit.js +39 -0
  76. package/dist/components/cms/canvas-commit.js.map +1 -0
  77. package/dist/components/cms/cms-store.d.ts +21 -8
  78. package/dist/components/cms/cms-store.js +19 -10
  79. package/dist/components/cms/cms-store.js.map +1 -1
  80. package/dist/components/cms/collection-table.js +27 -18
  81. package/dist/components/cms/collection-table.js.map +1 -1
  82. package/dist/components/cms/commit-policy.d.ts +6 -2
  83. package/dist/components/cms/commit-policy.js +1 -1
  84. package/dist/components/cms/commit-policy.js.map +1 -1
  85. package/dist/components/cms/detail-editor.js +12 -2
  86. package/dist/components/cms/detail-editor.js.map +1 -1
  87. package/dist/components/cms/draft-commit.d.ts +16 -3
  88. package/dist/components/cms/draft-commit.js +3 -2
  89. package/dist/components/cms/draft-commit.js.map +1 -1
  90. package/dist/components/cms/use-document-draft.d.ts +121 -5
  91. package/dist/components/cms/use-document-draft.js +197 -54
  92. package/dist/components/cms/use-document-draft.js.map +1 -1
  93. package/dist/components/cms/use-live-url.d.ts +6 -1
  94. package/dist/components/cms/use-live-url.js +12 -9
  95. package/dist/components/cms/use-live-url.js.map +1 -1
  96. package/dist/components/collection-view/cms-collection-view-source.js +11 -5
  97. package/dist/components/collection-view/cms-collection-view-source.js.map +1 -1
  98. package/dist/components/components/transfer-dialog.js +6 -4
  99. package/dist/components/components/transfer-dialog.js.map +1 -1
  100. package/dist/components/deployment-setup-gate.js +10 -1
  101. package/dist/components/deployment-setup-gate.js.map +1 -1
  102. package/dist/components/records/record-lens.js +3 -8
  103. package/dist/components/records/record-lens.js.map +1 -1
  104. package/dist/lib/ai/chat-state.d.ts +7 -0
  105. package/dist/lib/ai/chat-state.js.map +1 -1
  106. package/dist/lib/ai/tools.d.ts +8 -0
  107. package/dist/lib/ai/tools.js +4 -0
  108. package/dist/lib/ai/tools.js.map +1 -1
  109. package/dist/lib/assets/upload.d.ts +25 -7
  110. package/dist/lib/assets/upload.js +30 -21
  111. package/dist/lib/assets/upload.js.map +1 -1
  112. package/dist/lib/cms/published-documents.d.ts +24 -6
  113. package/dist/lib/cms/published-documents.js +39 -6
  114. package/dist/lib/cms/published-documents.js.map +1 -1
  115. package/dist/lib/cms-views/view-model.d.ts +25 -105
  116. package/dist/lib/cms-views/view-model.js +36 -103
  117. package/dist/lib/cms-views/view-model.js.map +1 -1
  118. package/dist/lib/collection-view/__fixtures__/document-writes.js +1 -0
  119. package/dist/lib/collection-view/__fixtures__/document-writes.js.map +1 -1
  120. package/dist/lib/collection-view/collection-view-source.d.ts +21 -8
  121. package/dist/lib/collection-view/collection-view-source.js.map +1 -1
  122. package/dist/lib/data/collection-content.js +5 -0
  123. package/dist/lib/data/collection-content.js.map +1 -1
  124. package/dist/lib/engine-version.d.ts +1 -1
  125. package/dist/lib/engine-version.js +1 -1
  126. package/dist/lib/engine-version.js.map +1 -1
  127. package/dist/lib/i18n/messages.de.js +20 -4
  128. package/dist/lib/i18n/messages.de.js.map +1 -1
  129. package/dist/lib/i18n/messages.en.d.ts +16 -1
  130. package/dist/lib/i18n/messages.en.js +22 -5
  131. package/dist/lib/i18n/messages.en.js.map +1 -1
  132. package/dist/lib/operations/cms-persistence.d.ts +80 -35
  133. package/dist/lib/operations/cms-persistence.js +348 -85
  134. package/dist/lib/operations/cms-persistence.js.map +1 -1
  135. package/dist/lib/operations/config-manifest-lookup.d.ts +1 -1
  136. package/dist/lib/operations/config-manifest-lookup.js +3 -3
  137. package/dist/lib/operations/config-manifest-lookup.js.map +1 -1
  138. package/dist/lib/persistence/convex-adapter.js +54 -8
  139. package/dist/lib/persistence/convex-adapter.js.map +1 -1
  140. package/dist/lib/persistence/deployment-readiness.js +14 -11
  141. package/dist/lib/persistence/deployment-readiness.js.map +1 -1
  142. package/dist/lib/persona/labels.js +2 -0
  143. package/dist/lib/persona/labels.js.map +1 -1
  144. package/dist/lib/query/index.d.ts +2 -2
  145. package/dist/lib/query/index.js +1 -1
  146. package/dist/lib/query/index.js.map +1 -1
  147. package/dist/lib/query/operations-query.d.ts +26 -0
  148. package/dist/lib/query/operations-query.js +12 -0
  149. package/dist/lib/query/operations-query.js.map +1 -1
  150. package/dist/lib/registry/index.js +2 -2
  151. package/dist/lib/registry/index.js.map +1 -1
  152. package/dist/lib/registry/preview.d.ts +10 -0
  153. package/dist/lib/registry/preview.js +13 -4
  154. package/dist/lib/registry/preview.js.map +1 -1
  155. package/dist/lib/routes/route-issues.js +6 -3
  156. package/dist/lib/routes/route-issues.js.map +1 -1
  157. package/dist/lib/workspace/workspace-source.js +6 -7
  158. package/dist/lib/workspace/workspace-source.js.map +1 -1
  159. package/dist/previews/entry-node.d.ts +14 -0
  160. package/dist/previews/entry-node.js +22 -0
  161. package/dist/previews/entry-node.js.map +1 -0
  162. package/dist/previews/harness.js +2 -16
  163. package/dist/previews/harness.js.map +1 -1
  164. package/package.json +3 -3
@@ -2,41 +2,68 @@ import type { CmsDocument, CollectionDef, DocumentPatch, DocumentRef, PropFieldL
2
2
  import type { CmsDocumentRecord } from '@elytracms/core/persistence';
3
3
  import type { OperationError, OperationsClient } from './operations-package.js';
4
4
  /**
5
- * CMS session → operations persistence (EC-141 → EC-595 part A2): the PATCH QUEUE.
5
+ * CMS session → operations persistence (EC-141 → EC-595 part A2 → EC-647): the PATCH QUEUE.
6
6
  *
7
7
  * The CMS session keeps documents as optimistic, synchronous state; every write is
8
8
  * mirrored onto the operations layer through the revisioned aggregate's two verbs
9
9
  * (EC-595, ratified 2026-10-24): `documents.patch` for editing, `documents.replace`
10
- * for whole-document writers (create, import). There is no document diffing anywhere:
11
- * every writer EMITS the patches it made at its source (the form's field path, the
12
- * canvas's node patches, the AI proposal's field changes, bulk set-field, transfer) and
13
- * hands them to {@link CmsPersister.commit}.
10
+ * for whole-document writers (create, import, the slot alias). There is no document
11
+ * diffing anywhere: every writer EMITS the patches it made at its source (the form's
12
+ * field path, the canvas's node patches, the AI proposal's field changes, bulk
13
+ * set-field, transfer) and hands them to {@link CmsPersister.commit}.
14
14
  *
15
- * **Per document, the persister keeps** the server row it last adopted (`base` — its
16
- * revision is what a patch is made against), the committed-but-unacknowledged patches
17
- * (each with the revision the user's view was at when it was made), and the refused
18
- * paths awaiting a decision. It derives the session's view of the document from them:
15
+ * **Per document, the persister keeps** the server row it last adopted (`base`), the
16
+ * committed-but-unacknowledged writes (each with the revision it was AUTHORED against),
17
+ * the refused paths awaiting a decision, and the writes it made itself (the own-write
18
+ * ledger). It derives the session's view of the document from them:
19
19
  *
20
20
  * display = base.document ⊕ unresolved conflicts (mine) ⊕ in-flight ⊕ queued
21
21
  *
22
22
  * by re-applying the patches with THE merge (`applyDocumentPatches`, `since: []`) — the
23
- * same pure function both stores run. So local edits win on their paths until they are
24
- * flushed (EC-595 point 7), and a server row adopted underneath them (a write result,
25
- * a subscription push, a conflict's `current`) shows everywhere else at once.
23
+ * same pure function both stores run (a queued whole-document write resets the view to
24
+ * its snapshot at its place in the order). So local edits win on their paths until they
25
+ * are flushed (EC-595 point 7), and a server row adopted underneath them (a write
26
+ * result, a subscription push, a conflict's `current`) shows everywhere else at once.
26
27
  *
27
- * **Base revisions are per patch** (not per call): a patch is checked against the
28
- * changes in `(its base, current]`, so a colleague's change that arrived (by subscribe)
29
- * AFTER the user started typing in a field is still detected on that field. A patch
30
- * whose path our own successful write just covered moves its base to that write's
31
- * revision — the server guarantees no other session touched the path in between, so
32
- * a second edit of a field during the first one's flight never conflicts with itself.
33
- * Consecutive runs of equal base are sent in commit order, one `documents.patch` call
34
- * per run.
28
+ * **A value and the revision it was authored against travel together (EC-647).** Every
29
+ * view this persister shows is registered with the revision it is at
30
+ * ({@link CmsPersister.revisionOfView} — the shown document object IS the token), and a
31
+ * writer hands each patch over WITH the revision of the view it was authored on
32
+ * (`bases`, required). The persister never substitutes a newer revision for it — not at
33
+ * commit (the old `bases ?? tracked revision` default), not at send (the old
34
+ * `queued.base ?? entry.base.revision`), not in a whole-document `replace` (which read
35
+ * the tracked revision at SEND time — Codex F10), and not for an untracked document
36
+ * (which sent at whatever revision a later read returned). A write that cannot name the
37
+ * revision it was authored against is refused, visibly — it never borrows one.
38
+ *
39
+ * - **A position under one of this session's own unacknowledged writes** (queued, in
40
+ * flight, or an open conflict's "mine") was authored against THAT write's base: the
41
+ * view showed mine there, not a colleague's change that may sit underneath it. The
42
+ * later write takes the earlier base, so a colleague's hidden change is still refused
43
+ * on it (same-field supersession has always kept the earlier base for this reason).
44
+ * - **The own-write ledger.** A base may be LIFTED, at send time, across revisions this
45
+ * persister wrote — and only those: across a run of consecutive own revisions right
46
+ * after it (nothing foreign happened in between), or to an own write's revision when
47
+ * the patch only touches positions that write covered and the write was sent at or
48
+ * below the patch's base (the store refused every foreign change of those positions in
49
+ * between, or the write would not have applied). Without it, the session's second
50
+ * edit of a position, made at the revision its first was authored against, would be
51
+ * refused by its own first write.
52
+ * - **The provisional view** (`undefined` authoring revision): a document whose view
53
+ * has no revision yet — our own create in flight, or a document the session never
54
+ * tracked (a staged transfer). Writes on it are based on the revision our create
55
+ * landed at, or — untracked — on a read of the document that EQUALS the view they were
56
+ * authored on; anything else is refused as a conflict per position.
57
+ *
58
+ * Consecutive runs of equal (lifted) base are sent in commit order, one
59
+ * `documents.patch` call per run; a queued whole-document write is sent in its place in
60
+ * that order.
35
61
  *
36
62
  * **A refused path is a conflict ON THAT FIELD** ({@link DocumentConflict}): the
37
63
  * session shows "changed by X — take theirs / keep mine"; until the user decides, the
38
64
  * field keeps showing mine (and further edits of it replace mine without being sent);
39
- * keep-mine re-sends at the current base, take-theirs drops mine. Never a
65
+ * keep-mine re-sends at the current base — the ONE deliberate "at the current base"
66
+ * write: the user chose it after seeing theirs; take-theirs drops mine. Never a
40
67
  * whole-document dialog, never a silent overwrite.
41
68
  */
42
69
  /** One refused patch awaiting the user's decision (EC-595 binding point 5). */
@@ -67,7 +94,7 @@ export type CommitOutcome = {
67
94
  readonly status: 'refused';
68
95
  readonly conflicts: readonly DocumentConflict[];
69
96
  }
70
- /** A transport / authorization failure (also reported through `onError`). */
97
+ /** A transport / authorization failure, or a write that named no authoring revision (also reported through `onError`). */
71
98
  | {
72
99
  readonly status: 'failed';
73
100
  readonly error: OperationError;
@@ -75,29 +102,46 @@ export type CommitOutcome = {
75
102
  export interface CmsPersister {
76
103
  /** Adopt a server row as this session's base for its document (hydration, a create result). */
77
104
  track(row: TrackedRow): void;
78
- /** The revision the session's view of a document is at (`undefined` = untracked). */
105
+ /** The revision the session's view of a document is at now (`undefined` = untracked). */
79
106
  revisionOf(ref: DocumentRef): number | undefined;
80
107
  /** The session's view of a tracked document (base ⊕ local), or `undefined`. */
81
108
  documentOf(ref: DocumentRef): CmsDocument | undefined;
82
109
  /**
83
- * Commit patches a writer made (at the session's current view) and flush them. The
84
- * session's view updates synchronously (via `onDocument`); the patches persist in
85
- * order through `documents.patch`. `view` — the session's current document, the
86
- * provisional base of a document this session holds no revision for (a staged
87
- * transfer); its revision is read before the first send. `bases` — per patch, the
88
- * revision the user's view was at when they STARTED that edit (an editor field typed
89
- * across a colleague's change); absent → the revision the view is at now.
110
+ * EC-647 — the revision a document value this persister SHOWED is at: every view it
111
+ * emits (`onDocument`, {@link documentOf}) and every row it tracks is registered with
112
+ * its revision, so a writer holding the value it displayed holds its revision too —
113
+ * the pair cannot drift apart. `undefined` for a value the persister never showed (a
114
+ * copy, a fixture), or a provisional view (our own create in flight, an untracked
115
+ * document).
116
+ */
117
+ revisionOfView(view: CmsDocument): number | undefined;
118
+ /**
119
+ * Commit patches a writer made and flush them. The session's view updates
120
+ * synchronously (via `onDocument`); the patches persist in order through
121
+ * `documents.patch`. `bases` — REQUIRED, one per patch: the revision of the view the
122
+ * writer authored that patch against (an editor field: the revision its draft was at
123
+ * when the user STARTED that edit; the canvas: the revision of the value its delta is
124
+ * measured from; a table row: {@link revisionOfView} of the row it showed; an AI
125
+ * proposal: the revision of the read it was built from). `undefined` entries mean the
126
+ * view was provisional (see the module doc) — on a document this session tracks and
127
+ * did not create, such a patch is refused. `view` — the session's current document,
128
+ * the provisional view of a document this session holds no revision for (a staged
129
+ * transfer).
90
130
  */
91
- commit(ref: DocumentRef, patches: readonly DocumentPatch[], view?: CmsDocument, bases?: readonly (number | undefined)[]): Promise<CommitOutcome>;
131
+ commit(ref: DocumentRef, patches: readonly DocumentPatch[], bases: readonly (number | undefined)[], view?: CmsDocument): Promise<CommitOutcome>;
92
132
  /** Adopt a server row one of the session's OWN direct writes returned (publish, restore). */
93
133
  adoptRecord(record: CmsDocumentRecord): void;
94
134
  /** Create a document (insert-if-absent: `documents.replace` `{ ifAbsent: true }`). */
95
135
  create(document: CmsDocument): void;
96
136
  /**
97
- * Write a whole document at the session's tracked revision (or insert-if-absent when
98
- * untracked): an import. A move since then is refused as a conflict, never overwritten.
137
+ * Write a whole document at the revision it was AUTHORED against (EC-647 — the
138
+ * snapshot and its revision travel together; Codex F10): an import, the slot alias. A
139
+ * move since then — by anyone but this session's own writes — is refused as a
140
+ * conflict, never overwritten. `revision` `undefined`: the snapshot was authored on a
141
+ * provisional view (our own pending create; insert-if-absent when untracked). Sent in
142
+ * its place among the session's queued patches.
99
143
  */
100
- replace(document: CmsDocument): void;
144
+ replace(document: CmsDocument, revision: number | undefined): void;
101
145
  deleteDocument(ref: DocumentRef): void;
102
146
  unpublishDocument(ref: DocumentRef): void;
103
147
  /**
@@ -125,7 +169,8 @@ export interface CmsPersisterOptions {
125
169
  onError: (error: OperationError) => void;
126
170
  /**
127
171
  * The session's view of a document changed (an optimistic commit, an adopted server
128
- * row, a resolved conflict) — `undefined` when the document is gone. Synchronous.
172
+ * row, a resolved conflict) — `undefined` when the document is gone. Synchronous. The
173
+ * document object is registered with its revision ({@link CmsPersister.revisionOfView}).
129
174
  */
130
175
  onDocument?: (ref: DocumentRef, document: CmsDocument | undefined) => void;
131
176
  /**