@neocompose/cli 0.55.0 → 0.56.0

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,11 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.56.0] - 2026-09-23
4
+
5
+ - `@storage(allowed: .Writable)` declares a member that is always writable at runtime and persists wherever its owner persists: Save under a Save parent, Session otherwise. NeoScript can write it through any receiver, including one read from Assets, and animation clips can target it. `neo pull` writes it back in the same form. Earlier CLIs fail on a project that uses it, so upgrade before pulling.
6
+ - `@storage(allowed: .Writable)` on a class requires every placement of the class to resolve to Save or Session.
7
+ - An override below a `.Writable` member may narrow it to `.Save` or `.Session`. Declaring `.Immutable` or `.Inherit` on it fails at the member's position and names the `.Writable` ancestor. `.Writable` on a static member also fails at its position. A `readonly` member under or declaring `.Writable` fails with the existing "Read-only field … requires resolved @storage(allowed: .Immutable)" error.
8
+
3
9
  ## [0.55.0] - 2026-09-23
4
10
 
5
11
  - `@settings` before a `List` element or `Dictionary` value type configures the collection's entries, such as `List<@settings(format: .Plain) string>` or `Dictionary<string, @settings(min: 0) int>`. `neo pull` writes entry settings in this form. Switching entries between Localized and Plain converts every entry's text exactly as the same change on a string member does. Annotations on a Dictionary key, another generic's type argument, or an override's element type are reported at their position. Run `neo pull` before your first push so entry settings set in the web app appear in source.
