@neocompose/cli 0.61.1 → 0.61.3

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/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.61.3] - 2026-10-01
4
+
5
+ - Resolve `collectionValue: Reference(root.Assets.Quests.Daily)` through construction defaults, including classes and empty collections declared in the same push. Preparation creates the required rows and associations together; no preliminary push or manually assigned value IDs are needed.
6
+ - Preserve generated collection identities through source verification and subsequent pulls. Report one source diagnostic when a collection path cannot resolve.
7
+ - Capture root-path reads through construction defaults as executable initializers, including nested object fields. Preserve sparse receiver identities during read-only construction replay so local and web preparation can resolve those reads.
8
+
9
+ ## [0.61.2] - 2026-09-30
10
+
11
+ - Delete hidden collection entry members when their owning class or collection declaration is removed or changed to a scalar, while preserving entries still used by surviving declarations.
12
+
3
13
  ## [0.61.1] - 2026-09-30
4
14
 
5
15
  - Resolve and emit lookup dialogue group collections as `Class.Member` and `Reference(root.Assets.Member)`, including lists created in the same push. Existing raw-ID references remain readable.
package/dist/neo.mjs CHANGED
@@ -49838,6 +49838,40 @@ function descendantClassIdsInChildIndex(classId, childrenByClassId) {
49838
49838
  }
49839
49839
  return result;
49840
49840
  }
49841
+ function addDependentMemberIds(memberIds, members) {
49842
+ const childIdsByParentId = /* @__PURE__ */ new Map();
49843
+ const entryIdByOwnerId = /* @__PURE__ */ new Map();
49844
+ const remainingOwnersByEntryId = /* @__PURE__ */ new Map();
49845
+ for (const member of members) {
49846
+ if (member.extendsMemberId) {
49847
+ const children = childIdsByParentId.get(member.extendsMemberId);
49848
+ if (children === void 0) {
49849
+ childIdsByParentId.set(member.extendsMemberId, [member.id]);
49850
+ } else children.push(member.id);
49851
+ }
49852
+ if (member.entryMemberId) {
49853
+ entryIdByOwnerId.set(member.id, member.entryMemberId);
49854
+ remainingOwnersByEntryId.set(
49855
+ member.entryMemberId,
49856
+ (remainingOwnersByEntryId.get(member.entryMemberId) ?? 0) + 1
49857
+ );
49858
+ }
49859
+ }
49860
+ const pending = [...memberIds];
49861
+ function include(memberId) {
49862
+ if (memberIds.has(memberId)) return;
49863
+ memberIds.add(memberId);
49864
+ pending.push(memberId);
49865
+ }
49866
+ for (let id2 = pending.pop(); id2 !== void 0; id2 = pending.pop()) {
49867
+ for (const childId of childIdsByParentId.get(id2) ?? []) include(childId);
49868
+ const entryId = entryIdByOwnerId.get(id2);
49869
+ if (entryId === void 0) continue;
49870
+ const remainingOwners = remainingOwnersByEntryId.get(entryId) - 1;
49871
+ remainingOwnersByEntryId.set(entryId, remainingOwners);
49872
+ if (remainingOwners === 0) include(entryId);
49873
+ }
49874
+ }
49841
49875
  var UnresolvedMemberInheritanceError, NO_CLASSES, NO_MEMBERS, instanceSurfaceMemo, storedInstanceMemo, readOnlyMemberMemo, staticMemberMemo;
