@elytracms/studio 0.0.102 → 0.0.104
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.
- package/convex/ingest.test.ts +251 -23
- package/convex/ingest.ts +77 -19
- package/dist/components/ai/translate-document-dialog.d.ts +43 -0
- package/dist/components/ai/translate-document-dialog.js +104 -36
- package/dist/components/ai/translate-document-dialog.js.map +1 -1
- package/dist/components/ai/translate-skipped.d.ts +19 -3
- package/dist/components/ai/translate-skipped.js +33 -7
- package/dist/components/ai/translate-skipped.js.map +1 -1
- package/dist/components/builder/canvas-theme-notice.js +4 -1
- package/dist/components/builder/canvas-theme-notice.js.map +1 -1
- package/dist/components/components/components-index.d.ts +0 -7
- package/dist/components/components/components-index.js +9 -2
- package/dist/components/components/components-index.js.map +1 -1
- package/dist/lib/ai/translate-apply.d.ts +69 -0
- package/dist/lib/ai/translate-apply.js +36 -0
- package/dist/lib/ai/translate-apply.js.map +1 -0
- package/dist/lib/ai/translate-document.d.ts +95 -7
- package/dist/lib/ai/translate-document.js +195 -57
- package/dist/lib/ai/translate-document.js.map +1 -1
- package/dist/lib/ai/writer-form.d.ts +33 -2
- package/dist/lib/ai/writer-form.js +188 -27
- package/dist/lib/ai/writer-form.js.map +1 -1
- package/dist/lib/engine-version.d.ts +1 -1
- package/dist/lib/engine-version.js +1 -1
- package/dist/lib/engine-version.js.map +1 -1
- package/dist/lib/i18n/messages.de.js +56 -10
- package/dist/lib/i18n/messages.de.js.map +1 -1
- package/dist/lib/i18n/messages.en.d.ts +41 -1
- package/dist/lib/i18n/messages.en.js +61 -10
- package/dist/lib/i18n/messages.en.js.map +1 -1
- package/package.json +3 -3
|
@@ -50,11 +50,19 @@
|
|
|
50
50
|
* current value holds a defined locked value is a `locked` problem, located at the holder,
|
|
51
51
|
* and the holder keeps its current value. A repeater whose items hold one cannot be
|
|
52
52
|
* shortened at all (items are matched by index). Translate reuses the judge
|
|
53
|
-
* ({@link withLockedFrom}, {@link lockedChanges})
|
|
53
|
+
* ({@link withLockedFrom}, {@link lockedChanges});
|
|
54
|
+
* - **the unreadable value** (EC-748, Codex r9 F44) — a stored value the traversal cannot
|
|
55
|
+
* read (a holder of the other cardinality after a schema edit, a repeater item that is no
|
|
56
|
+
* object, a body that is no v2 sequence, a body read without the prop lookup, …) is
|
|
57
|
+
* judged by its DECLARATION (`declarationMayHold`, failing closed on bodies), never read
|
|
58
|
+
* as empty: the model read WITHHOLDS it when it may hold an excluded value, and a write
|
|
59
|
+
* that would change or clear it is REFUSED when it may hold a locked one — until a non-AI
|
|
60
|
+
* save stores it in its canonical form. A value the MODEL writes in a shape its
|
|
61
|
+
* declaration does not read is refused too (nothing below it could be leashed or judged).
|
|
54
62
|
*
|
|
55
63
|
* Pure and deterministic; no provider, no I/O.
|
|
56
64
|
*/
|
|
57
|
-
import { carryAssetPlacements, childPositions, fieldPath, fieldTypeOf, fieldValuePosition, isSequenceValue, readAssetId, rebuildFromChildren, referenceNode, resolveDocumentTitle, seedObjectItem, sequenceComponentNodes, walkReferences, walkValuePositions, writerLockOf, } from '@elytracms/core/cms-core';
|
|
65
|
+
import { carryAssetPlacements, childPositions, declarationMayHold, fieldPath, fieldTypeOf, fieldValuePosition, isOpaquePosition, isSequenceValue, readAssetId, rebuildFromChildren, referenceNode, resolveDocumentTitle, seedObjectItem, selfTypeFieldValue, sequenceComponentNodes, walkReferences, walkValuePositions, writerLockOf, } from '@elytracms/core/cms-core';
|
|
58
66
|
import { parseRichTextValue, richTextValueToMarkdownProse } from '@elytracms/core/rich-text';
|
|
59
67
|
import { richTextValueFromModelInput } from './rich-text-input.js';
|
|
60
68
|
export const NO_HELD_REFERENCES = { documents: new Map(), assets: new Set() };
|
|
@@ -150,16 +158,65 @@ function childKey(parent, child) {
|
|
|
150
158
|
? `node:${child.component.nodeId}:${child.field.name}`
|
|
151
159
|
: `at:${JSON.stringify(child.at.slice(parent.at.length))}`;
|
|
152
160
|
}
|
|
161
|
+
/** {@link childKey} for a value the traversal cannot read, in its own key space (EC-748). */
|
|
162
|
+
function opaqueChildKey(parent, child) {
|
|
163
|
+
return child.opaque === 'not-a-static-prop' && child.component
|
|
164
|
+
? `opaque:node:${child.component.nodeId}:${child.declaration.name}`
|
|
165
|
+
: `opaque:at:${JSON.stringify(child.at.slice(parent.at.length))}`;
|
|
166
|
+
}
|
|
167
|
+
/** The model-facing name of an unreadable value: its declaration's name, located when nested. */
|
|
168
|
+
function opaqueSubject(position, root) {
|
|
169
|
+
const name = `"${position.declaration.name}"`;
|
|
170
|
+
return position.at.length === 0 ? name : `${name} (at ${label(root, position.at)})`;
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* What a writer is told about a value it wrote in a shape the declaration does not read
|
|
174
|
+
* (EC-748): nothing below it could be converted, leashed or judged for locks, so it is not
|
|
175
|
+
* written — said in the shape the field does take.
|
|
176
|
+
*/
|
|
177
|
+
function unreadableWriteMessage(position, root) {
|
|
178
|
+
const what = `Field ${opaqueSubject(position, root)}`;
|
|
179
|
+
switch (position.opaque) {
|
|
180
|
+
case 'cardinality':
|
|
181
|
+
return Array.isArray(position.value)
|
|
182
|
+
? `${what} holds one value, not a list — send the value itself.`
|
|
183
|
+
: `${what} holds a list — send a list.`;
|
|
184
|
+
case 'not-an-object':
|
|
185
|
+
return `${what} takes an object of its declared sub-fields.`;
|
|
186
|
+
case 'not-an-asset':
|
|
187
|
+
return `${what} takes asset id string(s) from listAssets results.`;
|
|
188
|
+
case 'not-a-sequence':
|
|
189
|
+
return `${what} takes rich text as markdown, or the stored { version: 2, content } object.`;
|
|
190
|
+
case 'not-a-static-prop':
|
|
191
|
+
return `${what} is a component prop stored as no { value } member; the assistant cannot write it.`;
|
|
192
|
+
case 'no-prop-declarations':
|
|
193
|
+
return `${what} holds components whose props cannot be judged here: the project's components are not available.`;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
153
196
|
/** Convert every child position of `pos` (its value already in a readable shape). */
|
|
154
197
|
function convertChildren(pos, current, ctx, problems, root) {
|
|
155
198
|
const positionCtx = ctx.lookupManifest ? { lookupManifest: ctx.lookupManifest } : {};
|
|
156
199
|
const currentChildren = new Map();
|
|
157
200
|
if (current !== undefined) {
|
|
158
201
|
for (const child of childPositions({ ...pos, value: current }, positionCtx)) {
|
|
159
|
-
|
|
202
|
+
if (isOpaquePosition(child))
|
|
203
|
+
currentChildren.set(opaqueChildKey(pos, child), child.value);
|
|
204
|
+
else
|
|
205
|
+
currentChildren.set(childKey(pos, child), child.value);
|
|
160
206
|
}
|
|
161
207
|
}
|
|
162
|
-
return rebuildFromChildren(pos, positionCtx, (child) => convert(child, currentChildren.get(childKey(pos, child)), ctx, problems, root, false)
|
|
208
|
+
return rebuildFromChildren(pos, positionCtx, (child) => convert(child, currentChildren.get(childKey(pos, child)), ctx, problems, root, false),
|
|
209
|
+
// EC-748: a value the model wrote in a shape its declaration does not read — nothing
|
|
210
|
+
// below it can be converted, leashed or judged for locks (a list written for a single
|
|
211
|
+
// object would reach a locked sub-field through the canonicaliser's unwrap). The stored
|
|
212
|
+
// value echoed back, unchanged, is no write; anything else is a problem, and not written.
|
|
213
|
+
(unreadable) => {
|
|
214
|
+
const stored = currentChildren.get(opaqueChildKey(pos, unreadable));
|
|
215
|
+
if (stored !== undefined && canonicalJson(stored) === canonicalJson(unreadable.value))
|
|
216
|
+
return unreadable.value;
|
|
217
|
+
problems.push({ at: unreadable.at, code: 'invalid-field-value', message: unreadableWriteMessage(unreadable, root) });
|
|
218
|
+
return undefined;
|
|
219
|
+
});
|
|
163
220
|
}
|
|
164
221
|
/**
|
|
165
222
|
* `isRoot` — whether `pos` is the write's root (the field, or the prop a node change
|
|
@@ -170,8 +227,18 @@ function convert(pos, current, ctx, problems, root, isRoot) {
|
|
|
170
227
|
// EC-716: a locked position keeps its current value — and so, by not being visited,
|
|
171
228
|
// does everything below it (the lock cascades as the form's `readOnly` does).
|
|
172
229
|
const lock = writerLockOf(pos.field);
|
|
173
|
-
if (lock === undefined)
|
|
174
|
-
|
|
230
|
+
if (lock === undefined) {
|
|
231
|
+
const written = convertWritable(pos, current, ctx, problems, root, isRoot);
|
|
232
|
+
// EC-748: a component prop is judged against its canonical form (THE drift issue, at prop
|
|
233
|
+
// depth), and a proposal's write does not re-canonicalise props (update-fields: it would
|
|
234
|
+
// rewrite every untouched node's wrappers) — so a prop the converter WRITES is stored in
|
|
235
|
+
// its canonical form here, by the write lens itself (`writeField` does it for a field): a
|
|
236
|
+
// multi-select's lone option becomes a list, an emptied list or sequence absent. A value
|
|
237
|
+
// kept as it is (`current`) is never rewritten.
|
|
238
|
+
if (pos.kind !== 'prop' || written === current || written === undefined)
|
|
239
|
+
return written;
|
|
240
|
+
return selfTypeFieldValue(pos.field, written, ctx.lookupManifest ? { lookupManifest: ctx.lookupManifest } : {});
|
|
241
|
+
}
|
|
175
242
|
if (pos.value === undefined || isEcho(pos, current, ctx, root))
|
|
176
243
|
return current;
|
|
177
244
|
problems.push({
|
|
@@ -211,10 +278,13 @@ function convertWritable(pos, current, ctx, problems, root, isRoot) {
|
|
|
211
278
|
(Array.isArray(next) ? next.length : 0) < current.length;
|
|
212
279
|
if (lost.length === 0 && !shortened)
|
|
213
280
|
return next;
|
|
281
|
+
const judged = lost.length > 0 ? lost : held;
|
|
214
282
|
problems.push({
|
|
215
283
|
at: pos.at,
|
|
216
284
|
code: 'invalid-field-value',
|
|
217
|
-
message:
|
|
285
|
+
message: judged.some((entry) => entry.opaque !== undefined)
|
|
286
|
+
? unreadableLockedMessage(subject(pos, root), root, judged)
|
|
287
|
+
: lockedRemovalMessage(subject(pos, root), root, judged, shortened),
|
|
218
288
|
locked: true,
|
|
219
289
|
});
|
|
220
290
|
return current;
|
|
@@ -224,6 +294,26 @@ function convertWritable(pos, current, ctx, problems, root, isRoot) {
|
|
|
224
294
|
function isPresent(value) {
|
|
225
295
|
return value !== undefined && value !== null;
|
|
226
296
|
}
|
|
297
|
+
/**
|
|
298
|
+
* The writer lock an unreadable value's declaration may hold (EC-748): `excluded` when it may
|
|
299
|
+
* hold an `ai.exclude` position, else `read-only` when it may hold a `form.readOnly` one, else
|
|
300
|
+
* none — over the declaration alone (`declarationMayHold`: a body may hold any component, so
|
|
301
|
+
* it may hold either; `excluded` wins, the stricter lock).
|
|
302
|
+
*/
|
|
303
|
+
export function declaredWriterLock(declaration) {
|
|
304
|
+
if (declarationMayHold(declaration, (field) => writerLockOf(field) === 'excluded'))
|
|
305
|
+
return 'excluded';
|
|
306
|
+
if (declarationMayHold(declaration, (field) => writerLockOf(field) === 'read-only'))
|
|
307
|
+
return 'read-only';
|
|
308
|
+
return undefined;
|
|
309
|
+
}
|
|
310
|
+
/** {@link lockedChildKey} for a value the traversal cannot read — its own key space (EC-748). */
|
|
311
|
+
function lockedOpaqueKey(parent, parentKey, child) {
|
|
312
|
+
if (child.opaque === 'not-a-static-prop' && child.component) {
|
|
313
|
+
return `opaque:node:${child.component.nodeId}:${child.declaration.name}`;
|
|
314
|
+
}
|
|
315
|
+
return `${parentKey}/opaque:${JSON.stringify(child.at.slice(parent.at.length))}`;
|
|
316
|
+
}
|
|
227
317
|
/**
|
|
228
318
|
* The key matching a child position across two values of one position: a component prop by
|
|
229
319
|
* its NODE (ids are unique in a document and survive a prose edit that shifts indices), an
|
|
@@ -253,20 +343,48 @@ function lockedPositionsUnder(position, positionCtx) {
|
|
|
253
343
|
out.set(key, { value: pos.value, lock, at: pos.at });
|
|
254
344
|
return;
|
|
255
345
|
}
|
|
256
|
-
for (const child of childPositions(pos, positionCtx))
|
|
257
|
-
|
|
346
|
+
for (const child of childPositions(pos, positionCtx)) {
|
|
347
|
+
if (!isOpaquePosition(child)) {
|
|
348
|
+
visit(child, lockedChildKey(pos, key, child));
|
|
349
|
+
continue;
|
|
350
|
+
}
|
|
351
|
+
// EC-748: what cannot be read is judged by its declaration — never "nothing locked here".
|
|
352
|
+
const lock = declaredWriterLock(child.declaration);
|
|
353
|
+
if (lock !== undefined)
|
|
354
|
+
out.set(lockedOpaqueKey(pos, key, child), { value: child.value, lock, at: child.at, opaque: child.opaque });
|
|
355
|
+
}
|
|
258
356
|
};
|
|
259
357
|
visit(position, '');
|
|
260
358
|
return out;
|
|
261
359
|
}
|
|
262
|
-
/**
|
|
360
|
+
/** Whether `entry` sits in `other` unchanged at `key` (a defined value, the same JSON). */
|
|
361
|
+
function keptAt(other, key, entry) {
|
|
362
|
+
const now = other.get(key);
|
|
363
|
+
return now !== undefined && isPresent(now.value) && canonicalJson(now.value) === canonicalJson(entry.value);
|
|
364
|
+
}
|
|
365
|
+
/**
|
|
366
|
+
* The defined locked values of `before` that `after` does not hold, unchanged, at their key —
|
|
367
|
+
* and (EC-748) every unreadable value that may hold one on EITHER side and is not on the other
|
|
368
|
+
* unchanged: what it holds cannot be compared, so a change to it (or one written where none
|
|
369
|
+
* was) is counted lost — fail closed.
|
|
370
|
+
*/
|
|
263
371
|
function lockedLosses(before, after) {
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
372
|
+
const lost = [...before].flatMap(([key, entry]) => (isPresent(entry.value) && !keptAt(after, key, entry) ? [entry] : []));
|
|
373
|
+
const written = [...after].flatMap(([key, entry]) => entry.opaque !== undefined && isPresent(entry.value) && !keptAt(before, key, entry) ? [entry] : []);
|
|
374
|
+
return [...lost, ...written];
|
|
375
|
+
}
|
|
376
|
+
/**
|
|
377
|
+
* What a writer is told about a write that would change or clear a value stored in a shape
|
|
378
|
+
* its declaration cannot read, which may hold locked content (EC-748), located at the holder
|
|
379
|
+
* the model wrote. Its content is never named (it cannot be read — and excluded content may
|
|
380
|
+
* not be disclosed, EC-741); the repair is a save outside the assistant.
|
|
381
|
+
*/
|
|
382
|
+
export function unreadableLockedMessage(holder, root, locked) {
|
|
383
|
+
const excluded = locked.some((entry) => entry.lock === 'excluded');
|
|
384
|
+
const where = locked.filter((entry) => entry.opaque !== undefined && entry.at.length > 0).map((entry) => label(root, entry.at));
|
|
385
|
+
const at = !excluded && where.length > 0 ? ` (${where.join(', ')})` : '';
|
|
386
|
+
const what = excluded ? 'content the assistant must not change' : 'read-only content';
|
|
387
|
+
return `Field ${holder} holds a value stored in a shape its declaration cannot read${at} — it was saved under an earlier schema or written past the editor — and it may hold ${what}; the assistant cannot change, replace or clear it. Repair it outside the assistant: save the field again in the editor, in the shape its declaration reads; then the assistant can change its other values.`;
|
|
270
388
|
}
|
|
271
389
|
/**
|
|
272
390
|
* What a writer is told about a write that would remove locked content (EC-738), located at
|
|
@@ -295,7 +413,9 @@ export function withLockedFrom(position, current, ctx) {
|
|
|
295
413
|
const rebuild = (pos, key) => {
|
|
296
414
|
if (writerLockOf(pos.field) !== undefined)
|
|
297
415
|
return held.has(key) ? held.get(key).value : pos.value;
|
|
298
|
-
|
|
416
|
+
// EC-748: a value `position` holds unreadably is kept as it is — nothing below it can be
|
|
417
|
+
// matched by key — and {@link lockedChanges} counts it lost unless `current` holds it too.
|
|
418
|
+
return rebuildFromChildren(pos, positionCtx, (child) => rebuild(child, lockedChildKey(pos, key, child)), (unreadable) => unreadable.value);
|
|
299
419
|
};
|
|
300
420
|
return rebuild(position, '');
|
|
301
421
|
}
|
|
@@ -303,7 +423,10 @@ export function withLockedFrom(position, current, ctx) {
|
|
|
303
423
|
* The locked values a write from `position.value` (the CURRENT value) to `next` would lose or
|
|
304
424
|
* create, judged both ways (EC-738, translate): `lost` — a defined locked value `next` does
|
|
305
425
|
* not hold unchanged; `created` — a defined locked value `next` holds where the current value
|
|
306
|
-
* holds none.
|
|
426
|
+
* holds none. EC-748: a value stored in a shape its declaration cannot read, which may hold a
|
|
427
|
+
* locked value, on EITHER side and not on the other unchanged, is `lost` (fail closed) — so a
|
|
428
|
+
* drifted target keeps its value whole, and a drifted source never copies its locked values
|
|
429
|
+
* across.
|
|
307
430
|
*/
|
|
308
431
|
export function lockedChanges(position, next, ctx) {
|
|
309
432
|
const positionCtx = ctx.lookupManifest ? { lookupManifest: ctx.lookupManifest } : {};
|
|
@@ -311,7 +434,7 @@ export function lockedChanges(position, next, ctx) {
|
|
|
311
434
|
const after = lockedPositionsUnder({ ...position, value: next }, positionCtx);
|
|
312
435
|
return {
|
|
313
436
|
lost: lockedLosses(before, after),
|
|
314
|
-
created: [...after].flatMap(([key, entry]) => isPresent(entry.value) && !isPresent(before.get(key)?.value) ? [entry] : []),
|
|
437
|
+
created: [...after].flatMap(([key, entry]) => entry.opaque === undefined && isPresent(entry.value) && !isPresent(before.get(key)?.value) ? [entry] : []),
|
|
315
438
|
};
|
|
316
439
|
}
|
|
317
440
|
/**
|
|
@@ -421,7 +544,11 @@ function convertValue(pos, current, ctx, problems, root) {
|
|
|
421
544
|
return undefined;
|
|
422
545
|
// EC-558 / 2c: the ONE carry rule — a held placement keeps its per-use sub-fields,
|
|
423
546
|
// a swap carries them to the new image, a new id is an envelope.
|
|
424
|
-
const
|
|
547
|
+
const carriedAsStored = carryAssetPlacements(current, asset.multiple === true ? { multiple: true, ids: kept.map((e) => e.id) } : { multiple: false, id: kept[0].id });
|
|
548
|
+
// EC-748: the carry hands back a single field's stored one-item list as it is (same id);
|
|
549
|
+
// the WRITTEN value takes the declared cardinality — the model wrote one value. (Whether
|
|
550
|
+
// replacing that unreadable stored list is allowed is the lock judge's question.)
|
|
551
|
+
const carried = asset.multiple !== true && Array.isArray(carriedAsStored) ? carriedAsStored[0] : carriedAsStored;
|
|
425
552
|
// The writer's own sub-field values win over the carried ones.
|
|
426
553
|
const withWriter = (entry, index) => {
|
|
427
554
|
const rest = kept[index]?.rest ?? {};
|
|
@@ -503,6 +630,8 @@ export function fromWriterFormAtNode(field, displayed, nodeId, prop, raw, ctx, l
|
|
|
503
630
|
let targetLock;
|
|
504
631
|
const declared = [];
|
|
505
632
|
let componentId;
|
|
633
|
+
// EC-748: the target prop stored as no static member — declared, but unreadable.
|
|
634
|
+
let unreadableTarget = false;
|
|
506
635
|
const visit = (position, inherited) => {
|
|
507
636
|
const lock = writerLockOf(position.field) ?? inherited;
|
|
508
637
|
if (position.kind === 'prop' && position.component?.nodeId === nodeId) {
|
|
@@ -513,10 +642,29 @@ export function fromWriterFormAtNode(field, displayed, nodeId, prop, raw, ctx, l
|
|
|
513
642
|
targetLock = lock;
|
|
514
643
|
}
|
|
515
644
|
}
|
|
516
|
-
for (const child of childPositions(position, positionCtx))
|
|
517
|
-
|
|
645
|
+
for (const child of childPositions(position, positionCtx)) {
|
|
646
|
+
if (!isOpaquePosition(child)) {
|
|
647
|
+
visit(child, lock);
|
|
648
|
+
continue;
|
|
649
|
+
}
|
|
650
|
+
// EC-748: a node inside a value the traversal cannot read is not addressable — it is not
|
|
651
|
+
// found (the write never reaches into an unreadable holder); a prop of the node stored as
|
|
652
|
+
// no static member is declared but cannot be written.
|
|
653
|
+
if (child.opaque === 'not-a-static-prop' && child.component?.nodeId === nodeId) {
|
|
654
|
+
componentId = child.component.componentId;
|
|
655
|
+
declared.push(child.declaration.name);
|
|
656
|
+
if (child.declaration.name === prop)
|
|
657
|
+
unreadableTarget = true;
|
|
658
|
+
}
|
|
659
|
+
}
|
|
518
660
|
};
|
|
519
661
|
visit(root, undefined);
|
|
662
|
+
if (!target && unreadableTarget) {
|
|
663
|
+
return {
|
|
664
|
+
ok: false,
|
|
665
|
+
message: `Prop "${prop}" of component "${nodeId}" (${componentId}) is stored as no { value } member (a retired shape); the assistant cannot change it. Repair it outside the assistant: set the prop again in the editor.`,
|
|
666
|
+
};
|
|
667
|
+
}
|
|
520
668
|
if (target && targetLock !== undefined) {
|
|
521
669
|
return {
|
|
522
670
|
ok: false,
|
|
@@ -539,7 +687,7 @@ export function fromWriterFormAtNode(field, displayed, nodeId, prop, raw, ctx, l
|
|
|
539
687
|
// The prop is the write's ROOT (an omitted value clears it); messages locate positions
|
|
540
688
|
// from the top-level field (`body.content[0].props.group.locked`).
|
|
541
689
|
const { value: converted, problems } = convertRoot({ ...found, value: raw }, found.value, ctx, field.name);
|
|
542
|
-
const rebuild = (position) => rebuildFromChildren(position, positionCtx, (child) => (isTarget(child) ? converted : rebuild(child)));
|
|
690
|
+
const rebuild = (position) => rebuildFromChildren(position, positionCtx, (child) => (isTarget(child) ? converted : rebuild(child)), (unreadable) => unreadable.value);
|
|
543
691
|
const value = rebuild(root);
|
|
544
692
|
return found.exact
|
|
545
693
|
? { ok: true, value, path: found.path, propValue: converted, exact: true, problems }
|
|
@@ -574,8 +722,11 @@ export function textPositionsOf(position, ctx) {
|
|
|
574
722
|
out.push(current);
|
|
575
723
|
if (current.field.type === 'text')
|
|
576
724
|
return;
|
|
725
|
+
// EC-748: a value the traversal cannot read yields no unit — its text cannot be found
|
|
726
|
+
// (and translate skips a bucket where one may hold locked content: `lockedChanges`).
|
|
577
727
|
for (const child of childPositions(current, positionCtx))
|
|
578
|
-
|
|
728
|
+
if (!isOpaquePosition(child))
|
|
729
|
+
visit(child);
|
|
579
730
|
};
|
|
580
731
|
visit(position);
|
|
581
732
|
return out;
|
|
@@ -595,7 +746,8 @@ export function rebuildTextPositions(position, ctx, replace) {
|
|
|
595
746
|
if (current.field.type === 'text')
|
|
596
747
|
return replace(current);
|
|
597
748
|
const value = isTextPosition(current) ? replace(current) : current.value;
|
|
598
|
-
|
|
749
|
+
// EC-748: an unreadable value comes back as it was (it held no unit).
|
|
750
|
+
return rebuildFromChildren({ ...current, value }, positionCtx, rebuild, (unreadable) => unreadable.value);
|
|
599
751
|
};
|
|
600
752
|
return rebuild(position);
|
|
601
753
|
}
|
|
@@ -618,6 +770,13 @@ export function rebuildTextPositions(position, ctx, replace) {
|
|
|
618
770
|
* ({@link modelFacingTitle}) and the echo checks (a value the model read and sent back is
|
|
619
771
|
* no change) — so what the model can see and what it can echo are one declaration. The raw
|
|
620
772
|
* stored document stays local: proposals are always built and validated against it.
|
|
773
|
+
*
|
|
774
|
+
* EC-748 (Codex r9 F44): a stored value the traversal cannot read (a holder of the other
|
|
775
|
+
* cardinality after a schema edit, a repeater item that is no object, a body that is no v2
|
|
776
|
+
* sequence or is read without the prop lookup, …) is WITHHELD — absent where it sits: the
|
|
777
|
+
* field, the list entry, the prop — whenever its declaration may hold an excluded position
|
|
778
|
+
* (`declaredWriterLock`, failing closed on bodies). What cannot be read cannot be projected,
|
|
779
|
+
* so it is never sent whole. One that may hold none reads as stored.
|
|
621
780
|
*/
|
|
622
781
|
export function toWriterForm(position, ctx) {
|
|
623
782
|
if (writerLockOf(position.field) === 'excluded')
|
|
@@ -632,7 +791,7 @@ export function toWriterForm(position, ctx) {
|
|
|
632
791
|
if (markdown !== undefined)
|
|
633
792
|
return markdown;
|
|
634
793
|
}
|
|
635
|
-
return rebuildFromChildren(position, positionCtx, (child) => toWriterForm(child, ctx));
|
|
794
|
+
return rebuildFromChildren(position, positionCtx, (child) => toWriterForm(child, ctx), (unreadable) => (declaredWriterLock(unreadable.declaration) === 'excluded' ? undefined : unreadable.value));
|
|
636
795
|
}
|
|
637
796
|
/**
|
|
638
797
|
* The components a document holds whose props a writer may change — every component
|
|
@@ -650,7 +809,9 @@ export function componentIdsOf(collection, document, lookupManifest) {
|
|
|
650
809
|
walkValuePositions(fieldValuePosition(field, value), { lookupManifest }, (position) => {
|
|
651
810
|
if (position.kind === 'prop' && position.component)
|
|
652
811
|
ids.add(position.component.componentId);
|
|
653
|
-
}
|
|
812
|
+
},
|
|
813
|
+
// EC-748: a component inside a value the traversal cannot read has no prop a writer can change.
|
|
814
|
+
() => { });
|
|
654
815
|
}
|
|
655
816
|
};
|
|
656
817
|
visitBucket(document.values);
|