rapier-markdown-kit 1.1.102 → 1.1.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.
Files changed (60) hide show
  1. package/LICENSE +2 -2
  2. package/README.md +213 -53
  3. package/dist/agent/vendor/hyphen-en-us/LICENSE +14 -0
  4. package/dist/agent/vendor/hyphen-en-us/SOURCE.json +33 -0
  5. package/dist/agent/vendor/hyphen-en-us/hyph-en-us.hyp.txt +14 -0
  6. package/dist/agent/vendor/hyphen-en-us/hyph-en-us.pat.txt +4938 -0
  7. package/dist/agent/vendor/hyphen-en-us/patterns.js +25 -0
  8. package/dist/agent/vendor/pretext/LICENSE +208 -0
  9. package/dist/agent/vendor/pretext/NOTICE +26 -13
  10. package/dist/agent/vendor/pretext/SOURCE.json +213 -45
  11. package/dist/agent/vendor/pretext/analysis.js +675 -1224
  12. package/dist/agent/vendor/pretext/entry-geometry.js +183 -0
  13. package/dist/agent/vendor/pretext/gecko-line-breaks.js +688 -0
  14. package/dist/agent/vendor/pretext/generated/engine-break-data.js +57 -0
  15. package/dist/agent/vendor/pretext/generated/webkit-generic-families.js +80 -0
  16. package/dist/agent/vendor/pretext/graphemes.js +98 -0
  17. package/dist/agent/vendor/pretext/han-kerning.js +253 -0
  18. package/dist/agent/vendor/pretext/layout.js +352 -290
  19. package/dist/agent/vendor/pretext/line-break.js +1154 -588
  20. package/dist/agent/vendor/pretext/line-breaks.js +892 -0
  21. package/dist/agent/vendor/pretext/line-text.js +81 -39
  22. package/dist/agent/vendor/pretext/measurement.js +1105 -176
  23. package/dist/agent/vendor/pretext/prepare.js +807 -0
  24. package/dist/agent/vendor/pretext/rich-inline.js +1242 -273
  25. package/dist/agent/will.mjs +2 -0
  26. package/dist/kit/assets.mjs +5 -1
  27. package/dist/kit/conformance/run.mjs +4 -4
  28. package/dist/kit/export-fonts.mjs +26 -11
  29. package/dist/kit/index.mjs +1 -0
  30. package/dist/kit/ledger/carried.mjs +9 -7
  31. package/dist/kit/ledger/data.mjs +73 -13
  32. package/dist/kit/ledger/format.mjs +166 -53
  33. package/dist/kit/ledger/hash.mjs +75 -18
  34. package/dist/kit/ledger/history.mjs +143 -115
  35. package/dist/kit/ledger/inverse-source.mjs +158 -19
  36. package/dist/kit/ledger/journal-records.mjs +292 -67
  37. package/dist/kit/model.mjs +1 -1
  38. package/dist/kit/pdf-glyphs.mjs +4 -4
  39. package/dist/kit/render-markdown.mjs +259 -122
  40. package/dist/kit/render-sanitize.mjs +24 -4
  41. package/dist/kit/render-work.mjs +99 -20
  42. package/dist/kit/render.mjs +101 -65
  43. package/dist/kit/typography.mjs +2 -0
  44. package/dist/layout/hyphenation.mjs +127 -0
  45. package/dist/layout/line-plan.mjs +88 -96
  46. package/dist/layout/model.mjs +61 -80
  47. package/dist/layout/paragraph-fit.mjs +191 -0
  48. package/dist/layout/preparation.mjs +373 -0
  49. package/dist/layout/prepared-paragraph.mjs +223 -0
  50. package/dist/layout/typography.mjs +345 -111
  51. package/dist/packages/rapier-diff/index.mjs +288 -196
  52. package/dist/spec/document-fonts.mjs +13 -4
  53. package/dist/spec/md-assets.mjs +149 -30
  54. package/dist/spec/md-layout.mjs +5 -2
  55. package/dist/spec/md-marks.mjs +218 -3
  56. package/dist/spec/md-source.mjs +122 -0
  57. package/package.json +2 -1
  58. package/style.css +9 -0
  59. package/dist/agent/vendor/pretext/bidi.js +0 -151
  60. package/dist/agent/vendor/pretext/generated/bidi-data.js +0 -3831
@@ -111,16 +111,47 @@ function _rapierValidAuthored(origin) {
111
111
  !/[\uD800-\uDFFF]/u.test(row.removed) && !/[\uD800-\uDFFF]/u.test(row.inserted));
112
112
  }
113
113
 