package/dist/neo.mjs CHANGED
@@ -18769,13 +18769,15 @@ var init_project_schema_contract_generated = __esm({
18769
18769
  "Inherit",
18770
18770
  "Immutable",
18771
18771
  "Save",
18772
- "Session"
18772
+ "Session",
18773
+ "Writable"
18773
18774
  ],
18774
18775
  "NeoAllowedStorage": [
18775
18776
  "Any",
18776
18777
  "Immutable",
18777
18778
  "Save",
18778
- "Session"
18779
+ "Session",
18780
+ "Writable"
18779
18781
  ],
18780
18782
  "NeoListKind": [
18781
18783
  "Ordered",
@@ -32457,10 +32459,22 @@ function resolvedAllowedStorage(classId, environment) {
32457
32459
  }
32458
32460
  return null;
32459
32461
  }
32460
- function staticDefaultWritability(classId, environment) {
32461
- const allowed = resolvedAllowedStorage(classId, environment);
32462
+ function constraintDefaultWritability(allowed) {
32462
32463
  return allowed === "immutable" || allowed === "save" || allowed === "session" ? allowed : "session";
32463
32464
  }
32465
+ function declaredStorageWritability(storage, staticOwnerClassId, environment) {
32466
+ if (storage === "immutable" || storage === "save" || storage === "session") {
32467
+ return storage;
32468
+ }
32469
+ if (storage === "writable") return "runtime";
32470
+ if (!staticOwnerClassId) return void 0;
32471
+ return staticDefaultWritability(staticOwnerClassId, environment);
32472
+ }
32473
+ function staticDefaultWritability(classId, environment) {
32474
+ return constraintDefaultWritability(
32475
+ resolvedAllowedStorage(classId, environment)
32476
+ );
32477
+ }
32464
32478
  function createRootType(ids, environment) {
32465
32479
  const definitions = [
32466
32480
  ["Assets", ids.assets, false],
@@ -32522,7 +32536,7 @@ function effectiveMemberWritability(members, classes, roots) {
32522
32536
  seen.add(memberId);
32523
32537
  const member = memberById2.get(memberId);
32524
32538
  if (!member) return null;
32525
- if (member.storage === "immutable" || member.storage === "save" || member.storage === "session") {
32539
+ if (member.storage === "immutable" || member.storage === "save" || member.storage === "session" || member.storage === "writable") {
32526
32540
  return member.storage;
32527
32541
  }
32528
32542
  return typeof member.overrideOf === "string" ? declaredStorage(member.overrideOf, seen) : null;
@@ -32545,8 +32559,8 @@ function effectiveMemberWritability(members, classes, roots) {
32545
32559
  seen.add(currentId);
32546
32560
  const current = classById.get(currentId);
32547
32561
  if (!current) break;
32548
- if (current.allowedStorage === "immutable" || current.allowedStorage === "save" || current.allowedStorage === "session") {
32549
- return current.allowedStorage;
32562
+ if (typeof current.allowedStorage === "string") {
32563
+ return constraintDefaultWritability(current.allowedStorage);
32550
32564
  }
32551
32565
  currentId = typeof current.extendsClassId === "string" ? current.extendsClassId : null;
32552
32566
  }
@@ -32581,6 +32595,7 @@ function effectiveMemberWritability(members, classes, roots) {
32581
32595
  }
32582
32596
  const effective = /* @__PURE__ */ new Map();
32583
32597
  const anchored = /* @__PURE__ */ new Set();
32598
+ const writable = /* @__PURE__ */ new Set();
32584
32599
  const rootStorage = /* @__PURE__ */ new Map();
32585
32600
  if (typeof roots.assets === "string") {
32586
32601
  rootStorage.set(roots.assets, "immutable");
@@ -32590,16 +32605,19 @@ function effectiveMemberWritability(members, classes, roots) {
32590
32605
  rootStorage.set(roots.session, "session");
32591
32606
  }
32592
32607
  for (const [memberId, member] of memberById2) {
32593
- const root = rootStorage.get(memberId);
32594
- const declared = declaredStorage(memberId);
32595
- let storage = root ?? declared;
32596
- if (storage === void 0 || storage === null) {
32608
+ const declared = rootStorage.get(memberId) ?? declaredStorage(memberId);
32609
+ if (declared === "writable") {
32610
+ writable.add(memberId);
32611
+ continue;
32612
+ }
32613
+ let storage = declared;
32614
+ if (storage === null) {
32597
32615
  const owner = optionalRecord(member.owner);
32598
32616
  if (member.modifier === "static" && owner?.kind === "classMember" && typeof owner.classId === "string") {
32599
32617
  storage = staticDefault(owner.classId);
32600
32618
  }
32601
32619
  }
32602
- if (storage === void 0 || storage === null) continue;
32620
+ if (storage === null) continue;
32603
32621
  effective.set(memberId, /* @__PURE__ */ new Set([storage]));
32604
32622
  anchored.add(memberId);
32605
32623
  }
@@ -32609,8 +32627,11 @@ function effectiveMemberWritability(members, classes, roots) {
32609
32627
  for (const memberId of memberById2.keys()) {
32610
32628
  if (anchored.has(memberId)) continue;
32611
32629
  const next = new Set(effective.get(memberId) ?? []);
32630
+ const isWritable = writable.has(memberId);
32612
32631
  for (const parentId of parents.get(memberId) ?? []) {
32613
- for (const storage of effective.get(parentId) ?? []) next.add(storage);
32632
+ for (const storage of effective.get(parentId) ?? []) {
32633
+ next.add(isWritable && storage === "immutable" ? "session" : storage);
32634
+ }
32614
32635
  }
32615
32636
  const current = effective.get(memberId);
32616
32637
  if (current?.size === next.size || next.size === 0) continue;
@@ -32741,7 +32762,16 @@ function memberSymbol(member, schemaKey, environment, field) {
32741
32762
  }
32742
32763
  const writable = isReadOnly ? false : kind === "computed" ? computedHasSetter(member, field) : true;
32743
32764
  const storage = typeof member.storage === "string" ? member.storage : void 0;
32744
- const writability = isReadOnly ? "readOnly" : kind === "computed" ? writable ? "setter" : "readOnly" : !writable ? "readOnly" : storage === "immutable" || storage === "save" || storage === "session" ? storage : isStatic && ownerClassId ? staticDefaultWritability(ownerClassId, environment) : void 0;
32765
+ let writability;
32766
+ if (!writable) writability = "readOnly";
32767
+ else if (kind === "computed") writability = "setter";
32768
+ else {
32769
+ writability = declaredStorageWritability(
32770
+ storage,
32771
+ isStatic ? ownerClassId : void 0,
32772
+ environment
32773
+ );
32774
+ }
32745
32775
  const lookupCollectionMemberId = kind === "lookup" ? string2(member.collectionMemberId, `${field}.collectionMemberId`) : null;
32746
32776
  const lookupCollectionWritability = lookupCollectionMemberId === null ? void 0 : environment.effectiveWritabilityByMemberId.get(
32747
32777
  lookupCollectionMemberId
@@ -40103,7 +40133,13 @@ var init_codecs = __esm({
40103
40133
  };
40104
40134
  MEMBER_MODIFIERS = ["virtual", "sealed", "abstract", "static"];
40105
40135
  MEMBER_ACCESS = ["public", "protected", "private"];
40106
- MEMBER_STORAGE = ["inherit", "immutable", "save", "session"];
40136
+ MEMBER_STORAGE = [
40137
+ "inherit",
40138
+ "immutable",
40139
+ "save",
40140
+ "session",
40141
+ "writable"
40142
+ ];
40107
40143
  MEMBER_DISTRIBUTION = ["sparse", "packed"];
40108
40144
  STRING_FORMAT = ["localized", "plain"];
40109
40145
  MEMBER_SEARCH_BY = ["none", "memberKey"];
@@ -40823,7 +40859,7 @@ function schemaClassToDocument(schemaClass2) {
40823
40859
  uiVisibility: schemaClass2.hiddenInMemberSelector ? 1 : void 0,
40824
40860
  modifier: schemaClass2.declarationModifier === "abstract" ? 1 : schemaClass2.declarationModifier === "sealed" ? 2 : void 0,
40825
40861
  system: systemToDocument(schemaClass2.system),
40826
- allowedStorage: schemaClass2.allowedStorage === null ? void 0 : ["immutable", "save", "session"].indexOf(schemaClass2.allowedStorage) + 1,
40862
+ allowedStorage: schemaClass2.allowedStorage === null ? void 0 : CLASS_ALLOWED_STORAGE.indexOf(schemaClass2.allowedStorage),
40827
40863
  allowedStorageKeys: schemaClass2.allowedStorageKeys,
40828
40864
  // An unconstrained param is stored with no `constraint` key. The manifest
40829
40865
  // spells that absence `null` for a uniform in-memory shape, so emitting
@@ -40846,7 +40882,7 @@ function schemaClassToDocument(schemaClass2) {
40846
40882
  function allowedStorageFromDocument(value, record6) {
40847
40883
  const storage = ordinalValue(
40848
40884
  value ?? 0,
40849
- ["any", "immutable", "save", "session"],
40885
+ CLASS_ALLOWED_STORAGE,
40850
40886
  record6,
40851
40887
  "allowedStorage"
40852
40888
  );
@@ -40893,12 +40929,19 @@ function optionalDocsTextProperty(value, record6) {
40893
40929
  const docsText = optionalDocsText(value, record6, "docsText");
40894
40930
  return docsText === void 0 ? {} : { docsText };
40895
40931
  }
40896
- var CLASS_SCHEMA_RECORD_ADAPTER;
40932
+ var CLASS_ALLOWED_STORAGE, CLASS_SCHEMA_RECORD_ADAPTER;
40897
40933
  var init_class = __esm({
40898
40934
  "src/project-manifest/record-adapters/class.ts"() {
40899
40935
  "use strict";
40900
40936
  init_contracts();
40901
40937
  init_codecs();
40938
+ CLASS_ALLOWED_STORAGE = [
40939
+ "any",
40940
+ "immutable",
40941
+ "save",
40942
+ "session",
40943
+ "writable"
40944
+ ];
40902
40945
  CLASS_SCHEMA_RECORD_ADAPTER = {
40903
40946
  recordKind: "class",
40904
40947
  manifestCollection: "classes",
@@ -42999,7 +43042,7 @@ var init_schema_member = __esm({
42999
43042
  defaultValue: nullable(schemaDefaultValueSchema),
43000
43043
  overrideOf: nullableId2,
43001
43044
  system: nullable(schemaSystemMetadataSchema),
43002
- storage: nullable(picklist(["immutable", "save", "session"])),
43045
+ storage: nullable(picklist(["immutable", "save", "session", "writable"])),
43003
43046
  distribution: nullable(picklist(["sparse", "packed"])),
43004
43047
  storageKey: nullableId2
43005
43048
  };
@@ -44665,7 +44708,7 @@ function booleanAt(value, path) {
44665
44708
  }
44666
44709
  function nullableStorage(value, path) {
44667
44710
  if (value === null) return;
44668
- stringIn(value, /* @__PURE__ */ new Set(["immutable", "save", "session"]), path);
44711
+ stringIn(value, /* @__PURE__ */ new Set(["immutable", "save", "session", "writable"]), path);
44669
44712
  }
44670
44713
  function stringIn(value, allowed, path) {
44671
44714
  const result = stringAt(value, path);
@@ -45434,15 +45477,59 @@ var init_class_generics = __esm({
45434
45477
 
45435
45478
  // ../src/models/members/member-storage.ts
45436
45479
  function isMemberStorageKind(value) {
45437
- return value === 0 /* Inherit */ || value === 1 /* Immutable */ || value === 2 /* Save */ || value === 3 /* Session */;
45480
+ return value === 0 /* Inherit */ || value === 1 /* Immutable */ || value === 2 /* Save */ || value === 3 /* Session */ || value === 4 /* Writable */;
45438
45481
  }
45439
45482
  function isEffectiveMemberStorage(value) {
45483
+ return value === 1 /* Immutable */ || value === 2 /* Save */ || value === 3 /* Session */;
45484
+ }
45485
+ function isClassAllowedStorage(value) {
45440
45486
  if (!isMemberStorageKind(value)) return false;
45441
45487
  return value !== 0 /* Inherit */;
45442
45488
  }
45489
+ function childEffectiveStorage(declared, parent) {
45490
+ switch (declared) {
45491
+ case 0 /* Inherit */:
45492
+ return parent;
45493
+ case 4 /* Writable */:
45494
+ return parent === 2 /* Save */ ? 2 /* Save */ : 3 /* Session */;
45495
+ default:
45496
+ return declared;
45497
+ }
45498
+ }
45499
+ function constraintDefaultStorage(constraint) {
45500
+ if (constraint === null) return 3 /* Session */;
45501
+ if (constraint === 4 /* Writable */) {
45502
+ return 3 /* Session */;
45503
+ }
45504
+ return constraint;
45505
+ }
45506
+ function createWritableAncestorLookup(memberById2) {
45507
+ const memo = /* @__PURE__ */ new Map();
45508
+ const lookup = (member) => {
45509
+ const cached = memo.get(member.id);
45510
+ if (cached !== void 0) return cached;
45511
+ memo.set(member.id, null);
45512
+ const baseId = member.extendsMemberId;
45513
+ const base = baseId === void 0 ? void 0 : memberById2(baseId);
45514
+ if (base === void 0) return null;
45515
+ const result = base.storage === 4 /* Writable */ ? base : lookup(base);
45516
+ memo.set(member.id, result);
45517
+ return result;
45518
+ };
45519
+ return lookup;
45520
+ }
45521
+ var MemberStorageKind;
45443
45522
  var init_member_storage = __esm({
45444
45523
  "../src/models/members/member-storage.ts"() {
45445
45524
  "use strict";
45525
+ MemberStorageKind = /* @__PURE__ */ ((MemberStorageKind3) => {
45526
+ MemberStorageKind3[MemberStorageKind3["Inherit"] = 0] = "Inherit";
45527
+ MemberStorageKind3[MemberStorageKind3["Immutable"] = 1] = "Immutable";
45528
+ MemberStorageKind3[MemberStorageKind3["Save"] = 2] = "Save";
45529
+ MemberStorageKind3[MemberStorageKind3["Session"] = 3] = "Session";
45530
+ MemberStorageKind3[MemberStorageKind3["Writable"] = 4] = "Writable";
45531
+ return MemberStorageKind3;
45532
+ })(MemberStorageKind || {});
45446
45533
  }
45447
45534
  });
45448
45535
 
@@ -47419,7 +47506,7 @@ function isNeoSchemaClassBase(value) {
47419
47506
  const v = value;
47420
47507
  return typeof v?.name === "string" && isValidDocsText(v.docsText) && isClassSchema(v?.schema) && isOptionalSchemaKeyOrder(v?.schemaKeyOrder) && (v.implementsInterfaceIds === void 0 || Array.isArray(v.implementsInterfaceIds) && v.implementsInterfaceIds.every(
47421
47508
  (interfaceId) => typeof interfaceId === "string" && interfaceId.length > 0
47422
- )) && (v.uiVisibility === void 0 || v.uiVisibility === 0 /* Visible */ || v.uiVisibility === 1 /* Hidden */) && (v.modifier === void 0 || v.modifier === 0 /* Open */ || v.modifier === 1 /* Abstract */ || v.modifier === 2 /* Sealed */) && (v.allowedStorage === void 0 || isEffectiveMemberStorage(v.allowedStorage)) && (v.allowedStorageKeys === void 0 || Array.isArray(v.allowedStorageKeys) && v.allowedStorageKeys.every((key) => typeof key === "string")) && (v.genericParams === void 0 || Array.isArray(v.genericParams) && v.genericParams.every(isGenericParamDeclaration)) && (v.extendsGenericBindings === void 0 || isGenericBindingsRecord(v.extendsGenericBindings)) && (v.constructorIds === void 0 || Array.isArray(v.constructorIds) && v.constructorIds.every(
47509
+ )) && (v.uiVisibility === void 0 || v.uiVisibility === 0 /* Visible */ || v.uiVisibility === 1 /* Hidden */) && (v.modifier === void 0 || v.modifier === 0 /* Open */ || v.modifier === 1 /* Abstract */ || v.modifier === 2 /* Sealed */) && (v.allowedStorage === void 0 || isClassAllowedStorage(v.allowedStorage)) && (v.allowedStorageKeys === void 0 || Array.isArray(v.allowedStorageKeys) && v.allowedStorageKeys.every((key) => typeof key === "string")) && (v.genericParams === void 0 || Array.isArray(v.genericParams) && v.genericParams.every(isGenericParamDeclaration)) && (v.extendsGenericBindings === void 0 || isGenericBindingsRecord(v.extendsGenericBindings)) && (v.constructorIds === void 0 || Array.isArray(v.constructorIds) && v.constructorIds.every(
47423
47510
  (constructorId) => typeof constructorId === "string" && constructorId.length > 0
47424
47511
  ) && new Set(v.constructorIds).size === v.constructorIds.length) && (v.requiredConstructorId === void 0 || typeof v.requiredConstructorId === "string" && v.requiredConstructorId.length > 0 && (v.constructorIds ?? []).length === 0) && (v.targetMemberId === void 0 || typeof v.targetMemberId === "string" && v.targetMemberId.length > 0) && (v.system === void 0 || isSystemMetadata(v.system));
47425
47512
  }
@@ -53020,7 +53107,7 @@ function assertOwnedSchemaIsImmutable(args) {
53020
53107
  const bindingKey = `${args.memberId}\0${genericEnvironmentKey(args.genericEnv)}`;
53021
53108
  if (args.visitedBindings.has(bindingKey)) return;
53022
53109
  args.visitedBindings.add(bindingKey);
53023
- if (resolved.storage === 2 /* Save */ || resolved.storage === 3 /* Session */) {
53110
+ if (resolved.storage === 2 /* Save */ || resolved.storage === 3 /* Session */ || resolved.storage === 4 /* Writable */) {
53024
53111
  throw new Error(
53025
53112
  `Read-only member "${args.label}" has a non-Immutable owned default child "${resolved.name}"; ${args.ownerContext}.`
53026
53113
  );
@@ -56911,6 +56998,9 @@ var init_effective_storage = __esm({
56911
56998
  schemaIndex;
56912
56999
  parentIdsByMemberId = /* @__PURE__ */ new Map();
56913
57000
  effectiveByMemberId = /* @__PURE__ */ new Map();
57001
+ writableAncestor = createWritableAncestorLookup(
57002
+ (id2) => this.schemaIndex.member(id2)
57003
+ );
56914
57004
  resolving = /* @__PURE__ */ new Set();
56915
57005
  /** Declared storage of `memberId`, resolved through the extends chain. */
56916
57006
  declaredStorage(memberId) {
@@ -56919,6 +57009,15 @@ var init_effective_storage = __esm({
56919
57009
  this.schemaIndex.membersById
56920
57010
  );
56921
57011
  }
57012
+ /**
57013
+ * Nearest member in `memberId`'s `extendsMemberId` chain, excluding itself,
57014
+ * whose own record declares `Writable` (P93 §1.4), or null.
57015
+ */
57016
+ writableAncestorId(memberId) {
57017
+ const member = this.schemaIndex.member(memberId);
57018
+ if (member === void 0) return null;
57019
+ return this.writableAncestor(member)?.id ?? null;
57020
+ }
56922
57021
  /** Placement-parent member ids of `memberId` (empty for roots). */
56923
57022
  parentMemberIds(memberId) {
56924
57023
  if (this.ctx.rootStorage.has(memberId)) return [];
@@ -56942,21 +57041,21 @@ var init_effective_storage = __esm({
56942
57041
  result.add(rootStorage);
56943
57042
  } else {
56944
57043
  const declared = this.declaredStorage(memberId);
56945
- const member = this.schemaIndex.member(memberId);
56946
- if (isStaticMember(member) === true) {
56947
- if (declared !== 0 /* Inherit */) {
56948
- result.add(declared);
56949
- } else {
56950
- const declaringClassId = this.schemaIndex.firstSchemaPlacement(memberId)?.ownerClass.id;
56951
- const inherited = declaringClassId === void 0 ? null : resolveAllowedStorage(declaringClassId, this.ctx.classes);
56952
- result.add(inherited ?? 3 /* Session */);
56953
- }
56954
- } else if (declared !== 0 /* Inherit */) {
57044
+ if (isEffectiveMemberStorage(declared)) {
56955
57045
  result.add(declared);
57046
+ } else if (isStaticMember(this.schemaIndex.member(memberId))) {
57047
+ const declaringClassId = this.schemaIndex.firstSchemaPlacement(memberId)?.ownerClass.id;
57048
+ const constraint = declaringClassId === void 0 ? null : resolveAllowedStorage(declaringClassId, this.ctx.classes);
57049
+ result.add(
57050
+ childEffectiveStorage(
57051
+ declared,
57052
+ constraintDefaultStorage(constraint)
57053
+ )
57054
+ );
56956
57055
  } else {
56957
57056
  for (const parentId of this.parentMemberIds(memberId)) {
56958
57057
  for (const storage of this.effectiveStorages(parentId)) {
56959
- result.add(storage);
57058
+ result.add(childEffectiveStorage(declared, storage));
56960
57059
  }
56961
57060
  }
56962
57061
  }
@@ -56971,26 +57070,44 @@ var init_effective_storage = __esm({
56971
57070
  * Effective storage for one concrete placement chain, ordered root-first
56972
57071
  * (the project root at index 0, the target member last). Used when the
56973
57072
  * caller knows the exact path (NeoScript member walks, value trees).
57073
+ * Anchors at the root, the nearest concrete declaration, or a single-storage
57074
+ * static head, then folds the members below through `childEffectiveStorage`.
56974
57075
  */
56975
57076
  effectiveStorageForPath(memberIdsRootToLeaf) {
56976
- for (let i = memberIdsRootToLeaf.length - 1; i >= 0; i--) {
56977
- const memberId = memberIdsRootToLeaf[i];
56978
- const rootStorage = this.ctx.rootStorage.get(memberId);
56979
- if (rootStorage !== void 0 && i === 0) return rootStorage;
57077
+ let anchorIndex = memberIdsRootToLeaf.length - 1;
57078
+ let storage;
57079
+ for (; anchorIndex >= 0; anchorIndex--) {
57080
+ const memberId = memberIdsRootToLeaf[anchorIndex];
57081
+ if (anchorIndex === 0) storage = this.ctx.rootStorage.get(memberId);
57082
+ if (storage !== void 0) break;
56980
57083
  const declared = this.declaredStorage(memberId);
56981
- if (declared !== 0 /* Inherit */) return declared;
57084
+ if (isEffectiveMemberStorage(declared)) {
57085
+ storage = declared;
57086
+ break;
57087
+ }
57088
+ }
57089
+ if (storage === void 0) {
57090
+ anchorIndex = 0;
57091
+ storage = this.staticRootStorage(memberIdsRootToLeaf[0]);
57092
+ }
57093
+ for (let i = anchorIndex + 1; i < memberIdsRootToLeaf.length; i++) {
57094
+ storage = childEffectiveStorage(
57095
+ this.declaredStorage(memberIdsRootToLeaf[i]),
57096
+ storage
57097
+ );
56982
57098
  }
56983
- const rootId = memberIdsRootToLeaf[0];
56984
- const anchored = rootId === void 0 ? void 0 : this.ctx.rootStorage.get(rootId);
56985
- if (anchored !== void 0) return anchored;
57099
+ return storage;
57100
+ }
57101
+ /** A static chain head's storage when it resolves to exactly one. */
57102
+ staticRootStorage(rootId) {
56986
57103
  if (rootId !== void 0 && isStaticMember(this.schemaIndex.member(rootId))) {
56987
57104
  const staticStorages = this.effectiveStorages(rootId);
56988
57105
  if (staticStorages.size === 1) {
56989
- return staticStorages.values().next().value;
57106
+ for (const storage of staticStorages) return storage;
56990
57107
  }
56991
57108
  }
56992
57109
  throw new Error(
56993
- `Storage path did not resolve: no member in the chain declares a storage class and the chain head "${rootId ?? "(empty)"}" is not a stamped project root.`
57110
+ `Storage path did not resolve: no member in the chain declares an Immutable, Save, or Session storage class and the chain head "${rootId ?? "(empty)"}" is not a stamped project root.`
56994
57111
  );
56995
57112
  }
56996
57113
  /** Class's merged (inheritance-resolved) schema. */
@@ -64211,6 +64328,14 @@ function buildRootStorageMap(project) {
64211
64328
  }
64212
64329
  return map;
64213
64330
  }
64331
+ function writtenRootStorage(project, memberIds) {
64332
+ const map = /* @__PURE__ */ new Map();
64333
+ for (const root of PROJECT_ROOTS) {
64334
+ const memberId = project[root.projectField];
64335
+ if (memberIds.has(memberId)) map.set(memberId, root.storage);
64336
+ }
64337
+ return map;
64338
+ }
64214
64339
  function isProjectRootMember(project, memberId) {
64215
64340
  return PROJECT_ROOTS.some((root) => project[root.projectField] === memberId);
64216
64341
  }
@@ -64802,7 +64927,9 @@ function symbolWritability(memberId, context, staticOwnerClassId) {
64802
64927
  if (storage === 0 /* Inherit */ && staticOwnerClassId === null) {
64803
64928
  return {};
64804
64929
  }
64805
- const effectiveStorage = storage === 0 /* Inherit */ ? resolveAllowedStorage(staticOwnerClassId, context.vm.classes) ?? 3 /* Session */ : storage;
64930
+ const effectiveStorage = storage === 0 /* Inherit */ ? constraintDefaultStorage(
64931
+ resolveAllowedStorage(staticOwnerClassId, context.vm.classes)
64932
+ ) : storage;
64806
64933
  switch (effectiveStorage) {
64807
64934
  case 1 /* Immutable */:
64808
64935
  return { writability: "immutable" };
@@ -64810,6 +64937,8 @@ function symbolWritability(memberId, context, staticOwnerClassId) {
64810
64937
  return { writability: "save" };
64811
64938
  case 3 /* Session */:
64812
64939
  return { writability: "session" };
64940
+ case 4 /* Writable */:
64941
+ return { writability: "runtime" };
64813
64942
  }
64814
64943
  }
64815
64944
  function uniqueEffectiveWritability(memberId, context) {
@@ -101375,10 +101504,6 @@ var init_unity_static_members = __esm({
101375
101504
  });
101376
101505
 
101377
101506
  // ../src/models/animation/animation-clips.ts
101378
- function isWorldAnimationStructuralMember(memberId, members) {
101379
- const membersById = new Map(members.map((member) => [member.id, member]));
101380
- return isWorldAnimationStructuralMemberFromIndex(memberId, membersById);
101381
- }
101382
101507
  function isWorldAnimationStructuralMemberFromIndex(memberId, membersById) {
101383
101508
  const visited = /* @__PURE__ */ new Set();
101384
101509
  let currentId = memberId;
@@ -101389,6 +101514,35 @@ function isWorldAnimationStructuralMemberFromIndex(memberId, membersById) {
101389
101514
  }
101390
101515
  return false;
101391
101516
  }
101517
+ function worldOverridePathIsStructurallyEligible(memberIdsRootToLeaf, membersById) {
101518
+ for (const memberId of memberIdsRootToLeaf) {
101519
+ if (isWorldAnimationStructuralMemberFromIndex(memberId, membersById)) {
101520
+ return false;
101521
+ }
101522
+ const member = membersById.get(memberId);
101523
+ if (member === void 0) return false;
101524
+ const resolved = resolveMemberFromIndex(member, membersById);
101525
+ if (isStaticMember(resolved) || isReadOnlyMember(resolved)) return false;
101526
+ if (resolved.kind === 13 /* Function */ || resolved.kind === 23 /* NSFunction */ || resolved.kind === 10 /* NSProperty */ || resolved.kind === 6 /* List */ || resolved.kind === 5 /* Dictionary */) {
101527
+ return false;
101528
+ }
101529
+ }
101530
+ return true;
101531
+ }
101532
+ function animationTrackStorage(memberIdsRootToLeaf, membersById, childrenPrefix = []) {
101533
+ if (!worldOverridePathIsStructurallyEligible(memberIdsRootToLeaf, membersById)) {
101534
+ return null;
101535
+ }
101536
+ const path = [...childrenPrefix, ...memberIdsRootToLeaf];
101537
+ for (let index = path.length - 1; index >= 0; index -= 1) {
101538
+ const memberId = path[index];
101539
+ if (memberId === void 0) continue;
101540
+ const storage = declaredMemberStorageFromIndex(memberId, membersById);
101541
+ if (storage === 0 /* Inherit */) continue;
101542
+ return storage === 1 /* Immutable */ ? null : storage;
101543
+ }
101544
+ return null;
101545
+ }
101392
101546
  function assertAnimationClipDocumentValid(document, scope) {
101393
101547
  const context = new AnimationValidationContext(document);
101394
101548
  context.validateSegments(scope);
@@ -101416,7 +101570,6 @@ var init_animation_clips = __esm({
101416
101570
  init_member_storage();
101417
101571
  init_neoscript_types();
101418
101572
  init_neoscript_guards();
101419
- init_project_root_members();
101420
101573
  init_members();
101421
101574
  WORLD_ANIMATION_STRUCTURAL_MEMBER_ROOT_IDS = /* @__PURE__ */ new Set([
101422
101575
  WORLD_GRID_CHILDREN_MEMBER_ID,
@@ -101442,17 +101595,11 @@ var init_animation_clips = __esm({
101442
101595
  document.members.map((value) => [value.id, value])
101443
101596
  );
101444
101597
  this.valueById = new Map(document.values.map((value) => [value.id, value]));
101445
- this.storage = new EffectiveStorageResolver({
101446
- classes: document.classes,
101447
- members: document.members,
101448
- rootStorage: buildRootStorageMap(document.project)
101449
- });
101450
101598
  }
101451
101599
  document;
101452
101600
  classById;
101453
101601
  memberById;
101454
101602
  valueById;
101455
- storage;
101456
101603
  classHasWorldKind(classId, worldKind) {
101457
101604
  const visited = /* @__PURE__ */ new Set();
101458
101605
  let current = this.classById.get(classId);
@@ -101528,6 +101675,7 @@ var init_animation_clips = __esm({
101528
101675
  definitionNode: targetDefinition,
101529
101676
  memberPath: [],
101530
101677
  storagePath: [],
101678
+ childrenPrefix: [],
101531
101679
  visited: /* @__PURE__ */ new Set()
101532
101680
  });
101533
101681
  }
@@ -101612,7 +101760,8 @@ var init_animation_clips = __esm({
101612
101760
  overrideNode: overrides,
101613
101761
  definitionNode: child,
101614
101762
  memberPath: [`child:${childId}`],
101615
- storagePath: [...args.childStoragePath],
101763
+ storagePath: [],
101764
+ childrenPrefix: args.childStoragePath,
101616
101765
  visited: /* @__PURE__ */ new Set()
101617
101766
  });
101618
101767
  }
@@ -101955,12 +102104,13 @@ var init_animation_clips = __esm({
101955
102104
  `${args.label} targets "${boundChildName}.${targetEntry.schemaKey}" of kind ${MemberKind[targetMember.kind]}, but its segment plays ${MemberKind[valueBindingMember.kind]} values.`
101956
102105
  );
101957
102106
  }
101958
- if (!this.overrideLeafEligible(targetMember, [
101959
- ...args.childStoragePath,
101960
- targetMember.id
101961
- ])) {
102107
+ if (!this.overrideLeafEligible(
102108
+ targetMember,
102109
+ [targetMember.id],
102110
+ args.childStoragePath
102111
+ )) {
101962
102112
  throw new Error(
101963
- `${args.label} targets "${boundChildName}.${targetEntry.schemaKey}", which is not a writable Save/Session override leaf.`
102113
+ `${args.label} targets "${boundChildName}.${targetEntry.schemaKey}", which is not a writable Save, Session, or Writable override leaf.`
101964
102114
  );
101965
102115
  }
101966
102116
  }
@@ -102130,7 +102280,8 @@ var init_animation_clips = __esm({
102130
102280
  partial: overrideChild.value,
102131
102281
  path,
102132
102282
  schemaKey,
102133
- storagePath: args.storagePath
102283
+ storagePath: args.storagePath,
102284
+ childrenPrefix: args.childrenPrefix
102134
102285
  });
102135
102286
  continue;
102136
102287
  }
@@ -102161,9 +102312,13 @@ var init_animation_clips = __esm({
102161
102312
  });
102162
102313
  continue;
102163
102314
  }
102164
- if (!this.overrideLeafEligible(member, [...args.storagePath, member.id])) {
102315
+ if (!this.overrideLeafEligible(
102316
+ member,
102317
+ [...args.storagePath, member.id],
102318
+ args.childrenPrefix
102319
+ )) {
102165
102320
  throw new Error(
102166
- `Animation clip "${args.clipName}" frame ${args.frameIndex} member "${path.join(".")}" is not a writable Save/Session override leaf.`
102321
+ `Animation clip "${args.clipName}" frame ${args.frameIndex} member "${path.join(".")}" is not a writable Save, Session, or Writable override leaf.`
102167
102322
  );
102168
102323
  }
102169
102324
  }
@@ -102198,12 +102353,13 @@ var init_animation_clips = __esm({
102198
102353
  );
102199
102354
  }
102200
102355
  const kindLabel = MemberKind[kind];
102201
- if (!this.overrideLeafEligible(args.member, [
102202
- ...args.storagePath,
102203
- args.member.id
102204
- ])) {
102356
+ if (!this.overrideLeafEligible(
102357
+ args.member,
102358
+ [...args.storagePath, args.member.id],
102359
+ args.childrenPrefix
102360
+ )) {
102205
102361
  throw new Error(
102206
- `${prefix} ${kindLabel} member "${memberPath3}" cannot take a "${STRUCTURED_LEAF_PARTIAL_KEY}" field override; member "${args.member.name}" is not a writable Save/Session override leaf.`
102362
+ `${prefix} ${kindLabel} member "${memberPath3}" cannot take a "${STRUCTURED_LEAF_PARTIAL_KEY}" field override; member "${args.member.name}" is not a writable Save, Session, or Writable override leaf.`
102207
102363
  );
102208
102364
  }
102209
102365
  const violation = structuredLeafPartialViolation(
@@ -102244,24 +102400,9 @@ var init_animation_clips = __esm({
102244
102400
  throw new Error(`${descent}; its value is not a ${kindLabel} record.`);
102245
102401
  }
102246
102402
  }
102247
- overrideLeafEligible(member, storagePath) {
102248
- if (isReadOnlyMember(member) === true || isStaticMember(member))
102249
- return false;
102250
- if (isWorldAnimationStructuralMember(member.id, this.document.members)) {
102251
- return false;
102252
- }
102253
- if (member.kind === 13 /* Function */ || member.kind === 23 /* NSFunction */ || member.kind === 10 /* NSProperty */ || member.kind === 7 /* Class */ || member.kind === 6 /* List */ || member.kind === 5 /* Dictionary */) {
102254
- return false;
102255
- }
102256
- try {
102257
- const storage = this.storage.effectiveStorageForPath(storagePath);
102258
- return storage === 2 /* Save */ || storage === 3 /* Session */;
102259
- } catch {
102260
- const storages = this.storage.effectiveStorages(member.id);
102261
- return storages.size > 0 && [...storages].every(
102262
- (storage) => storage === 2 /* Save */ || storage === 3 /* Session */
102263
- );
102264
- }
102403
+ overrideLeafEligible(member, storagePath, childrenPrefix) {
102404
+ if (member.kind === 7 /* Class */) return false;
102405
+ return animationTrackStorage(storagePath, this.memberById, childrenPrefix) !== null;
102265
102406
  }
102266
102407
  authoredChildren(targetClassId, definitionNode) {
102267
102408
  const childEntry = mergeStoredInstanceSchema(
@@ -102285,9 +102426,8 @@ var init_animation_clips = __esm({
102285
102426
  (value) => typeof value === "string"
102286
102427
  ) : []
102287
102428
  ),
102288
- // Child values inherit storage from their concrete placement under the
102289
- // owner's resolved Children member. This mirrors the authoring panel's
102290
- // storage path instead of treating a shared child-class leaf as a root.
102429
+ // P93 §5: a child track's declaration walk continues through the
102430
+ // owner's resolved Children member, as the authoring pane's does.
102291
102431
  storagePath: [childEntry.memberId]
102292
102432
  };
102293
102433
  }
@@ -103250,7 +103390,7 @@ function readClass(value) {
103250
103390
  CLASS_VISIBILITY_KINDS,
103251
103391
  `${label} uiVisibility`
103252
103392
  );
103253
- if (value.allowedStorage != null && !isEffectiveMemberStorage(value.allowedStorage)) {
103393
+ if (value.allowedStorage != null && !isClassAllowedStorage(value.allowedStorage)) {
103254
103394
  throw new Error(`${label} allowedStorage is invalid.`);
103255
103395
  }
103256
103396
  if (value.genericParams !== void 0 && value.genericParams !== null) {
@@ -103632,6 +103772,14 @@ function assertProjectInterfaceDocumentValid(view, options = {}) {
103632
103772
  const classes = view.classes.map(readClass);
103633
103773
  const interfaces = view.interfaces.map(readInterface);
103634
103774
  const membersById = new Map(members.map((member) => [member.id, member]));
103775
+ for (const [rootId, stamped] of view.rootStorage ?? []) {
103776
+ const root = membersById.get(rootId);
103777
+ if (root === void 0 || root.storage === stamped) continue;
103778
+ const declared = root.storage === void 0 ? "none" : MemberStorageKind[root.storage];
103779
+ throw new Error(
103780
+ `Project root member "${root.name}" (${rootId}) must declare ${MemberStorageKind[stamped]} storage but declares ${declared}.`
103781
+ );
103782
+ }
103635
103783
  const classesById = new Map(
103636
103784
  classes.map((schemaClass2) => [schemaClass2.id, schemaClass2])
103637
103785
  );
@@ -103751,6 +103899,11 @@ function assertProjectInterfaceDocumentValid(view, options = {}) {
103751
103899
  `Static member "${member.name}" (${member.id}) must be a plain non-override member.`
103752
103900
  );
103753
103901
  }
103902
+ if (member.storage === 4 /* Writable */) {
103903
+ throw new Error(
103904
+ `Static member "${member.name}" (${member.id}) cannot declare Writable storage; a static member has no placement parent to resolve it against.`
103905
+ );
103906
+ }
103754
103907
  if (member.kind === 21) {
103755
103908
  throw new Error(
103756
103909
  `Generic placeholder member "${member.name}" (${member.id}) cannot be static.`
@@ -104055,6 +104208,19 @@ function assertProjectInterfaceDocumentValid(view, options = {}) {
104055
104208
  if (member.mutability === void 0) delete resolved.mutability;
104056
104209
  return resolved;
104057
104210
  };
104211
+ const writableAncestor2 = createWritableAncestorLookup(
104212
+ (id2) => membersById.get(id2)
104213
+ );
104214
+ for (const member of members) {
104215
+ if (member.storage !== 1 /* Immutable */ && member.storage !== 0 /* Inherit */) {
104216
+ continue;
104217
+ }
104218
+ const ancestor = writableAncestor2(member);
104219
+ if (ancestor === null) continue;
104220
+ throw new Error(
104221
+ `Member "${member.name}" (${member.id}) declares ${MemberStorageKind[member.storage]} storage but overrides Writable member "${ancestor.name}" (${ancestor.id}); an override of a Writable member may declare Writable, Save, or Session, or omit storage.`
104222
+ );
104223
+ }
104058
104224
  const rootAndEntryMemberIds = /* @__PURE__ */ new Set();
104059
104225
  for (const owner of members) {
104060
104226
  if (typeof owner.entryMemberId === "string") {
@@ -104196,7 +104362,7 @@ function assertProjectInterfaceDocumentValid(view, options = {}) {
104196
104362
  }
104197
104363
  }
104198
104364
  if (rawOwned.id === member.id && isAbstractMember(member)) return;
104199
- if (owned.storage === 2 /* Save */ || owned.storage === 3 /* Session */) {
104365
+ if (owned.storage === 2 /* Save */ || owned.storage === 3 /* Session */ || owned.storage === 4 /* Writable */) {
104200
104366
  throw new Error(
104201
104367
  `Read-only member "${member.name}" has a non-Immutable owned default child "${owned.name}"; ${ownerLabel}.`
104202
104368
  );
@@ -104727,7 +104893,7 @@ function assertProjectInterfaceDocumentValid(view, options = {}) {
104727
104893
  return resolved.setterCode === void 0 ? "NSProperty has no setter implementation." : null;
104728
104894
  }
104729
104895
  const storage = resolved.storage;
104730
- if (storage === 2 /* Save */ || storage === 3 /* Session */) {
104896
+ if (storage === 2 /* Save */ || storage === 3 /* Session */ || storage === 4 /* Writable */) {
104731
104897
  return null;
104732
104898
  }
104733
104899
  if (storage === 1 /* Immutable */) {
@@ -106765,6 +106931,14 @@ function assertStagedProjectInterfaceValid(projected, changes) {
106765
106931
  variants: projected.variants ?? [],
106766
106932
  variantFolders: projected.variantFolders ?? [],
106767
106933
  interfaces: projected.interfaces,
106934
+ rootStorage: writtenRootStorage(
106935
+ projected.project,
106936
+ new Set(
106937
+ changes.flatMap(
106938
+ (change) => change.recordKind === "member" ? [change.recordId] : []
106939
+ )
106940
+ )
106941
+ ),
106768
106942
  values: projected.values
106769
106943
  },
106770
106944
  {
@@ -107360,6 +107534,7 @@ var init_project_version_whole_graph_validation = __esm({
107360
107534
  init_core();
107361
107535
  init_animation_clips();
107362
107536
  init_projectInterfaceValidation();
107537
+ init_project_root_members();
107363
107538
  init_project_schema_identifier_validation();
107364
107539
  init_packed_value_write_fold();
107365
107540
  init_project_version_schema_commit();
@@ -109487,6 +109662,7 @@ function memberCommon(context, ownerClass, declaration, id2, owner, base) {
109487
109662
  const storage = annotation(declaration.annotations, "storage");
109488
109663
  const distribution = annotation(declaration.annotations, "distribution");
109489
109664
  const inheritedId = declaration.modifiers.includes("override") || declaration.modifiers.includes("sealed") ? inheritedMemberId(context, ownerClass, declaration.name) : null;
109665
+ assertWritableStorageRules(context, ownerClass, declaration, inheritedId);
109490
109666
  return {
109491
109667
  id: id2,
109492
109668
  source: sourceIdentity2(declaration, "member", declaration.name),
@@ -111523,7 +111699,74 @@ function numberArgument(value, name) {
111523
111699
  }
111524
111700
  function storageValue(value, name) {
111525
111701
  const enumValue2 = contextualEnumArgument(value, name)?.toLowerCase();
111526
- return enumValue2 === "immutable" || enumValue2 === "save" || enumValue2 === "session" ? enumValue2 : null;
111702
+ return enumValue2 === "immutable" || enumValue2 === "save" || enumValue2 === "session" || enumValue2 === "writable" ? enumValue2 : null;
111703
+ }
111704
+ function assertWritableStorageRules(context, ownerClass, declaration, inheritedId) {
111705
+ const site = `${ownerClass.name}.${declaration.name}`;
111706
+ const storage = annotation(declaration.annotations, "storage");
111707
+ const declared = storage ? contextualEnumArgument(storage, "allowed")?.toLowerCase() : void 0;
111708
+ if (declared === "writable" && declaration.modifiers.includes("static")) {
111709
+ throw new Error(
111710
+ `Static member ${site} cannot declare Writable storage: a static member has no parent to resolve it against. Declare Save or Session.`
111711
+ );
111712
+ }
111713
+ if (inheritedId === null) return;
111714
+ const ancestor = writableAncestor(context, inheritedId);
111715
+ if (ancestor === null) return;
111716
+ if (declared === "immutable") {
111717
+ throw new Error(
111718
+ `${site} cannot declare Immutable storage because it overrides ${ancestor}, which declares Writable. Declare Writable, Save, or Session, or omit @storage.`
111719
+ );
111720
+ }
111721
+ if (declared === "inherit") {
111722
+ throw new Error(
111723
+ `${site} cannot declare Inherit storage because it overrides ${ancestor}, which declares Writable. Declare Writable, Save, or Session, or omit @storage.`
111724
+ );
111725
+ }
111726
+ }
111727
+ function writableAncestor(context, memberId) {
111728
+ const memo = context.writableAncestors;
111729
+ const chain = /* @__PURE__ */ new Set();
111730
+ let currentId = memberId;
111731
+ let found = null;
111732
+ while (currentId !== null) {
111733
+ const known = memo.get(currentId);
111734
+ if (known !== void 0) {
111735
+ found = known;
111736
+ break;
111737
+ }
111738
+ if (chain.has(currentId)) break;
111739
+ chain.add(currentId);
111740
+ const step = storageChainStep(context, currentId);
111741
+ if (step === null) break;
111742
+ if (step.storage === "writable") {
111743
+ found = step.label;
111744
+ break;
111745
+ }
111746
+ currentId = step.overrideOf;
111747
+ }
111748
+ for (const id2 of chain) memo.set(id2, found);
111749
+ return found;
111750
+ }
111751
+ function storageChainStep(context, memberId) {
111752
+ const sourcePlacement = context.sourceMembersById.get(memberId);
111753
+ if (sourcePlacement !== void 0) {
111754
+ const { ownerClass, member } = sourcePlacement;
111755
+ const storage = annotation(member.annotations, "storage");
111756
+ return {
111757
+ label: `${ownerClass.name}.${member.name}`,
111758
+ storage: storage ? storageValue(storage, "allowed") : null,
111759
+ overrideOf: member.modifiers.includes("override") || member.modifiers.includes("sealed") ? inheritedMemberId(context, ownerClass, member.name) : null
111760
+ };
111761
+ }
111762
+ const persisted = context.baseMembers.get(memberId);
111763
+ if (persisted === void 0) return null;
111764
+ const ownerName = persisted.owner.kind === "classMember" ? context.baseClasses.get(persisted.owner.classId)?.name : void 0;
111765
+ return {
111766
+ label: ownerName === void 0 ? persisted.name : `${ownerName}.${persisted.name}`,
111767
+ storage: persisted.storage,
111768
+ overrideOf: persisted.overrideOf
111769
+ };
111527
111770
  }
111528
111771
  function referenceIdArgument(value, name) {
111529
111772
  const raw = argument(value, name);
@@ -112503,6 +112746,7 @@ function lowerProjectSchemaV4(base, analysis, options = {}) {
112503
112746
  enumOptionsByName,
112504
112747
  loweredMembers: /* @__PURE__ */ new Map(),
112505
112748
  entryTypesByEntryId: /* @__PURE__ */ new Map(),
112749
+ writableAncestors: /* @__PURE__ */ new Map(),
112506
112750
  placements: /* @__PURE__ */ new Map(),
112507
112751
  staticValueIdsBySymbol: staticValueIdsBySymbol3,
112508
112752
  settleInitializerReferences: createSchemaInitializerReferenceSettlerV4({
@@ -139241,7 +139485,7 @@ var init_registry2 = __esm({
139241
139485
  "schema-contract/registry.mjs"() {
139242
139486
  "use strict";
139243
139487
  PROJECT_SCHEMA_CONTRACT = Object.freeze({
139244
- cliVersion: "0.55.0",
139488
+ cliVersion: "0.56.0",
139245
139489
  projectFileUploadBatchSize: 32,
139246
139490
  documentRecords: {
139247
139491
  member: {
@@ -139583,8 +139827,8 @@ var init_registry2 = __esm({
139583
139827
  sdkDeclarations: {
139584
139828
  attributes: {
139585
139829
  enums: {
139586
- NeoStorage: ["Inherit", "Immutable", "Save", "Session"],
139587
- NeoAllowedStorage: ["Any", "Immutable", "Save", "Session"],
139830
+ NeoStorage: ["Inherit", "Immutable", "Save", "Session", "Writable"],
139831
+ NeoAllowedStorage: ["Any", "Immutable", "Save", "Session", "Writable"],
139588
139832
  NeoListKind: ["Ordered", "Unordered"],
139589
139833
  NeoDictionaryKeyKind: ["String", "Enum"],
139590
139834
  NeoClassVisibility: ["Visible", "Hidden"],
@@ -147062,7 +147306,7 @@ There is no --keep-current: preserving current semantic state defines flatten.
147062
147306
  async function main() {
147063
147307
  const args = parseArgs(process.argv.slice(2));
147064
147308
  if (args.command === "--version") {
147065
- console.log("0.55.0");
147309
+ console.log("0.56.0");
147066
147310
  return;
147067
147311
  }
147068
147312
  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.55.0",
3
+ "version": "0.56.0",
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.55.0 -->
12
+ <!-- reviewed-through-cli: 0.56.0 -->
13
13
 
14
14
  # Neo Compose CLI
15
15
 
@@ -97,7 +97,7 @@ wrappers.
97
97
  The marker near the top of `SKILL.md` must exactly match the package version:
98
98
 
99
99
  ```html
100
- <!-- reviewed-through-cli: 0.55.0 -->
100
+ <!-- reviewed-through-cli: 0.56.0 -->
101
101
  ```
102
102
 
103
103
  The quoted version above is checked too, so this instruction cannot go stale
@@ -103,6 +103,39 @@ Use context-aware typed annotations:
103
103
  Use numeric literals for numeric limits. Keep annotations separate instead of
104
104
  inventing a generic metadata bag.
105
105
 
106
+ ### Storage
107
+
108
+ A member's `@storage(allowed: ...)` is `.Immutable`, `.Save`, `.Session`, or
109
+ `.Writable`. Omit it to inherit the placement parent's storage. `.Writable` is
110
+ always writable at runtime and resolves from where the value is placed: Save
111
+ under a Save parent, Session under a Session or Immutable parent. Use it for a
112
+ member that NeoScript or an animation clip writes and whose value should persist
113
+ exactly when its owner persists. Animation clips can target `.Writable` members.
114
+
115
+ ```neo
116
+ class AnimationSpriteObject(SpriteInfo sprite) : NeoSpriteObject {
117
+ @id("69905c25-8c05-4d18-90fd-069f1526a38a")
118
+ @storage(allowed: .Writable)
119
+ public override Vector3 Size = new(1, 1, 0);
120
+ }
121
+
122
+ abstract class BodyLayerSprite(CharacterCosmeticConfig config, SpriteInfo sprite, int nearOrder, int farOrder) : AnimationSpriteObject(sprite: sprite) {
123
+ @id("e81dcef4-9ea8-4bbc-a83b-565b7cf3feff")
124
+ @storage(allowed: .Session)
125
+ public override Vector3 Size = new(2, 2, 0);
126
+ }
127
+ ```
128
+
129
+ An override below a `.Writable` member, directly or through other overrides,
130
+ may declare `.Writable`, `.Save`, or `.Session`, or omit `@storage`. It may not
131
+ declare `.Immutable` or `.Inherit`, and it may not be `readonly`. Static
132
+ members, `readonly` members, and project roots cannot declare `.Writable`.
133
+ Lowering reports each violation at the member's position.
134
+
135
+ On a class, `@storage(allowed: .Writable)` means every placement must resolve
136
+ to Save or Session. A subclass may narrow it to `.Save` or `.Session`. An
137
+ undeclared static member of such a class is Session.
138
+
106
139
  `@index(member: Field, kind: .Unique)` and
107
140
  `@column(member: Field, width: ..., visibility: ..., pin: ..., overflow: ...)`
108
141
  are repeatable List annotations. `member:` is a bare field name on the entry