49842
49876
  var init_inheritance = __esm({
49843
49877
  "../src/models/classes/inheritance.ts"() {
@@ -63050,6 +63084,7 @@ function isSourceMemberValueRead(context, expression, ownerClass) {
63050
63084
  while (current.kind === "annotated") current = current.expression;
63051
63085
  const path = expressionMemberPath(current);
63052
63086
  if (path === null) return false;
63087
+ if (path.startsWith("root.")) return true;
63053
63088
  const parts = path.split(".");
63054
63089
  let classId;
63055
63090
  let memberName;
@@ -75726,7 +75761,7 @@ function withEvaluationRuntime(ctx, writes) {
75726
75761
  ctx.__runtimeSessionValues
75727
75762
  );
75728
75763
  const referencesAlreadyRemapped = ctx.__valueOverlay !== void 0 && ctx.__runtimeReferenceRemap !== void 0;
75729
- const remap = (value) => referencesAlreadyRemapped ? value : remapRuntimeReferences(
75764
+ const remap = (value) => referencesAlreadyRemapped || ctx.storedConstructionReplay === true ? value : remapRuntimeReferences(
75730
75765
  value,
75731
75766
  overlay,
75732
75767
  referenceRemap,
@@ -85719,14 +85754,15 @@ function claimVirtualIdentity(args) {
85719
85754
  virtualId: args.fixedVirtualId
85720
85755
  };
85721
85756
  }
85722
- const derivedId = virtualValueId(args.instanceRootId, args.rawSourceIdentity);
85757
+ const valueId = (sourceIdentity4) => args.instanceRootId.startsWith("__pending__:") || hasPendingVirtualSourceIdentity(sourceIdentity4) ? pendingVirtualValueId(args.instanceRootId, sourceIdentity4) : virtualValueId(args.instanceRootId, sourceIdentity4);
85758
+ const derivedId = valueId(args.rawSourceIdentity);
85723
85759
  const claimedPath = args.claimedVirtualIds.get(derivedId);
85724
85760
  if (claimedPath === void 0 || claimedPath === args.pathKey) {
85725
85761
  args.claimedVirtualIds.set(derivedId, args.pathKey);
85726
85762
  return { sourceIdentity: args.rawSourceIdentity, virtualId: derivedId };
85727
85763
  }
85728
85764
  const sourceIdentity3 = `${args.rawSourceIdentity}:${args.pathKey}`;
85729
- const virtualId = virtualValueId(args.instanceRootId, sourceIdentity3);
85765
+ const virtualId = valueId(sourceIdentity3);
85730
85766
  args.claimedVirtualIds.set(virtualId, args.pathKey);
85731
85767
  return { sourceIdentity: sourceIdentity3, virtualId };
85732
85768
  }
@@ -88107,6 +88143,8 @@ function createHeadlessVirtualInstanceResolver(args) {
88107
88143
  const remember = (graph, rows) => {
88108
88144
  graphRowsByRootId.set(graph.instanceRootId, rows);
88109
88145
  for (const [id2, row] of graph.rowsById) {
88146
+ if (id2 !== graph.instanceRootId && graphByValueId.get(id2)?.instanceRootId === id2)
88147
+ continue;
88110
88148
  expandedRowById.set(id2, row);
88111
88149
  graphByValueId.set(id2, graph);
88112
88150
  if (typeof row.value === "object" && row.value !== null) {
@@ -88114,6 +88152,8 @@ function createHeadlessVirtualInstanceResolver(args) {
88114
88152
  }
88115
88153
  }
88116
88154
  for (const id2 of graph.locationsById.keys()) {
88155
+ if (id2 !== graph.instanceRootId && graphByValueId.get(id2)?.instanceRootId === id2)
88156
+ continue;
88117
88157
  graphByValueId.set(id2, graph);
88118
88158
  }
88119
88159
  graphByValueId.set(graph.instanceRootId, graph);
@@ -88232,7 +88272,10 @@ function createHeadlessVirtualInstanceResolver(args) {
88232
88272
  rowForValueReference: (value) => expandedRowByRecord.get(value) ?? null,
88233
88273
  virtualInstanceGraphForValue: (valueId) => {
88234
88274
  const known = graphByValueId.get(valueId);
88235
- if (known !== void 0) return known;
88275
+ if (known?.instanceRootId === valueId) return known;
88276
+ const stored = rawById.get(valueId);
88277
+ if (known !== void 0 && (stored === void 0 || !isStoredClassResolutionRoot(stored)))
88278
+ return known;
88236
88279
  virtualInstanceRowsForValue(valueId);
88237
88280
  return graphByValueId.get(valueId) ?? null;
88238
88281
  },
@@ -98302,6 +98345,389 @@ var init_project_commit_preparation_scoped_loader = __esm({
98302
98345
  }
98303
98346
  });
98304
98347
 
98348
+ // ../src/runtime/neoscript/index.ts
98349
+ var init_neoscript2 = __esm({
98350
+ "../src/runtime/neoscript/index.ts"() {
98351
+ "use strict";
98352
+ init_NSGetterRuntimeError();
98353
+ init_evaluateNSGetter();
98354
+ init_evaluateInitializer();
98355
+ init_initializer_context();
98356
+ }
98357
+ });
98358
+
98359
+ // ../src/database/headless-virtual-instance-lookups.ts
98360
+ function headlessVirtualInstanceLookups(document, options = {}) {
98361
+ const members = document.members;
98362
+ const expandedDocument = expandPackedDocumentValues(document);
98363
+ const values = expandedDocument.values;
98364
+ const indexed = makeEvaluatorLookups(
98365
+ members,
98366
+ values,
98367
+ options.includeValueGraphIndexes === true ? {
98368
+ includeValueGraphIndexes: true,
98369
+ constructorInitializerDocument: {
98370
+ classes: document.classes,
98371
+ members,
98372
+ values
98373
+ }
98374
+ } : {}
98375
+ );
98376
+ const virtual = createHeadlessVirtualInstanceResolver2({
98377
+ document: {
98378
+ ...expandedDocument,
98379
+ ...options.initializerCompiler === void 0 ? {} : { initializerCompiler: options.initializerCompiler }
98380
+ },
98381
+ // A thunk: the resolver resolves nested reads through the lookups being
98382
+ // built around it.
98383
+ resolverLookups: () => lookups
98384
+ });
98385
+ const lookups = {
98386
+ ...indexed,
98387
+ // Raw first, then virtual: a materialized row always wins over the
98388
+ // expansion that would otherwise answer for it.
98389
+ valueById: (id2) => indexed.valueById(id2) ?? virtual.valueById(id2),
98390
+ rowForValueReference: (value) => indexed.rowForValueReference(value) ?? virtual.rowForValueReference(value),
98391
+ virtualInstanceRowsForValue: (receiverValueId) => virtual.virtualInstanceRowsForValue(receiverValueId),
98392
+ // Stored-construction replay evaluates `ToVariant`, and production's
98393
+ // variant-constructed placements collapse through exactly this path.
98394
+ resolveVariantInstanceGraph: (args) => resolveVariantInstanceGraphForDocument2({
98395
+ document,
98396
+ resolverLookups: lookups,
98397
+ ...args
98398
+ }),
98399
+ // A write to a virtual key must MATERIALIZE it (a pin row at the
98400
+ // deterministic virtual id plus the sparse spine); the planner needs the
98401
+ // owning graph, not just its rows.
98402
+ virtualInstanceGraphForValue: (valueId) => virtual.virtualInstanceGraphForValue(valueId)
98403
+ };
98404
+ return lookups;
98405
+ }
98406
+ var init_headless_virtual_instance_lookups = __esm({
98407
+ "../src/database/headless-virtual-instance-lookups.ts"() {
98408
+ "use strict";
98409
+ init_virtual_instance_values();
98410
+ init_packed_value_encoding();
98411
+ init_neoscript2();
98412
+ }
98413
+ });
98414
+
98415
+ // ../src/database/collection-binding-materialization.ts
98416
+ function mergeBodies(left, right) {
98417
+ if (right === void 0) return left;
98418
+ if (typeof left === "object" && left !== null && !Array.isArray(left) && typeof right === "object" && right !== null && !Array.isArray(right)) {
98419
+ return { ...left, ...right };
98420
+ }
98421
+ return right;
98422
+ }
98423
+ var CollectionBindingMaterializer;
98424
+ var init_collection_binding_materialization = __esm({
98425
+ "../src/database/collection-binding-materialization.ts"() {
98426
+ "use strict";
98427
+ init_members();
98428
+ init_virtual_instance_values();
98429
+ init_headless_virtual_instance_lookups();
98430
+ init_document_initializer_compiler();
98431
+ CollectionBindingMaterializer = class {
98432
+ constructor(document) {
98433
+ this.document = document;
98434
+ this.stored = new Map(document.values.map((row) => [row.id, row]));
98435
+ this.lookups = headlessVirtualInstanceLookups(document, {
98436
+ initializerCompiler: documentInitializerCompiler(document)
98437
+ });
98438
+ }
98439
+ document;
98440
+ rows = /* @__PURE__ */ new Map();
98441
+ pathRowsByRootId = /* @__PURE__ */ new Map();
98442
+ stored;
98443
+ lookups;
98444
+ resolvePath(rootId, segments) {
98445
+ let id2 = rootId;
98446
+ const pathRows = this.pathRowsByRootId.get(rootId) ?? /* @__PURE__ */ new Set();
98447
+ this.pathRowsByRootId.set(rootId, pathRows);
98448
+ for (const segment of segments) {
98449
+ const graph = this.lookups.virtualInstanceGraphForValue(id2);
98450
+ const row = graph?.rowsById.get(id2) ?? this.stored.get(id2);
98451
+ if (row === void 0 || !isLiteralValueContent(row)) return null;
98452
+ const body = row.value;
98453
+ if (typeof body !== "object" || body === null || Array.isArray(body))
98454
+ return null;
98455
+ const next = Reflect.get(body, segment);
98456
+ if (typeof next !== "string") return null;
98457
+ id2 = next;
98458
+ pathRows.add(id2);
98459
+ }
98460
+ return this.materialize(id2, pathRows) ? id2 : null;
98461
+ }
98462
+ /** Discover opaque virtual IDs from web selections against this candidate's roots. */
98463
+ materializeTargets(ids) {
98464
+ const missing = new Set([...ids].filter((id2) => !this.stored.has(id2)));
98465
+ if (missing.size === 0) return;
98466
+ const visited = /* @__PURE__ */ new Set();
98467
+ for (const row of this.document.values) {
98468
+ if (typeof row.classId !== "string") continue;
98469
+ const graph = this.lookups.virtualInstanceGraphForValue(row.id);
98470
+ if (graph === null || visited.has(graph.instanceRootId)) continue;
98471
+ visited.add(graph.instanceRootId);
98472
+ for (const id2 of graph.rowsById.keys()) {
98473
+ if (!missing.has(id2)) continue;
98474
+ if (this.materialize(id2)) missing.delete(id2);
98475
+ }
98476
+ if (missing.size === 0) return;
98477
+ }
98478
+ if (missing.size > 0) {
98479
+ throw new Error(
98480
+ `Collection binding targets missing values: ${[...missing].join(", ")}.`
98481
+ );
98482
+ }
98483
+ }
98484
+ locationForValue(id2) {
98485
+ return this.lookups.virtualInstanceGraphForValue(id2)?.locationsById.get(id2);
98486
+ }
98487
+ rowForValue(id2) {
98488
+ return this.rows.get(id2) ?? this.stored.get(id2);
98489
+ }
98490
+ materialize(id2, pathRows) {
98491
+ if (this.stored.has(id2) || this.rows.has(id2)) return true;
98492
+ const graph = this.lookups.virtualInstanceGraphForValue(id2);
98493
+ const row = graph?.rowsById.get(id2);
98494
+ if (graph === null || row === void 0 || !isLiteralValueContent(row))
98495
+ return false;
98496
+ const plan = planVirtualOverrideWrite({
98497
+ graph,
98498
+ valueId: id2,
98499
+ value: row.value
98500
+ });
98501
+ for (const create of plan.creates) {
98502
+ pathRows?.add(create.id);
98503
+ const { valuePlacement: _placement, ...fields } = create;
98504
+ const previous = this.rows.get(create.id);
98505
+ this.rows.set(create.id, {
98506
+ ...fields,
98507
+ value: mergeBodies(fields.value, previous?.value),
98508
+ projectId: this.document.project.id,
98509
+ createdAt: 0,
98510
+ updatedAt: 0
98511
+ });
98512
+ }
98513
+ for (const update of plan.updates) {
98514
+ pathRows?.add(update.valueId);
98515
+ const previous = this.rows.get(update.valueId) ?? this.stored.get(update.valueId);
98516
+ if (previous === void 0)
98517
+ throw new Error(
98518
+ `Collection binding has no ancestor ${update.valueId}.`
98519
+ );
98520
+ this.rows.set(update.valueId, {
98521
+ ...previous,
98522
+ value: mergeBodies(previous.value, update.value)
98523
+ });
98524
+ }
98525
+ return true;
98526
+ }
98527
+ };
98528
+ }
98529
+ });
98530
+
98531
+ // ../src/database/project-version-write-changes.ts
98532
+ function applyProjectVersionWriteChanges(document, changes) {
98533
+ return projectVersionDocumentWithChanges(document, changes, false);
98534
+ }
98535
+ function projectVersionDocumentWithChanges(document, changes, reuseUnchangedBuckets) {
98536
+ const projectRows = (rows, kind) => {
98537
+ const relevant = changes.filter((change) => change.recordKind === kind);
98538
+ if (reuseUnchangedBuckets && relevant.length === 0) return rows;
98539
+ return applyArrayChanges(rows, relevant);
98540
+ };
98541
+ const hasGranularDialogueRecords = document.dialogueRecords !== void 0 || changes.some((change) => change.recordKind === "dialogue");
98542
+ const hasGranularDialogueNodes = document.dialogueNodes !== void 0 || changes.some((change) => change.recordKind === "dialogue-node");
98543
+ const dialogueRecords = hasGranularDialogueRecords ? projectRows(document.dialogueRecords ?? [], "dialogue") : void 0;
98544
+ const dialogueNodes = hasGranularDialogueNodes ? projectRows(document.dialogueNodes ?? [], "dialogue-node") : void 0;
98545
+ const projected = {
98546
+ localizedTexts: projectRows(
98547
+ document.localizedTexts ?? [],
98548
+ "localized-text"
98549
+ ),
98550
+ localizationConfig: applySingletonChange(
98551
+ document.localizationConfig,
98552
+ changes,
98553
+ "localization-config"
98554
+ ),
98555
+ project: applySingletonChange(document.project, changes, "project"),
98556
+ members: projectRows(document.members, "member"),
98557
+ classes: projectRows(document.classes, "class"),
98558
+ // P43 §6. Declared constructors participate in the post-transaction
98559
+ // document so the whole-graph validator and every body compile see the
98560
+ // same constructor surface the transaction is about to persist.
98561
+ constructors: projectRows(document.constructors ?? [], "constructor"),
98562
+ // P67 §2.1. Same reason as constructors: without these buckets the
98563
+ // whole-graph validator, the variant ownership roots, and every body
98564
+ // compile would grade the transaction against the pre-transaction
98565
+ // variant set — so a newly pushed variant's closures would compile
98566
+ // against a document that does not contain it.
98567
+ variants: projectRows(document.variants ?? [], "variant"),
98568
+ variantFolders: projectRows(
98569
+ document.variantFolders ?? [],
98570
+ "variant-folder"
98571
+ ),
98572
+ values: projectRows(document.values ?? [], "value"),
98573
+ // Project-file staging finalizes pending uploads before schema
98574
+ // preparation. Defaults and authored instance graphs may reference a file
98575
+ // created by the same source transaction, so every construction pass must
98576
+ // see that finalized row in its post-write document.
98577
+ projectFiles: projectRows(document.projectFiles ?? [], "project-file"),
98578
+ // Import-setting templates are part of the same file graph. A new file
98579
+ // may select a new template in one source transaction, and default-value
98580
+ // materialization validates that relationship immediately against this
98581
+ // post-write document.
98582
+ textureTemplates: projectRows(
98583
+ document.textureTemplates ?? [],
98584
+ "unity-texture-template"
98585
+ ),
98586
+ audioClipTemplates: projectRows(
98587
+ document.audioClipTemplates ?? [],
98588
+ "unity-audio-clip-template"
98589
+ ),
98590
+ internalRecordRelations: projectRows(
98591
+ document.internalRecordRelations ?? [],
98592
+ "internal-record-relation"
98593
+ ),
98594
+ enums: projectRows(document.enums, "enum"),
98595
+ interfaces: projectRows(document.interfaces, "interface"),
98596
+ dialogues: dialogueRecords === void 0 || dialogueNodes === void 0 || dialogueRecords === document.dialogueRecords && dialogueNodes === document.dialogueNodes ? document.dialogues : materializeDialogues(
98597
+ dialogueRecords,
98598
+ dialogueNodes
98599
+ ),
98600
+ dialogueGroups: projectRows(
98601
+ document.dialogueGroups ?? [],
98602
+ "dialogue-group"
98603
+ ),
98604
+ migrations: projectRows(document.migrations ?? [], "migration")
98605
+ };
98606
+ return {
98607
+ ...document,
98608
+ ...projected,
98609
+ ...dialogueRecords === void 0 ? {} : { dialogueRecords },
98610
+ ...dialogueNodes === void 0 ? {} : { dialogueNodes }
98611
+ // The document spread above carries every bucket the caller's view declares
98612
+ // beyond the projected ones, so the result is the same view; TypeScript
98613
+ // cannot prove that of an object literal built over a type parameter.
98614
+ };
98615
+ }
98616
+ function applySingletonChange(current, changes, recordKind) {
98617
+ const change = changes.find(
98618
+ (candidate) => candidate.recordKind === recordKind
98619
+ );
98620
+ if (change === void 0) return current;
98621
+ if (change.operation === "delete" || change.nextData === void 0) {
98622
+ throw new Error(`Schema commit cannot delete the ${recordKind} record.`);
98623
+ }
98624
+ return change.nextData;
98625
+ }
98626
+ function applyArrayChanges(current, changes) {
98627
+ const byId = new Map(current.map((record6) => [record6.id, record6]));
98628
+ for (const change of changes) {
98629
+ if (change.operation === "delete") {
98630
+ byId.delete(change.recordId);
98631
+ continue;
98632
+ }
98633
+ if (change.nextData !== void 0) {
98634
+ byId.set(change.recordId, change.nextData);
98635
+ }
98636
+ }
98637
+ return [...byId.values()];
98638
+ }
98639
+ var init_project_version_write_changes = __esm({
98640
+ "../src/database/project-version-write-changes.ts"() {
98641
+ "use strict";
98642
+ init_projectDocumentDialogueMaterialization();
98643
+ }
98644
+ });
98645
+
98646
+ // ../src/database/prepared-collection-bindings.ts
98647
+ function materializePreparedCollectionBindings(args) {
98648
+ const targets = /* @__PURE__ */ new Set();
98649
+ for (const change of args.prepared) {
98650
+ if (change.operation === "delete") continue;
98651
+ if (change.recordKind !== "member" && change.recordKind !== "variant-folder" && change.recordKind !== "dialogue-group")
98652
+ continue;
98653
+ const data = change.nextData;
98654
+ if (typeof data !== "object" || data === null) continue;
98655
+ const binding = change.recordKind === "variant-folder" ? Reflect.get(data, "binding") : data;
98656
+ if (typeof binding !== "object" || binding === null) continue;
98657
+ const id2 = Reflect.get(binding, "collectionValueId");
98658
+ if (typeof id2 === "string") targets.add(id2);
98659
+ }
98660
+ if (targets.size === 0) return;
98661
+ const deletedTargets = /* @__PURE__ */ new Set();
98662
+ for (const change of args.prepared) {
98663
+ if (change.recordKind !== "value" || !targets.has(change.recordId))
98664
+ continue;
98665
+ if (change.operation === "delete") deletedTargets.add(change.recordId);
98666
+ else targets.delete(change.recordId);
98667
+ }
98668
+ if (targets.size === 0) return;
98669
+ for (const row of args.document.values) {
98670
+ if (!deletedTargets.has(row.id)) targets.delete(row.id);
98671
+ if (targets.size === 0) return;
98672
+ }
98673
+ const document = projectVersionDocumentWithChanges(
98674
+ args.document,
98675
+ args.prepared,
98676
+ true
98677
+ );
98678
+ const existingIds = new Set(document.values.map((row) => row.id));
98679
+ const materializer = new CollectionBindingMaterializer(document);
98680
+ materializer.materializeTargets(targets);
98681
+ const indexes2 = /* @__PURE__ */ new Map();
98682
+ args.prepared.forEach((change, index) => {
98683
+ if (change.recordKind === "value") indexes2.set(change.recordId, index);
98684
+ });
98685
+ const hashes = new Map(
98686
+ args.contentHashHeads.filter((head) => head.recordKind === "value").map((head) => [head.recordId, head.contentHash])
98687
+ );
98688
+ for (const row of materializer.rows.values()) {
98689
+ const index = indexes2.get(row.id);
98690
+ if (index !== void 0) {
98691
+ const previous = args.prepared[index];
98692
+ args.prepared[index] = {
98693
+ ...previous,
98694
+ operation: previous.operation === "delete" ? "update" : previous.operation,
98695
+ deleted: false,
98696
+ nextData: row,
98697
+ intent: previous.operation === "delete" ? createProjectVersionIntent("value.update", {
98698
+ source: "collection-binding-materialization"
98699
+ }) : previous.intent
98700
+ };
98701
+ continue;
98702
+ }
98703
+ const contentHash = hashes.get(row.id);
98704
+ if (existingIds.has(row.id) && contentHash === void 0) {
98705
+ throw new Error(
98706
+ `Collection binding ancestor "${row.id}" has no content hash.`
98707
+ );
98708
+ }
98709
+ args.prepared.push({
98710
+ recordKind: "value",
98711
+ recordId: row.id,
98712
+ operation: existingIds.has(row.id) ? "update" : "create",
98713
+ nextData: row,
98714
+ expectedBaseContentHash: contentHash ?? null,
98715
+ intent: createProjectVersionIntent(
98716
+ existingIds.has(row.id) ? "value.update" : "value.create",
98717
+ { source: "collection-binding-materialization" }
98718
+ )
98719
+ });
98720
+ }
98721
+ }
98722
+ var init_prepared_collection_bindings = __esm({
98723
+ "../src/database/prepared-collection-bindings.ts"() {
98724
+ "use strict";
98725
+ init_project_version_intents();
98726
+ init_collection_binding_materialization();
98727
+ init_project_version_write_changes();
98728
+ }
98729
+ });
98730
+
98305
98731
  // ../src/models/project/world-content-sidecar-validation.ts
98306
98732
  function cachedWorldLayerTarget(context, args) {
98307
98733
  let target = context.linkTargetsByClassId.get(args.sourceClassId);
@@ -98957,17 +99383,6 @@ var init_world_content_sidecar_validation = __esm({
98957
99383
  }
98958
99384
  });
98959
99385
 
98960
- // ../src/runtime/neoscript/index.ts
98961
- var init_neoscript2 = __esm({
98962
- "../src/runtime/neoscript/index.ts"() {
98963
- "use strict";
98964
- init_NSGetterRuntimeError();
98965
- init_evaluateNSGetter();
98966
- init_evaluateInitializer();
98967
- init_initializer_context();
98968
- }
98969
- });
98970
-
98971
99386
  // ../src/database/dialogue-node-index.ts
98972
99387
  function indexDialogueNodesByDialogue(nodes, dialogueId) {
98973
99388
  const result = /* @__PURE__ */ new Map();
@@ -100266,62 +100681,6 @@ var init_general_function_call_ir_source_recompile = __esm({
100266
100681
  }
100267
100682
  });
100268
100683
 
100269
- // ../src/database/headless-virtual-instance-lookups.ts
100270
- function headlessVirtualInstanceLookups(document, options = {}) {
100271
- const members = document.members;
100272
- const expandedDocument = expandPackedDocumentValues(document);
100273
- const values = expandedDocument.values;
100274
- const indexed = makeEvaluatorLookups(
100275
- members,
100276
- values,
100277
- options.includeValueGraphIndexes === true ? {
100278
- includeValueGraphIndexes: true,
100279
- constructorInitializerDocument: {
100280
- classes: document.classes,
100281
- members,
100282
- values
100283
- }
100284
- } : {}
100285
- );
100286
- const virtual = createHeadlessVirtualInstanceResolver2({
100287
- document: {
100288
- ...expandedDocument,
100289
- ...options.initializerCompiler === void 0 ? {} : { initializerCompiler: options.initializerCompiler }
100290
- },
100291
- // A thunk: the resolver resolves nested reads through the lookups being
100292
- // built around it.
100293
- resolverLookups: () => lookups
100294
- });
100295
- const lookups = {
100296
- ...indexed,
100297
- // Raw first, then virtual: a materialized row always wins over the
100298
- // expansion that would otherwise answer for it.
100299
- valueById: (id2) => indexed.valueById(id2) ?? virtual.valueById(id2),
100300
- rowForValueReference: (value) => indexed.rowForValueReference(value) ?? virtual.rowForValueReference(value),
100301
- virtualInstanceRowsForValue: (receiverValueId) => virtual.virtualInstanceRowsForValue(receiverValueId),
100302
- // Stored-construction replay evaluates `ToVariant`, and production's
100303
- // variant-constructed placements collapse through exactly this path.
100304
- resolveVariantInstanceGraph: (args) => resolveVariantInstanceGraphForDocument2({
100305
- document,
100306
- resolverLookups: lookups,
100307
- ...args
100308
- }),
100309
- // A write to a virtual key must MATERIALIZE it (a pin row at the
100310
- // deterministic virtual id plus the sparse spine); the planner needs the
100311
- // owning graph, not just its rows.
100312
- virtualInstanceGraphForValue: (valueId) => virtual.virtualInstanceGraphForValue(valueId)
100313
- };
100314
- return lookups;
100315
- }
100316
- var init_headless_virtual_instance_lookups = __esm({
100317
- "../src/database/headless-virtual-instance-lookups.ts"() {
100318
- "use strict";
100319
- init_virtual_instance_values();
100320
- init_packed_value_encoding();
100321
- init_neoscript2();
100322
- }
100323
- });
100324
-
100325
100684
  // ../src/database/localized-instance-override-writes.ts
100326
100685
  function collapseEditedLocalizedInstanceOverrides(args) {
100327
100686
  const edits = args.changes.filter(
@@ -100753,121 +101112,6 @@ var init_member_access_modifier_validation = __esm({
100753
101112
  }
100754
101113
  });
100755
101114
 
100756
- // ../src/database/project-version-write-changes.ts
100757
- function applyProjectVersionWriteChanges(document, changes) {
100758
- return projectVersionDocumentWithChanges(document, changes, false);
100759
- }
100760
- function projectVersionDocumentWithChanges(document, changes, reuseUnchangedBuckets) {
100761
- const projectRows = (rows, kind) => {
100762
- const relevant = changes.filter((change) => change.recordKind === kind);
100763
- if (reuseUnchangedBuckets && relevant.length === 0) return rows;
100764
- return applyArrayChanges(rows, relevant);
100765
- };
100766
- const hasGranularDialogueRecords = document.dialogueRecords !== void 0 || changes.some((change) => change.recordKind === "dialogue");
100767
- const hasGranularDialogueNodes = document.dialogueNodes !== void 0 || changes.some((change) => change.recordKind === "dialogue-node");
100768
- const dialogueRecords = hasGranularDialogueRecords ? projectRows(document.dialogueRecords ?? [], "dialogue") : void 0;
100769
- const dialogueNodes = hasGranularDialogueNodes ? projectRows(document.dialogueNodes ?? [], "dialogue-node") : void 0;
100770
- const projected = {
100771
- localizedTexts: projectRows(
100772
- document.localizedTexts ?? [],
100773
- "localized-text"
100774
- ),
100775
- localizationConfig: applySingletonChange(
100776
- document.localizationConfig,
100777
- changes,
100778
- "localization-config"
100779
- ),
100780
- project: applySingletonChange(document.project, changes, "project"),
100781
- members: projectRows(document.members, "member"),
100782
- classes: projectRows(document.classes, "class"),
100783
- // P43 §6. Declared constructors participate in the post-transaction
100784
- // document so the whole-graph validator and every body compile see the
100785
- // same constructor surface the transaction is about to persist.
100786
- constructors: projectRows(document.constructors ?? [], "constructor"),
100787
- // P67 §2.1. Same reason as constructors: without these buckets the
100788
- // whole-graph validator, the variant ownership roots, and every body
100789
- // compile would grade the transaction against the pre-transaction
100790
- // variant set — so a newly pushed variant's closures would compile
100791
- // against a document that does not contain it.
100792
- variants: projectRows(document.variants ?? [], "variant"),
100793
- variantFolders: projectRows(
100794
- document.variantFolders ?? [],
100795
- "variant-folder"
100796
- ),
100797
- values: projectRows(document.values ?? [], "value"),
100798
- // Project-file staging finalizes pending uploads before schema
100799
- // preparation. Defaults and authored instance graphs may reference a file
100800
- // created by the same source transaction, so every construction pass must
100801
- // see that finalized row in its post-write document.
100802
- projectFiles: projectRows(document.projectFiles ?? [], "project-file"),
100803
- // Import-setting templates are part of the same file graph. A new file
100804
- // may select a new template in one source transaction, and default-value
100805
- // materialization validates that relationship immediately against this
100806
- // post-write document.
100807
- textureTemplates: projectRows(
100808
- document.textureTemplates ?? [],
100809
- "unity-texture-template"
100810
- ),
100811
- audioClipTemplates: projectRows(
100812
- document.audioClipTemplates ?? [],
100813
- "unity-audio-clip-template"
100814
- ),
100815
- internalRecordRelations: projectRows(
100816
- document.internalRecordRelations ?? [],
100817
- "internal-record-relation"
100818
- ),
100819
- enums: projectRows(document.enums, "enum"),
100820
- interfaces: projectRows(document.interfaces, "interface"),
100821
- dialogues: dialogueRecords === void 0 || dialogueNodes === void 0 || dialogueRecords === document.dialogueRecords && dialogueNodes === document.dialogueNodes ? document.dialogues : materializeDialogues(
100822
- dialogueRecords,
100823
- dialogueNodes
100824
- ),
100825
- dialogueGroups: projectRows(
100826
- document.dialogueGroups ?? [],
100827
- "dialogue-group"
100828
- ),
100829
- migrations: projectRows(document.migrations ?? [], "migration")
100830
- };
100831
- return {
100832
- ...document,
100833
- ...projected,
100834
- ...dialogueRecords === void 0 ? {} : { dialogueRecords },
100835
- ...dialogueNodes === void 0 ? {} : { dialogueNodes }
100836
- // The document spread above carries every bucket the caller's view declares
100837
- // beyond the projected ones, so the result is the same view; TypeScript
100838
- // cannot prove that of an object literal built over a type parameter.
100839
- };
100840
- }
100841
- function applySingletonChange(current, changes, recordKind) {
100842
- const change = changes.find(
100843
- (candidate) => candidate.recordKind === recordKind
100844
- );
100845
- if (change === void 0) return current;
100846
- if (change.operation === "delete" || change.nextData === void 0) {
100847
- throw new Error(`Schema commit cannot delete the ${recordKind} record.`);
100848
- }
100849
- return change.nextData;
100850
- }
100851
- function applyArrayChanges(current, changes) {
100852
- const byId = new Map(current.map((record6) => [record6.id, record6]));
100853
- for (const change of changes) {
100854
- if (change.operation === "delete") {
100855
- byId.delete(change.recordId);
100856
- continue;
100857
- }
100858
- if (change.nextData !== void 0) {
100859
- byId.set(change.recordId, change.nextData);
100860
- }
100861
- }
100862
- return [...byId.values()];
100863
- }
100864
- var init_project_version_write_changes = __esm({
100865
- "../src/database/project-version-write-changes.ts"() {
100866
- "use strict";
100867
- init_projectDocumentDialogueMaterialization();
100868
- }
100869
- });
100870
-
100871
101115
  // ../src/database/member-localization-normalization.ts
100872
101116
  function planMemberLocalizationNormalization(input, now = 0) {
100873
101117
  const before = expandPackedDocumentValues(input);
@@ -103316,6 +103560,11 @@ function* prepareServerOwnedSchemaCommitPasses(args) {
103316
103560
  prepared,
103317
103561
  contentHashHeads: args.contentHashHeads
103318
103562
  });
103563
+ materializePreparedCollectionBindings({
103564
+ document: args.document,
103565
+ prepared,
103566
+ contentHashHeads: args.contentHashHeads
103567
+ });
103319
103568
  drainConstructedGraphValidations({
103320
103569
  document: args.document,
103321
103570
  prepared,
@@ -106806,6 +107055,7 @@ var SERVER_COMPILATION_PROJECT_CACHE_LIMIT, serverCompilationProjectCache, READ_
106806
107055
  var init_project_version_schema_commit = __esm({
106807
107056
  "../src/database/project-version-schema-commit.ts"() {
106808
107057
  "use strict";
107058
+ init_prepared_collection_bindings();
106809
107059
  init_document_initializer_compiler();
106810
107060
  init_effective_value_comparison();
106811
107061
  init_classes();
@@ -120773,7 +121023,8 @@ function resolveRootPathCollectionValuesV4(args) {
120773
121023
  args.registry,
120774
121024
  placements,
120775
121025
  rootValues,
120776
- fail2
121026
+ fail2,
121027
+ args.resolveVirtualPath
120777
121028
  );
120778
121029
  if (target !== null) resolved.set(member.id, target);
120779
121030
  }
@@ -120811,7 +121062,8 @@ function resolveRootPathVariantFolderCollectionValuesV4(args) {
120811
121062
  args.registry,
120812
121063
  placements,
120813
121064
  rootValues,
120814
- fail2
121065
+ fail2,
121066
+ args.resolveVirtualPath
120815
121067
  );
120816
121068
  if (target !== null) resolved.set(folder.id, target);
120817
121069
  }
@@ -120848,7 +121100,8 @@ function resolveRootPathDialogueGroupCollectionValuesV4(args) {
120848
121100
  expression: null,
120849
121101
  label: String(record6.fileFields.name)
120850
121102
  }
120851
- })
121103
+ }),
121104
+ args.resolveVirtualPath
120852
121105
  );
120853
121106
  if (target !== null) record6.fileFields.collectionValueId = target;
120854
121107
  }
@@ -120868,7 +121121,7 @@ function rootSlotValueIds(state) {
120868
121121
  });
120869
121122
  return values;
120870
121123
  }
120871
- function walkRootValuePath(path, state, registry, placements, rootValues, fail2) {
121124
+ function walkRootValuePath(path, state, registry, placements, rootValues, fail2, resolveVirtualPath) {
120872
121125
  const segments = path.split(".");
120873
121126
  const slotName = segments[1];
120874
121127
  if (segments[0] !== "root" || slotName === void 0) {
@@ -120911,10 +121164,17 @@ function walkRootValuePath(path, state, registry, placements, rootValues, fail2)
120911
121164
  }
120912
121165
  const next = placements.childrenByParentValueId.get(cursor)?.get(segment)?.childValueId;
120913
121166
  if (typeof next !== "string") {
120914
- const cursorData = state[`value:${cursor}`]?.data;
120915
- const collapsed = isObjectRecord2(cursorData) && isVirtualInstanceRootShape(cursorData);
121167
+ try {
121168
+ const resolved = resolveVirtualPath?.(slotValueId, segments.slice(2));
121169
+ if (resolved != null) return resolved;
121170
+ } catch (error) {
121171
+ fail2(
121172
+ `collectionValue path ${path} could not resolve its construction: ${error instanceof Error ? error.message : String(error)}`
121173
+ );
121174
+ return null;
121175
+ }
120916
121176
  fail2(
120917
- collapsed ? `collectionValue path ${path} reaches segment ${segment} on a collapsed instance "${cursor}": the value exists in that instance's construction but has no stored row to bind, and this push cannot materialize one. Name the row with Reference(id: ...) instead.` : `collectionValue path ${path} has no value at segment ${segment} after this push.`
121177
+ `collectionValue path ${path} has no value at segment ${segment} after this push.`
120918
121178
  );
120919
121179
  return null;
120920
121180
  }
@@ -120984,7 +121244,6 @@ var init_root_source = __esm({
120984
121244
  "use strict";
120985
121245
  init_src();
120986
121246
  init_src2();
120987
- init_instance_provenance();
120988
121247
  init_value_sources();
120989
121248
  init_source_format();
120990
121249
  init_stored_value_placements();
@@ -122263,6 +122522,61 @@ function recordUpsertChange(args) {
122263
122522
  casBaseHash: inConflictState ? args.base.conflictServerHash : args.base.contentHash
122264
122523
  };
122265
122524
  }
122525
+ function recordDeleteChange(base, file) {
122526
+ return {
122527
+ kind: "delete",
122528
+ recordKind: base.recordKind,
122529
+ recordId: base.recordId,
122530
+ file,
122531
+ baseData: base.data,
122532
+ baseContentHash: base.contentHash,
122533
+ casBaseHash: base.conflictServerHash ?? base.contentHash
122534
+ };
122535
+ }
122536
+ function appendCollectionEntryDeletes(workspace, reconstructed3, changes) {
122537
+ const releasedOwnerIds = /* @__PURE__ */ new Set();
122538
+ for (const change of changes) {
122539
+ if (change.recordKind !== "member") continue;
122540
+ if (change.kind === "delete") {
122541
+ releasedOwnerIds.add(change.recordId);
122542
+ continue;
122543
+ }
122544
+ const base = workspace.state.records[recordStateKey("member", change.recordId)];
122545
+ if (base === void 0) continue;
122546
+ const data = effectiveRecordData(base);
122547
+ if (isObjectRecord2(data) && typeof data.entryMemberId === "string" && data.entryMemberId !== change.nextData?.entryMemberId)
122548
+ releasedOwnerIds.add(change.recordId);
122549
+ }
122550
+ if (releasedOwnerIds.size === 0) return;
122551
+ const members = /* @__PURE__ */ new Map();
122552
+ function setOwnership(id2, data) {
122553
+ if (!isObjectRecord2(data)) return;
122554
+ const entryMemberId = data.entryMemberId;
122555
+ members.set(id2, {
122556
+ id: id2,
122557
+ // Reconstructed entries survive, including entries moved to a new owner.
122558
+ ...typeof entryMemberId === "string" && !reconstructed3.has(recordStateKey("member", entryMemberId)) ? { entryMemberId } : {}
122559
+ });
122560
+ }
122561
+ for (const record6 of Object.values(workspace.state.records)) {
122562
+ if (record6.recordKind === "member" && record6.conflictServerHash !== null)
122563
+ setOwnership(record6.recordId, effectiveRecordData(record6));
122564
+ }
122565
+ for (const record6 of reconstructed3.values()) {
122566
+ if (record6.recordKind === "member" && !releasedOwnerIds.has(record6.recordId))
122567
+ setOwnership(record6.recordId, record6.fullData);
122568
+ }
122569
+ const dependentIds = new Set(releasedOwnerIds);
122570
+ addDependentMemberIds(dependentIds, members.values());
122571
+ for (const id2 of dependentIds) {
122572
+ if (releasedOwnerIds.has(id2)) continue;
122573
+ const key = recordStateKey("member", id2);
122574
+ if (reconstructed3.has(key)) continue;
122575
+ const base = workspace.state.records[key];
122576
+ if (base === void 0 || base.conflictServerHash === null) continue;
122577
+ changes.push(recordDeleteChange(base, base.file ?? null));
122578
+ }
122579
+ }
122266
122580
  function listVirtualProjectSourceFilesV4(files) {
122267
122581
  const selected = files.filter((file) => {
122268
122582
  const kind = neoProjectSourceKind(file.path);
@@ -122547,9 +122861,35 @@ function computeWorkspaceStatus(workspace, options) {
122547
122861
  [...staticValues.records, ...memberDefaults.records, ...rootValues.records],
122548
122862
  staticMemberValueIds
122549
122863
  );
122864
+ let collectionBindings;
122865
+ const resolveVirtualPath = (rootId, segments) => {
122866
+ if (collectionBindings === void 0) {
122867
+ const candidate = /* @__PURE__ */ new Map();
122868
+ for (const [key, record6] of Object.entries(rootPathResolutionState)) {
122869
+ if (record6.file !== void 0 && isSchemaRecordKindV4(record6.recordKind) && !documentsByKey.has(key))
122870
+ continue;
122871
+ candidate.set(key, { ...record6, deleted: false });
122872
+ }
122873
+ for (const row of valueLowerRegistry.pendingValues.values()) {
122874
+ candidate.set(`value:${row.id}`, {
122875
+ recordKind: "value",
122876
+ recordId: row.id,
122877
+ contentHash: "",
122878
+ deleted: false,
122879
+ data: { id: row.id, ...encodeStoredValueRowFields(row) }
122880
+ });
122881
+ }
122882
+ const document = readPulledProjectDocumentV4(candidate);
122883
+ compilePulledProjectDocumentForEvaluationV4(document);
122884
+ collectionBindings = new CollectionBindingMaterializer(document);
122885
+ }
122886
+ const resolved = collectionBindings.resolvePath(rootId, segments);
122887
+ return resolved;
122888
+ };
122550
122889
  const resolvedCollectionPaths = resolveRootPathCollectionValuesV4({
122551
122890
  manifest,
122552
122891
  state: rootPathResolutionState,
122892
+ resolveVirtualPath,
122553
122893
  registry: valueLowerRegistry,
122554
122894
  siteForMember: (memberId) => {
122555
122895
  const span2 = sourceByKey.get(recordStateKey("member", memberId))?.span;
@@ -122578,6 +122918,7 @@ function computeWorkspaceStatus(workspace, options) {
122578
122918
  const resolvedVariantFolderPaths = resolveRootPathVariantFolderCollectionValuesV4({
122579
122919
  manifest,
122580
122920
  state: rootPathResolutionState,
122921
+ resolveVirtualPath,
122581
122922
  registry: valueLowerRegistry,
122582
122923
  siteForFolder: (folderId) => {
122583
122924
  const span2 = sourceByKey.get(
@@ -122664,7 +123005,7 @@ function computeWorkspaceStatus(workspace, options) {
122664
123005
  }
122665
123006
  }
122666
123007
  }
122667
- const rootRecords = rootValues.records;
123008
+ const rootRecords = [...rootValues.records];
122668
123009
  const variantValues = lowerVariantValueSourcesV4(
122669
123010
  loweringRecords,
122670
123011
  manifest,
@@ -122680,8 +123021,71 @@ function computeWorkspaceStatus(workspace, options) {
122680
123021
  records: supplementalRecords,
122681
123022
  manifest,
122682
123023
  state: rootPathResolutionState,
123024
+ resolveVirtualPath,
122683
123025
  registry: valueLowerRegistry
122684
123026
  });
123027
+ if (collectionBindings !== void 0) {
123028
+ const bindingSeeds = new Map(authoredValueSeeds);
123029
+ const rootRecordsById = new Map(
123030
+ rootRecords.filter((record6) => record6.recordKind === "value").map((record6) => [record6.recordId, record6])
123031
+ );
123032
+ const rootMembers = /* @__PURE__ */ new Map();
123033
+ for (const record6 of Object.values(rootPathResolutionState)) {
123034
+ if (record6.recordKind === "member" && isObjectRecord2(record6.data) && typeof record6.data.valueId === "string")
123035
+ rootMembers.set(record6.data.valueId, record6.recordId);
123036
+ }
123037
+ for (const row of collectionBindings.rows.values()) {
123038
+ if (rootPathResolutionState[`value:${row.id}`] === void 0) continue;
123039
+ const existing = rootRecordsById.get(row.id);
123040
+ if (existing !== void 0) Object.assign(existing.fileFields, row);
123041
+ else
123042
+ rootRecords.push({
123043
+ recordKind: "value",
123044
+ recordId: row.id,
123045
+ file: "Root.neo",
123046
+ line: 1,
123047
+ fileFields: { ...row }
123048
+ });
123049
+ }
123050
+ for (const [rootId, rowIds] of collectionBindings.pathRowsByRootId) {
123051
+ const memberId = rootMembers.get(rootId);
123052
+ const root = collectionBindings.rowForValue(rootId);
123053
+ if (memberId === void 0 || root === void 0) continue;
123054
+ const seed = authoredValueSeeds.get(memberId);
123055
+ const rows = new Map((seed?.values ?? []).map((row) => [row.id, row]));
123056
+ for (const id2 of rowIds) {
123057
+ if (id2 === rootId) continue;
123058
+ const row = collectionBindings.rowForValue(id2);
123059
+ const location3 = collectionBindings.locationForValue(id2);
123060
+ if (row === void 0 || !isLiteralValueContent(row) || location3 === void 0)
123061
+ throw new Error(`Collection binding row ${id2} has no member.`);
123062
+ const isNew = rootPathResolutionState[`value:${id2}`] === void 0;
123063
+ const classId = row.classId ?? (isMemberClassBase(location3.member) ? location3.member.classId : null);
123064
+ const authored = staticValueSeedRow(
123065
+ {
123066
+ ...row,
123067
+ classId,
123068
+ ...isNew && classId !== null ? {
123069
+ constructorArgs: row.constructorArgs ?? {},
123070
+ instanceConstructorId: row.instanceConstructorId ?? null
123071
+ } : {}
123072
+ },
123073
+ location3.memberId
123074
+ );
123075
+ rows.set(id2, authored);
123076
+ if (isNew) valueLowerRegistry.pendingValues.set(id2, authored);
123077
+ }
123078
+ bindingSeeds.set(memberId, {
123079
+ ...seed,
123080
+ nodeType: "literal",
123081
+ valueId: rootId,
123082
+ value: root.value,
123083
+ classId: root.classId ?? null,
123084
+ values: [...rows.values()]
123085
+ });
123086
+ }
123087
+ authoredValueSeeds = bindingSeeds;
123088
+ }
122685
123089
  const prospectiveState = overlayProspectiveSourceRecords(
122686
123090
  loweringRecords,
122687
123091
  documents,
@@ -122790,16 +123194,9 @@ function computeWorkspaceStatus(workspace, options) {
122790
123194
  if (recordState.recordKind === "value") {
122791
123195
  deletedValueIds.add(recordState.recordId);
122792
123196
  }
122793
- changes.push({
122794
- kind: "delete",
122795
- recordKind: recordState.recordKind,
122796
- recordId: recordState.recordId,
122797
- file: recordFile,
122798
- baseData: recordState.data,
122799
- baseContentHash: recordState.contentHash,
122800
- casBaseHash: recordState.conflictServerHash !== void 0 ? recordState.conflictServerHash : recordState.contentHash
122801
- });
123197
+ changes.push(recordDeleteChange(recordState, recordFile));
122802
123198
  }
123199
+ appendCollectionEntryDeletes(workspace, reconstructed3, changes);
122803
123200
  const sourceTextByUri = new Map(
122804
123201
  sourceEntries.map((entry) => [entry.relPath, entry.source])
122805
123202
  );
@@ -124071,6 +124468,8 @@ var ANIMATION_DOCUMENT_RECORD_KINDS, ANIMATION_REPLAY_RECORD_KINDS, PROSPECTIVE_
124071
124468
  var init_workspace_status_core = __esm({
124072
124469
  "src/project-source/workspace-status-core.ts"() {
124073
124470
  "use strict";
124471
+ init_members();
124472
+ init_collection_binding_materialization();
124074
124473
  init_localizable_member_value_writes();
124075
124474
  init_localized_instance_override_writes();
124076
124475
  init_localization2();
@@ -124115,6 +124514,7 @@ var init_workspace_status_core = __esm({
124115
124514
  init_materialized_construction_cache();
124116
124515
  init_project_version_whole_graph_validation();
124117
124516
  init_value_base_desync();
124517
+ init_inheritance();
124118
124518
  ANIMATION_DOCUMENT_RECORD_KINDS = [
124119
124519
  ProjectRecordKind.Project,
124120
124520
  ProjectRecordKind.Class,
@@ -126741,6 +127141,7 @@ function collectionValueObligations(manifest) {
126741
127141
  for (const member of manifest.members) {
126742
127142
  if (member.kind !== "lookup") continue;
126743
127143
  if (typeof member.collectionValueId !== "string") continue;
127144
+ if (member.collectionValueId.startsWith("__pending__:root-path:")) continue;
126744
127145
  obligations.push({
126745
127146
  kind: "collectionValue",
126746
127147
  site: {
@@ -127125,6 +127526,7 @@ function memberInitializerRequiresEvaluation(context, member, expression, source
127125
127526
  function isSchemaMemberValueRead(context, expression, source) {
127126
127527
  const path = memberPath2(annotatedValue(expression).expression);
127127
127528
  if (path === null) return false;
127529
+ if (path.startsWith("root.")) return true;
127128
127530
  const parts = path.split(".");
127129
127531
  const name = parts.at(-1);
127130
127532
  if (name === void 0) return false;
@@ -131369,10 +131771,9 @@ function classValueBody(context, member, value, visited, targetTyped, outerEnvir
131369
131771
  `${key} = ${emitValue(context, emittedChildMember, childId, {
131370
131772
  environment,
131371
131773
  definitionSite: !context.symbolsByValueId.has(childId),
131372
- // A class slot recovers this row from the owning value and schema key.
131373
- // P61 creation metadata and localization back-pointers must not turn
131374
- // that structural child into an explicitly identified source value.
131375
- writeIdentity: false,
131774
+ // Existing slots recover identity from storage. A newly pinned virtual
131775
+ // slot must keep its derived pending identity through repeat lowering.
131776
+ writeIdentity: pendingVirtualValueIdentity(childId) !== null,
131376
131777
  visited
131377
131778
  })}`
131378
131779
  );
@@ -133038,6 +133439,7 @@ var INFERRED_GENERIC_CLASS_PREFIX, MEMBER_KIND_DICTIONARY2, MEMBER_KIND_LIST2, M
133038
133439
  var init_value_sources = __esm({
133039
133440
  "src/project-source/value-sources.ts"() {
133040
133441
  "use strict";
133442
+ init_member_value_id();
133041
133443
  init_effective_value_comparison();
133042
133444
  init_member_declared_type_info();
133043
133445
  init_constructor_argument_ownership();
@@ -145620,7 +146022,7 @@ var init_registry2 = __esm({
145620
146022
  "../packages/manifest/schema-contract/registry.mjs"() {
145621
146023
  "use strict";
145622
146024
  PROJECT_SCHEMA_CONTRACT = Object.freeze({
145623
- cliVersion: "0.61.1",
146025
+ cliVersion: "0.61.3",
145624
146026
  projectFileUploadBatchSize: 32,
145625
146027
  documentRecords: {
145626
146028
  member: {
@@ -154300,7 +154702,7 @@ There is no --keep-current: preserving current semantic state defines flatten.
154300
154702
  async function main() {
154301
154703
  const args = parseArgs(process.argv.slice(2));
154302
154704
  if (args.command === "--version") {
154303
- console.log("0.61.1");
154705
+ console.log("0.61.3");
154304
154706
  return;
154305
154707
  }
154306
154708
  if (args.command === null || args.command === "help" || args.command === "--help" || args.command === "-h") {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@neocompose/cli",
3
- "version": "0.61.1",
3
+ "version": "0.61.3",
4
4
  "description": "Neo Compose native project-source CLI with bidirectional sync.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -9,7 +9,7 @@ description: >-
9
9
  `@neocompose/cli` or `node cli/bin/neo.mjs` in the neo-compose repository.
10
10
  ---
11
11
 
12
- <!-- reviewed-through-cli: 0.61.1 -->
12
+ <!-- reviewed-through-cli: 0.61.3 -->
13
13
 
14
14
  # Neo Compose CLI
15
15
 
@@ -110,7 +110,7 @@ wrappers.
110
110
  The marker near the top of `SKILL.md` must exactly match the package version:
111
111
 
112
112
  ```html
113
- <!-- reviewed-through-cli: 0.61.1 -->
113
+ <!-- reviewed-through-cli: 0.61.3 -->
114
114
  ```
115
115
 
116
116
  The quoted version above is checked too, so this instruction cannot go stale
@@ -78,6 +78,12 @@ compilation, schema-commit materialization, seeded structural rows, and
78
78
  whole-graph validation — so a rejection reports before anything is sent. It
79
79
  does not prompt, upload, commit, or rewrite source.
80
80
 
81
+ Removing a class or collection declaration also deletes its hidden collection
82
+ entry members, including nested lists and dictionaries. Replacing a collection
83
+ with a scalar deletes its abandoned entries too. Entries used by surviving
84
+ declarations are retained. Review these derived deletions in `neo status` or
85
+ `neo diff` before pushing.
86
+
81
87
  Dry-run also compares the CLI's derived-edge extractor with the server version
82
88
  saved by the last pull. Run `neo pull` after upgrading the CLI or when an older
83
89
  workspace lacks that metadata. A newer server extractor requires a CLI
@@ -219,6 +219,10 @@ qualified static, or reached through a stable root path. For example,
219
219
  `protected int Slots = FeatureFlags.StartInventorySize;` evaluates the current
220
220
  static field or getter whenever a new instance is constructed; it is not baked
221
221
  into a persisted literal during push.
222
+ Root paths such as `root.Save.World.Time.Year` remain executable even when
223
+ `World` and `Time` exist only through construction defaults. This also applies
224
+ inside `new() { Year = root.Save.World.Time.Year }`: the child field retains
225
+ its initializer and reads the root when the instance is constructed.
222
226
 
223
227
  Declare overloadable constructors as members when each constructor body owns
224
228
  its parameter scope:
@@ -130,7 +130,11 @@ the edit is not a target, and lookup/dialogue references must still satisfy
130
130
  assignability, collection membership, multiplicity, and group constraints.
131
131
 
132
132
  `collectionValue` references follow the same post-push resolution and
133
- membership rules as member defaults.
133
+ membership rules as member defaults. Name the collection itself, for example
134
+ `Reference(root.Assets.Quests.Daily)`. A path may traverse construction defaults
135
+ and declarations created in the same push. Preparation materializes the target
136
+ collection and its required ancestor rows together, preserving the virtual
137
+ identities. No preliminary push or manual value ID is needed.
134
138
 
135
139
  ## Defaults and localization
136
140