114
+ // The lookup is an observation of one call's supplied records. Material rows keep
115
+ // their existing borrowed-row API; an inverse owns fresh rows. Navigation only
116
+ // follows strictly earlier revisions, including when an unadmitted list is unordered.
117
+ function _rapierRecordIndex(records) {
118
+ const byId = new Map(), splices = new Map(), metadata = new Map();
119
+ for (const record of records) {
120
+ const id = record?.transaction?.id;
121
+ if (!byId.has(id)) byId.set(id, []);
122
+ byId.get(id).push(record);
123
+ }
124
+ const resolve = (entry, effects) => {
125
+ const cache = effects ? metadata : splices, path = [], pending = new Set();
126
+ let current = entry, value;
127
+ while (true) {
128
+ if (cache.has(current)) { value = cache.get(current); break; }
129
+ if (!current || pending.has(current)) { value = effects ? undefined : null; break; }
130
+ if (effects && Object.hasOwn(current, 'metadata')) {
131
+ value = _rapierValidMetadataEffect(current.metadata) ? current.metadata : undefined; break;
132
+ }
133
+ if (Array.isArray(current.splices)) { value = effects ? null : current.splices; break; }
134
+ const tx = current.transaction, id = tx?.reverts || tx?.reapplies;
135
+ const target = id && byId.get(id)?.find(record => record?.transaction?.revision < tx.revision);
136
+ if (!target) { value = effects ? undefined : null; break; }
137
+ pending.add(current); path.push(current); current = target;
138
+ }
139
+ cache.set(current, value);
140
+ for (let i = path.length - 1; i >= 0; i--) {
141
+ if (path[i].transaction.reverts && value != null) value = effects
142
+ ? Object.fromEntries(Object.entries(value).map(([key, pair]) => [key, {before: pair.after, after: pair.before}]))
143
+ : value.slice().reverse().map(row => ({pos: row.pos, removed: row.inserted, inserted: row.removed}));
144
+ cache.set(path[i], value);
145
+ }
146
+ return value;
147
+ };
148
+ return {byId, splices: record => resolve(record, false), metadata: record => resolve(record, true)};
149
+ }
150
+
114
151
  function _rapierRecordSplices(entry, ledger) {
115
152
  try {
116
153
  if (entry && Array.isArray(entry.splices)) return entry.splices;
117
- const tx = entry?.transaction, targetId = tx?.reverts || tx?.reapplies;
118
- const prior = (ledger || []).filter(record => record?.transaction?.revision < tx?.revision);
119
- const target = targetId && prior.find(record => record?.transaction?.id === targetId);
120
- if (!target) return null;
121
- const rows = _rapierRecordSplices(target, prior);
122
- if (!rows) return null;
123
- return tx.reverts ? rows.slice().reverse().map(row => ({pos: row.pos, removed: row.inserted, inserted: row.removed})) : rows;
154
+ return _rapierRecordIndex(ledger || []).splices(entry);
124
155
  } catch (_) { return null; }
125
156
  }
126
157
 
@@ -128,16 +159,18 @@ function _rapierRecordSplices(entry, ledger) {
128
159
  function journalOperations(before, after, journal) {
129
160
  if (typeof before !== 'string' || typeof after !== 'string' || !Array.isArray(journal)) return;
130
161
  if (before === after) return [];
131
- let text = after;
132
- const groups = [];
133
- for (let index = journal.length - 1; index >= 0; index--) {
134
- const rows = _rapierRecordSplices(journal[index], journal);
135
- const prior = _rapierTransformSplices(text, rows, true);
136
- if (prior === null) return;
137
- groups.push(rows);
138
- if (prior === before) return groups.reverse().flat().map(({pos, removed, inserted}) => ({pos, removed, inserted}));
139
- text = prior;
140
- }
162
+ try {
163
+ let text = after;
164
+ const lookup = _rapierRecordIndex(journal), groups = [];
165
+ for (let index = journal.length - 1; index >= 0; index--) {
166
+ const rows = lookup.splices(journal[index]);
167
+ const prior = _rapierTransformSplices(text, rows, true);
168
+ if (prior === null) return;
169
+ groups.push(rows);
170
+ if (prior === before) return groups.reverse().flat().map(({pos, removed, inserted}) => ({pos, removed, inserted}));
171
+ text = prior;
172
+ }
173
+ } catch (_) { return; }
141
174
  }
142
175
 
143
176
  function _rapierRecordMetadata(entry, ledger) {
@@ -145,20 +178,13 @@ function _rapierRecordMetadata(entry, ledger) {
145
178
  if (Object.hasOwn(entry, 'metadata')) return _rapierValidMetadataEffect(entry.metadata) ? entry.metadata : undefined;
146
179
  // An expanded record owns its complete effect; only compact navigation inherits one.
147
180
  if (Array.isArray(entry.splices)) return null;
148
- const tx = entry.transaction, targetId = tx.reverts || tx.reapplies;
149
- const prior = (ledger || []).filter(record => record?.transaction?.revision < tx.revision);
150
- const target = targetId && prior.find(record => record?.transaction?.id === targetId);
151
- if (!target) return undefined;
152
- const effect = _rapierRecordMetadata(target, prior);
153
- if (effect == null) return effect;
154
- return tx.reverts ? Object.fromEntries(Object.entries(effect).map(([key, pair]) =>
155
- [key, {before: pair.after, after: pair.before}])) : effect;
181
+ return _rapierRecordIndex(ledger || []).metadata(entry);
156
182
  } catch (_) { return undefined; }
157
183
  }
158
184
 
159
185
  // A codec replacement owns only one inserted definition and its reference. Its
160
186
  // source act remains the picture's author; an arbitrary edit cannot claim upkeep.
161
- function _rapierPictureFinish(record, original, earlier) {
187
+ function _rapierPictureFinish(record, original, earlier, lookup) {
162
188
  const tx = record?.transaction, rows = record?.splices;
163
189
  if (tx?.operation !== 'document.finish-picture' || !tx.sourceTransactionId || tx.sourceTransactionIds ||
164
190
  tx.reverts || tx.reapplies || record.metadata || !Array.isArray(rows) || rows.length !== 2 ||
@@ -192,7 +218,7 @@ function _rapierPictureFinish(record, original, earlier) {
192
218
  };
193
219
  for (const prior of rows.slice(0, index).reverse()) if (cross(prior, false)) return false;
194
220
  for (let at = earlier.length - 1; at >= 0; at--) {
195
- const prior = earlier[at], source = _rapierRecordSplices(prior, earlier);
221
+ const prior = earlier[at], source = lookup.splices(prior);
196
222
  if (!source) return false;
197
223
  const owner = prior === original || prior.transaction?.reapplies === original.transaction.id;
198
224
  for (const splice of source.slice().reverse()) {
@@ -207,7 +233,7 @@ function _rapierPictureFinish(record, original, earlier) {
207
233
  // Every active inverse keeps its original targets suppressed. Independent inverses
208
234
  // can share a target; withdrawing one restores it only after the last blocker leaves.
209
235
  // This includes assignments hidden by later choices. No second history is retained.
210
- function _rapierHistoryEffects(records, visit = null) {
236
+ function _rapierWalkHistoryEffects(records, lookup, visit = null, observe = null) {
211
237
  try {
212
238
  if (!Array.isArray(records)) return {ok: false};
213
239
  const nodes = new Map(), references = new Map(), pictures = new Map(), byId = new Map(), finished = new Set(), earlier = [];
@@ -221,6 +247,25 @@ function _rapierHistoryEffects(records, visit = null) {
221
247
  if (was !== node.active) for (const ref of node.references) pending.push([ref, node.active ? 1 : -1]);
222
248
  }
223
249
  };
250
+ // Most admission needs only the final effect graph. A backwards pass proves it
251
+ // in linear work; prefix activity is asked only by native Redo, picture custody,
252
+ // or a metadata assignment. The explicit stack also bounds long inverse chains.
253
+ const activity = new Map();
254
+ const active = id => {
255
+ if (visit) return nodes.get(id).active;
256
+ const pending = [[id, 0]];
257
+ while (pending.length) {
258
+ const frame = pending.at(-1), target = frame[0];
259
+ if (activity.has(target)) { pending.pop(); continue; }
260
+ const dependents = nodes.get(target).dependents;
261
+ if (frame[1] === dependents.length) { activity.set(target, true); pending.pop(); continue; }
262
+ const next = dependents[frame[1]];
263
+ if (!activity.has(next)) pending.push([next, 0]);
264
+ else if (activity.get(next)) { activity.set(target, false); pending.pop(); }
265
+ else frame[1]++;
266
+ }
267
+ return activity.get(id);
268
+ };
224
269
  const lastUndo = new Map(), contexts = new Map(), order = [];
225
270
  // Native Redo names an original act. Its inverse target is the last Undo
226
271
  // in the author's proved causal view, not an unrelated later arrival.
@@ -256,7 +301,8 @@ function _rapierHistoryEffects(records, visit = null) {
256
301
  let refs = supplied;
257
302
  if (tx.operation === 'document.finish-picture' && tx.sourceTransactionId) {
258
303
  const owner = tx.sourceTransactionId;
259
- if (!nodes.get(owner)?.active || finished.has(owner) || !_rapierPictureFinish(record, byId.get(owner), earlier)) return {ok: false};
304
+ if (!nodes.has(owner) || !active(owner) || finished.has(owner) ||
305
+ !_rapierPictureFinish(record, byId.get(owner), earlier, lookup ||= _rapierRecordIndex(records))) return {ok: false};
260
306
  pictures.set(tx.id, owner); finished.add(owner); refs = [];
261
307
  }
262
308
  if (tx.reverts || tx.reapplies) {
@@ -266,16 +312,19 @@ function _rapierHistoryEffects(records, visit = null) {
266
312
  else {
267
313
  const context = contextOf(record.authored || tx.authored);
268
314
  const undo = (lastUndo.get(target) || []).findLast(id => context
269
- ? context.known.has(id) && !context.inactive.has(id) : nodes.get(id).active);
315
+ ? context.known.has(id) && !context.inactive.has(id) : active(id));
270
316
  if (!undo) return {ok: false};
271
317
  refs = [undo];
272
318
  }
273
319
  }
274
320
  if (refs.some(id => typeof id !== 'string' || !nodes.has(id) || pictures.has(id))) return {ok: false};
275
- nodes.set(tx.id, {active: true, blockers: 0, references: refs}); references.set(tx.id, refs.slice());
276
- byId.set(tx.id, record);
277
- earlier.push(record);
278
- for (const ref of refs) update(ref, 1);
321
+ nodes.set(tx.id, {active: true, blockers: 0, references: refs, dependents: []}); references.set(tx.id, refs.slice());
322
+ byId.set(tx.id, record); earlier.push(record);
323
+ for (const ref of refs) {
324
+ nodes.get(ref).dependents.push(tx.id);
325
+ if (visit) update(ref, 1);
326
+ }
327
+ activity.clear();
279
328
  const origin = record.authored || tx.authored;
280
329
  contexts.set(tx.id, {index: order.length, basis: origin?.basis || null}); order.push(tx.id);
281
330
  if (tx.reverts) {
@@ -283,55 +332,219 @@ function _rapierHistoryEffects(records, visit = null) {
283
332
  lastUndo.get(tx.reverts).push(tx.id);
284
333
  }
285
334
  if (visit) visit(record, nodes, refs);
335
+ if (observe) observe(record, refs, active);
336
+ }
337
+ if (!visit) for (let i = order.length - 1; i >= 0; i--) {
338
+ const node = nodes.get(order[i]); node.active = node.blockers === 0;
339
+ if (node.active) for (const ref of node.references) nodes.get(ref).blockers++;
286
340
  }
287
341
  const effectiveActIds = [...nodes].filter(([id, node]) => node.active && !pictures.has(id)).map(([id]) => id);
288
342
  return {ok: true, effectiveActIds, active: new Set(effectiveActIds), references, pictures};
289
343
  } catch (_) { return {ok: false}; }
290
344
  }
291
345
 
346
+ function _rapierHistoryEffects(records, visit = null) {
347
+ return _rapierWalkHistoryEffects(records, null, visit);
348
+ }
349
+
350
+ // Every transition constrains the two initial metadata scalars. This proves each
351
+ // inverse's result once, before the caller supplies the exact initial metadata.
352
+ function _rapierMetadataProof(lookup) {
353
+ const values = {}, required = {}, assignments = {filename: [], docKind: []};
354
+ const requireInitial = (key, value) => {
355
+ if (Object.hasOwn(required, key) && required[key] !== value) throw Error('metadata_effect');
356
+ required[key] = value;
357
+ };
358
+ const observe = (record, refs, active) => {
359
+ const effect = lookup.metadata(record);
360
+ if (effect === undefined) throw Error('metadata');
361
+ for (const [key, pair] of Object.entries(effect || {})) {
362
+ if (!Object.hasOwn(values, key)) requireInitial(key, pair.before);
363
+ else if (values[key] !== pair.before) throw Error('metadata');
364
+ values[key] = pair.after;
365
+ if (!refs.length) assignments[key].push({id: record.transaction.id, value: pair.after});
366
+ }
367
+ // Source-only rows cannot change a previously proved metadata assignment.
368
+ if (!effect && !refs.length) return;
369
+ for (const key of ['filename', 'docKind']) {
370
+ const expected = assignments[key].findLast(row => active(row.id));
371
+ if (expected) {
372
+ if (!Object.hasOwn(values, key)) requireInitial(key, expected.value);
373
+ else if (values[key] !== expected.value) throw Error('metadata_effect');
374
+ } else if (Object.hasOwn(values, key)) requireInitial(key, values[key]);
375
+ }
376
+ };
377
+ const replay = initial => {
378
+ if (!_rapierValidMetadata(initial) || Object.keys(required).some(key => initial[key] !== required[key])) return null;
379
+ return {...initial, ...values};
380
+ };
381
+ return {observe, replay};
382
+ }
383
+
292
384
  function _rapierMetadataState(initial, records, effects = _rapierHistoryEffects(records)) {
293
385
  if (!_rapierValidMetadata(initial) || !effects.ok) return null;
294
- const state = {...initial};
295
- for (const record of records) if (effects.active.has(record.transaction.id) && !effects.references.get(record.transaction.id)?.length) {
296
- const effect = _rapierRecordMetadata(record, records);
297
- if (effect === undefined) return null;
298
- for (const [key, pair] of Object.entries(effect || {})) state[key] = pair.after;
299
- }
300
- return state;
386
+ try {
387
+ const state = {...initial}, lookup = _rapierRecordIndex(records);
388
+ for (const record of records) if (effects.active.has(record.transaction.id) && !effects.references.get(record.transaction.id)?.length) {
389
+ const effect = lookup.metadata(record);
390
+ if (effect === undefined) return null;
391
+ for (const [key, pair] of Object.entries(effect || {})) state[key] = pair.after;
392
+ }
393
+ return state;
394
+ } catch (_) { return null; }
301
395
  }
302
396
 
303
397
  // Prove each recorded transition and each inverse's semantic result, not only the final values.
304
398
  function _rapierReplayMetadata(initial, records) {
305
399
  if (!_rapierValidMetadata(initial)) return null;
306
- let state = {...initial};
307
- const earlier = [], assignments = {filename: [], docKind: []};
308
- const effects = _rapierHistoryEffects(records, (record, nodes, refs) => {
309
- const effect = _rapierRecordMetadata(record, earlier);
310
- if (effect === undefined) throw Error('metadata');
311
- state = _rapierTransformMetadata(state, effect);
312
- if (!state) throw Error('metadata');
313
- if (!refs.length) for (const [key, pair] of Object.entries(effect || {}))
314
- assignments[key].push({id: record.transaction.id, value: pair.after});
315
- for (const key of ['filename', 'docKind']) {
316
- let expected = initial[key];
317
- for (let i = assignments[key].length - 1; i >= 0; i--) if (nodes.get(assignments[key][i].id).active) {
318
- expected = assignments[key][i].value; break;
319
- }
320
- if (state[key] !== expected) throw Error('metadata_effect');
321
- }
322
- earlier.push(record);
323
- });
324
- return effects.ok ? state : null;
400
+ try {
401
+ const lookup = _rapierRecordIndex(records), proof = _rapierMetadataProof(lookup);
402
+ return _rapierWalkHistoryEffects(records, lookup, null, proof.observe).ok ? proof.replay(initial) : null;
403
+ } catch (_) { return null; }
325
404
  }
326
405
 
327
406
  function _rapierHasHistoryEffect(record, earlier = []) {
328
407
  const tx = record?.transaction;
329
408
  if (!tx || !(tx.reverts || tx.reapplies || tx.sourceTransactionId || tx.sourceTransactionIds?.length)) return false;
330
409
  let id = tx.id;
331
- if (!id) { id = 'candidate'; while (earlier.some(row => row?.transaction?.id === id)) id += ':'; }
410
+ if (!id) {
411
+ const ids = new Set(earlier.map(row => row?.transaction?.id));
412
+ id = 'candidate'; while (ids.has(id)) id += ':';
413
+ }
332
414
  return _rapierHistoryEffects([...earlier, {...record, transaction: {...tx, id}}]).ok;
333
415
  }
334
416
 
417
+ // A cached observation owns every byte-bearing record. Mutable graphs are compared
418
+ // through descriptors on every call; only a proved, recursively frozen graph can
419
+ // bypass that walk. Per-object weak entries keep one latest copy, not a revision log.
420
+ const _rapierPrepareHistoryRecords = (() => {
421
+ const copies = new WeakMap();
422
+ // Repeated queries share the latest proved snapshot. A live but inactive input
423
+ // does not need to retain its record/effect indexes; immutable copies remain weak.
424
+ let prepared = new WeakMap();
425
+ // Object.freeze does not freeze Map/Set entries. Keep those collections inside
426
+ // the preparation and expose only immutable values through reading closures.
427
+ const view = collection => Object.freeze({size: collection.size, has: key => collection.has(key),
428
+ ...(collection instanceof Map ? {get: key => collection.get(key)} : {}),
429
+ keys: () => collection.keys(), values: () => collection.values(), entries: () => collection.entries(),
430
+ [Symbol.iterator]: () => collection[Symbol.iterator]()});
431
+ const capture = value => {
432
+ const active = new Set(), pending = [{value, depth: 0}], result = {};
433
+ const finish = (frame, value, immutable, height = -1) => {
434
+ const parent = frame.parent;
435
+ if (!parent) { result.value = value; return; }
436
+ parent.immutable &&= immutable;
437
+ parent.height = Math.max(parent.height, height + 1);
438
+ if (!parent.out && parent.previous[frame.key] !== value)
439
+ parent.out = Array.isArray(parent.previous) ? parent.previous.slice() : {...parent.previous};
440
+ if (parent.out) parent.out[frame.key] = value;
441
+ };
442
+ while (pending.length) {
443
+ const frame = pending.pop(), value = frame.value;
444
+ if (frame.leaving) {
445
+ active.delete(value);
446
+ const copy = frame.out ? Object.freeze(frame.out) : frame.previous;
447
+ copies.set(value, {copy, immutable: frame.immutable, height: frame.height, ...(frame.frozen && !frame.immutable ? {frozen: true, descriptors: frame.descriptors, fields: frame.fields} : {})});
448
+ copies.set(copy, {copy, immutable: true, height: frame.height});
449
+ finish(frame, copy, frame.immutable, frame.height); continue;
450
+ }
451
+ const old = frame.parent?.previous;
452
+ if (old && Object.hasOwn(old, frame.key) && Object.is(value, old[frame.key]) && (value === null || typeof value !== 'object')) {
453
+ finish(frame, value, true); continue;
454
+ }
455
+ if (value === null || typeof value === 'boolean' || typeof value === 'string' && !/[\uD800-\uDFFF]/u.test(value) ||
456
+ typeof value === 'number' && Number.isFinite(value) && !Object.is(value, -0)) {
457
+ finish(frame, value, true); continue;
458
+ }
459
+ if (!value || typeof value !== 'object' || frame.depth > 64 || active.has(value)) throw Error('history_data');
460
+ const previous = copies.get(value);
461
+ if (previous?.immutable) {
462
+ if (frame.depth + previous.height > 64) throw Error('history_data');
463
+ finish(frame, previous.copy, true, previous.height); continue;
464
+ }
465
+ frame.previous = previous?.copy;
466
+ frame.height = 0;
467
+ if (previous?.frozen) {
468
+ // A frozen shell fixes its descriptors, but every mutable child is still
469
+ // observed afresh. Fixed scalar fields need no second Unicode/data proof.
470
+ frame.frozen = frame.immutable = true;
471
+ frame.descriptors = previous.descriptors; frame.fields = previous.fields;
472
+ frame.out = null; frame.leaving = true; active.add(value); pending.push(frame);
473
+ for (let at = frame.fields.length - 1; at >= 0; at--) {
474
+ const key = frame.fields[at], child = frame.descriptors[key].value;
475
+ if (child && typeof child === 'object') pending.push({value: child, parent: frame, key, depth: frame.depth + 1});
476
+ }
477
+ continue;
478
+ }
479
+ // Check frozen-ness before capturing descriptors. A caller freezing a mutable
480
+ // input later cannot retroactively certify an earlier view of its properties.
481
+ frame.frozen = frame.immutable = Object.isFrozen(value);
482
+ const array = Array.isArray(value), proto = Object.getPrototypeOf(value);
483
+ if (!array && proto !== null && (Object.getPrototypeOf(proto) !== null ||
484
+ Object.getOwnPropertyDescriptor(Object.getOwnPropertyDescriptor(proto, 'constructor')?.value || {}, 'name')?.value !== 'Object')) throw Error('history_data');
485
+ const descriptors = Object.getOwnPropertyDescriptors(value), keys = Reflect.ownKeys(descriptors);
486
+ if (array && keys.length !== descriptors.length.value + 1) throw Error('history_data');
487
+ for (const key of keys) {
488
+ if (array && key === 'length') continue;
489
+ const field = descriptors[key];
490
+ if (typeof key !== 'string' || ['__proto__', 'constructor', 'prototype'].includes(key) ||
491
+ !Object.hasOwn(field, 'value') || !field.enumerable || array &&
492
+ (!/^(0|[1-9]\d*)$/.test(key) || +key >= descriptors.length.value)) throw Error('history_data');
493
+ }
494
+ const fields = array ? keys.filter(key => key !== 'length') : keys;
495
+ const sameKeys = frame.previous && Object.keys(frame.previous).length === fields.length && fields.every(key => Object.hasOwn(frame.previous, key));
496
+ frame.out = sameKeys ? null : array ? [] : {};
497
+ if (!sameKeys && !array) fields.sort();
498
+ frame.descriptors = descriptors; frame.fields = fields;
499
+ frame.leaving = true; active.add(value); pending.push(frame);
500
+ for (let at = fields.length - 1; at >= 0; at--) {
501
+ const key = fields[at]; pending.push({value: descriptors[key].value, parent: frame, key, depth: frame.depth + 1});
502
+ }
503
+ }
504
+ return result.value;
505
+ };
506
+ return (input, earlier = input) => {
507
+ try {
508
+ if (!Array.isArray(input) || !Array.isArray(earlier)) return {ok: false};
509
+ const records = capture(input), basis = input === earlier ? records : capture(earlier), previous = prepared.get(records);
510
+ if (previous?.basis === basis) return previous.observation;
511
+ const byId = new Map();
512
+ for (const record of records) {
513
+ const id = record?.transaction?.id;
514
+ if (typeof id !== 'string' || !id || byId.has(id)) return {ok: false};
515
+ byId.set(id, record);
516
+ }
517
+ const lookup = _rapierRecordIndex(basis), proof = _rapierMetadataProof(lookup);
518
+ const rows = records.map(record => {
519
+ const splices = lookup.splices(record), metadata = lookup.metadata(record);
520
+ if (splices && !Object.isFrozen(splices)) {
521
+ for (const row of splices) Object.freeze(row);
522
+ Object.freeze(splices);
523
+ }
524
+ if (metadata && !Object.isFrozen(metadata)) {
525
+ for (const pair of Object.values(metadata)) Object.freeze(pair);
526
+ Object.freeze(metadata);
527
+ }
528
+ return Object.freeze({record, splices, metadata});
529
+ });
530
+ const effects = _rapierWalkHistoryEffects(records, lookup, null, proof.observe);
531
+ if (!effects.ok) return {ok: false};
532
+ for (const refs of effects.references.values()) Object.freeze(refs);
533
+ const effectView = Object.freeze({ok: true, effectiveActIds: Object.freeze(effects.effectiveActIds),
534
+ active: view(effects.active), references: view(effects.references), pictures: view(effects.pictures)});
535
+ const observation = Object.freeze({ok: effects.ok, records, rows: Object.freeze(rows), effects: effectView, byId: view(byId),
536
+ replayMetadata: initial => effects.ok ? proof.replay(initial) : null,
537
+ validRecord: (index, documentAuthority, maxDocumentBytes, utf8Length, allowGap = false) => {
538
+ if (!effects.ok || !Number.isSafeInteger(index) || index < 0 || index >= records.length) return false;
539
+ const row = rows[index], tx = row.record.transaction;
540
+ return _rapierCheckLedgerRecord(row.record, records[index - 1] || null, documentAuthority, maxDocumentBytes, utf8Length, allowGap,
541
+ {...row, duplicate: false, semantic: !!(tx.reverts || tx.reapplies || tx.sourceTransactionId || tx.sourceTransactionIds?.length)});
542
+ }});
543
+ prepared = new WeakMap([[records, {basis, observation}]]); return observation;
544
+ } catch (_) { return {ok: false}; }
545
+ };
546
+ })();
547
+
335
548
  // A ledger record as the journal and the Notes return validate it below: plain data, nothing
336
549
  // undefined anywhere in it. A live record could carry a row whose type or order is not yet known (a
337
550
  // block just inserted, before its first render: `type: undefined`), and the validator would refuse
@@ -353,15 +566,28 @@ function _rapierValidLedgerRecord(record, prior, earlier, documentAuthority, max
353
566
  // Iterative traversal refuses cycles without exhausting the call stack. Proxies/counters that
354
567
  // throw are refused by the same gate rather than escaping into document recovery.
355
568
  try {
356
- if (!_rapierPlainLedgerData(record) || (Object.hasOwn(record, 'authored') && !_rapierValidAuthored(record.authored))) return false;
569
+ if (!_rapierPlainLedgerData(record) || !Array.isArray(earlier)) return false;
570
+ const own = Array.isArray(record?.splices), lookup = own ? null : _rapierRecordIndex(earlier);
571
+ const metadata = Object.hasOwn(record, 'metadata')
572
+ ? _rapierValidMetadataEffect(record.metadata) ? record.metadata : undefined : own ? null : lookup.metadata(record);
573
+ return _rapierCheckLedgerRecord(record, prior, documentAuthority, maxDocumentBytes, utf8Length, allowGap,
574
+ {splices: own ? record.splices : lookup.splices(record), metadata,
575
+ semantic: _rapierHasHistoryEffect(record, earlier), duplicate: earlier.some(row => row?.transaction?.id === record?.transaction?.id)});
576
+ } catch (_) { return false; }
577
+ }
578
+
579
+ // Both callers proved plain data themselves: the public single-row validator above,
580
+ // or the private owned-snapshot preparation. No caller-supplied trusted flag exists.
581
+ function _rapierCheckLedgerRecord(record, prior, documentAuthority, maxDocumentBytes, utf8Length, allowGap,
582
+ {splices, metadata, semantic, duplicate}) {
583
+ try {
584
+ if (Object.hasOwn(record, 'authored') && !_rapierValidAuthored(record.authored)) return false;
357
585
  if (!record || typeof record !== 'object' ||
358
586
  typeof record.beforeHash !== 'string' || !record.beforeHash.length ||
359
587
  typeof record.afterHash !== 'string' || !record.afterHash.length) return false;
360
588
  const own = Array.isArray(record.splices), navigation = !!(record.transaction?.reverts || record.transaction?.reapplies);
361
589
  if ((!own && !navigation) || (own && record.splices.length > 64) ||
362
590
  (record.transaction?.reverts && record.transaction?.reapplies)) return false;
363
- const splices = _rapierRecordSplices(record, earlier), metadata = _rapierRecordMetadata(record, earlier);
364
- const semantic = _rapierHasHistoryEffect(record, earlier);
365
591
  if (metadata === undefined || (!splices?.length && !Object.values(metadata || {}).some(pair => pair.before !== pair.after) && !semantic) ||
366
592
  (record.beforeHash === record.afterHash && splices?.length) ||
367
593
  (record.beforeHash !== record.afterHash && !splices?.length) ||
@@ -380,8 +606,7 @@ function _rapierValidLedgerRecord(record, prior, earlier, documentAuthority, max
380
606
  const optional = (value, valid) => value === undefined || valid(value);
381
607
  const identity = (value, limit) => typeof value === 'string' && value.length > 0 && value.length <= limit;
382
608
  if (!transaction || typeof transaction.id !== 'string' || !transaction.id.length ||
383
- transaction.id.length > 256 || !Array.isArray(earlier) ||
384
- earlier.some(row => row?.transaction?.id === transaction.id) ||
609
+ transaction.id.length > 256 || duplicate ||
385
610
  typeof transaction.documentAuthority !== 'string' ||
386
611
  transaction.documentAuthority !== documentAuthority ||
387
612
  (transaction.transport !== 'platform' && transaction.transport !== 'webmcp') ||
@@ -419,4 +644,4 @@ function _rapierValidLedgerRecord(record, prior, earlier, documentAuthority, max
419
644
  } catch (_) { return false; }
420
645
  }
421
646
 
422
- export { _rapierValidAuthored, _RAPIER_TRANSACTION_ACTOR_LIMIT, _RAPIER_TRANSACTION_OPERATION_LIMIT, _RAPIER_TRANSACTION_REQUEST_LIMIT, _rapierTransformSplices, _rapierRecordSplices, _rapierValidLedgerRecord, _rapierJournalRecord, _rapierValidMetadata, _rapierValidMetadataEffect, _rapierMetadataDelta, _rapierTransformMetadata, _rapierRecordMetadata, _rapierHistoryEffects, _rapierMetadataState, _rapierReplayMetadata, _rapierHasHistoryEffect, journalOperations};
647
+ export { _rapierPrepareHistoryRecords, _rapierValidAuthored, _RAPIER_TRANSACTION_ACTOR_LIMIT, _RAPIER_TRANSACTION_OPERATION_LIMIT, _RAPIER_TRANSACTION_REQUEST_LIMIT, _rapierTransformSplices, _rapierRecordSplices, _rapierValidLedgerRecord, _rapierJournalRecord, _rapierValidMetadata, _rapierValidMetadataEffect, _rapierMetadataDelta, _rapierTransformMetadata, _rapierRecordMetadata, _rapierHistoryEffects, _rapierMetadataState, _rapierReplayMetadata, _rapierHasHistoryEffect, journalOperations};
@@ -1,3 +1,3 @@
1
1
  // SPDX-License-Identifier: MIT
2
2
  export * from '../layout/model.mjs';
3
- export {prepareRichInline, materializeRichInlineLineRange} from '../agent/vendor/pretext/rich-inline.js';
3
+ export {layoutNextRichInlineLineRange, materializeRichInlineLineRange} from '../agent/vendor/pretext/rich-inline.js';
@@ -2,12 +2,11 @@
2
2
  // The licensed static faces retain the shipped variable font's exact glyphs and metrics.
3
3
  // Browser ranges own placement; unsupported shaping stays in the original page paint.
4
4
 
5
- const fontCache = new Map();
6
5
  const familyName = value => value.trim().replace(/^['"]|['"]$/g, '');
7
6
 
8
- function readFont(asset) {
7
+ function readFont(asset, fontCache) {
9
8
  if (fontCache.has(asset)) return fontCache.get(asset);
10
- const bytes = Uint8Array.from(atob(asset.data), character => character.charCodeAt(0));
9
+ const bytes = typeof asset.read === 'function' ? asset.read() : Uint8Array.from(atob(asset.data), character => character.charCodeAt(0));
11
10
  const view = new DataView(bytes.buffer), u16 = at => view.getUint16(at), i16 = at => view.getInt16(at), u32 = at => view.getUint32(at);
12
11
  const tag = at => String.fromCharCode(...bytes.subarray(at, at + 4)), tables = new Map();
13
12
  for (let at = 12, end = 12 + u16(4) * 16; at < end; at += 16) tables.set(tag(at), {at: u32(at + 8), length: u32(at + 12)});
@@ -94,6 +93,7 @@ function shape(value, font, preserve, ligatures) {
94
93
  }
95
94
 
96
95
  export async function collectPdfGlyphs(main, {width, check, faces = []}) {
96
+ const fontCache = new Map(); // Parsed byte copies belong to this PDF operation.
97
97
  const document = main.ownerDocument, window = document.defaultView, origin = main.getBoundingClientRect();
98
98
  const nodes = new Map(), hidden = [], canvas = document.createElement('canvas'); canvas.width = canvas.height = 1;
99
99
  const context = canvas.getContext('2d', {willReadFrequently: true});
@@ -134,7 +134,7 @@ export async function collectPdfGlyphs(main, {width, check, faces = []}) {
134
134
  if (generated || altered || !Array.from(document.fonts).some(face => familyName(face.family) === family && face.status === 'loaded')) continue;
135
135
  const asset = faces.find(face => face.family === family && face.weight === weight);
136
136
  if (!asset) continue;
137
- const font = readFont(asset), preserve = /pre|break-spaces/.test(style.whiteSpace), node = textNodes[0];
137
+ const font = readFont(asset, fontCache), preserve = /pre|break-spaces/.test(style.whiteSpace), node = textNodes[0];
138
138
  const shaped = shape(node.data, font, preserve, style.fontVariantLigatures === 'normal' && !(Number.parseFloat(style.letterSpacing) || 0));
139
139
  if (!shaped) continue;
140
140
  context.font = [style.fontStyle, style.fontWeight, style.fontSize, style.fontFamily].join(' ');