@neocompose/cli 0.22.7 → 0.23.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,38 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.23.0] - 2026-08-05
4
+
5
+ ### Added
6
+
7
+ - Member defaults may be operator expressions — arithmetic over statics,
8
+ coalesce, indexing, type tests, string interpolation, and negation of a
9
+ non-literal. They are stored as computed initializers evaluated during
10
+ construction, exactly like call defaults, instead of failing with
11
+ "Expression binary is not a persisted default." Positions that store no
12
+ rows keep requiring literals and now say so precisely: dictionary keys,
13
+ Vector/Color components, and an `@id` written on part of an unparenthesized
14
+ computed entry (parenthesize the expression so the annotation names the
15
+ row).
16
+
17
+ ### Fixed
18
+
19
+ - Function members lower with the contract's value-less `required: false`
20
+ shape, so a push that adds a `.neo` function passes prospective validation,
21
+ and stored functions stop generating phantom `required` updates on every
22
+ push. Compiled bodies that read static members validate through the
23
+ previously missing static-member pointer guard.
24
+ - Push preparation reads the prospective document with pending enum-option
25
+ ids admitted and source-authored localized texts completed through the
26
+ server's own create envelope, so a push that creates an enum, adds an
27
+ option, or authors localized text no longer fails initializer
28
+ materialization or prospective animation validation. Committed-document
29
+ reads keep their exact strictness.
30
+ - Stored function bodies re-emit whole. A body opening with a nested block
31
+ (`switch`, `if`) was truncated to that block — leaking trailing statements
32
+ to class scope — and a body that was exactly one block silently lost its
33
+ guard on pull. An authored empty body re-emits as `{ }` instead of mutating
34
+ into a `native` signature.
35
+
3
36
  ## [0.22.7] - 2026-08-05
4
37
 
5
38
  ### Fixed
package/dist/neo.mjs CHANGED
@@ -35135,6 +35135,12 @@ function isNSPointerVariable(value) {
35135
35135
  const v = value;
35136
35136
  return v?.type === "variable" /* variable */ && typeof v?.variableId === "string";
35137
35137
  }
35138
+ function isNSPointerStaticMember(value) {
35139
+ const v = value;
35140
+ if (v?.type !== "staticMember" /* staticMember */) return false;
35141
+ if (typeof v.memberId !== "string") return false;
35142
+ return v.memberId.length > 0;
35143
+ }
35138
35144
  function isNSPointerValue(value) {
35139
35145
  const v = value;
35140
35146
  return v?.type === "value" /* value */ && isNSValue(v?.value);
@@ -35260,7 +35266,7 @@ function isNSPointerFunctionErrorCheck(value) {
35260
35266
  return isNSFunctionErrorCheckMode(v.mode);
35261
35267
  }
35262
35268
  function isNSPointer(value) {
35263
- return isNSPointerReference(value) || isNSPointerVariable(value) || isNSPointerValue(value) || isNSPointerOperation(value) || isNSPointerFunction(value) || isNSPointerKeyOf(value) || isNSPointerListLiteral(value) || isNSPointerDictLiteral(value) || isNSPointerForceUnwrap(value) || isNSPointerIsCheck(value) || isNSPointerCallGetter(value) || isNSPointerCoalesce(value) || isNSPointerToBool(value) || isNSPointerStringify(value) || isNSPointerCallFunction(value) || isNSPointerCallDelegate(value) || isNSPointerFunctionErrorCheck(value);
35269
+ return isNSPointerReference(value) || isNSPointerVariable(value) || isNSPointerStaticMember(value) || isNSPointerValue(value) || isNSPointerOperation(value) || isNSPointerFunction(value) || isNSPointerKeyOf(value) || isNSPointerListLiteral(value) || isNSPointerDictLiteral(value) || isNSPointerForceUnwrap(value) || isNSPointerIsCheck(value) || isNSPointerCallGetter(value) || isNSPointerCoalesce(value) || isNSPointerToBool(value) || isNSPointerStringify(value) || isNSPointerCallFunction(value) || isNSPointerCallDelegate(value) || isNSPointerFunctionErrorCheck(value);
35264
35270
  }
35265
35271
  function isNSPointers(value) {
35266
35272
  return Array.isArray(value) && value.every(isNSPointer);
@@ -40714,20 +40720,25 @@ function isValidEnumOptionId(id2) {
40714
40720
  if (typeof id2 !== "string" || id2.length === 0) return false;
40715
40721
  return OPTION_ID_REGEX.test(withoutSystemRecordIdPrefix(id2));
40716
40722
  }
40723
+ function isProspectiveEnumOptionId(id2) {
40724
+ if (typeof id2 !== "string" || id2.length === 0) return false;
40725
+ if (isPendingId(id2)) return true;
40726
+ return isValidEnumOptionId(id2);
40727
+ }
40717
40728
  function isValidEnumOptionName(name) {
40718
40729
  return isValidSchemaAuthoredIdentifier(name);
40719
40730
  }
40720
- function isEnumOption(value) {
40731
+ function isEnumOptionWithIds(value, isOptionId) {
40721
40732
  const v = value;
40722
- return !!v && isValidDocsText(v.docsText) && typeof v.text === "string" && typeof v.id === "string" && isValidEnumOptionId(v.id) && typeof v.name === "string" && isValidEnumOptionName(v.name);
40733
+ return !!v && isValidDocsText(v.docsText) && typeof v.text === "string" && typeof v.id === "string" && isOptionId(v.id) && typeof v.name === "string" && isValidEnumOptionName(v.name);
40723
40734
  }
40724
- function isEnumOptionsRecord(value) {
40735
+ function isEnumOptionsRecordWithIds(value, isOptionId) {
40725
40736
  if (value === null || value === void 0) return false;
40726
40737
  if (typeof value !== "object") return false;
40727
40738
  if (Array.isArray(value)) return false;
40728
40739
  for (const [k, v] of Object.entries(value)) {
40729
- if (!isValidEnumOptionId(k)) return false;
40730
- if (!isEnumOption(v)) return false;
40740
+ if (!isOptionId(k)) return false;
40741
+ if (!isEnumOptionWithIds(v, isOptionId)) return false;
40731
40742
  if (v.id !== k) return false;
40732
40743
  }
40733
40744
  return true;
@@ -40737,12 +40748,12 @@ function isOptionalOptionKeyOrder(value) {
40737
40748
  if (!Array.isArray(value)) return false;
40738
40749
  return value.every((key) => typeof key === "string");
40739
40750
  }
40740
- function isEnumBase(value) {
40751
+ function isEnumBaseWithIds(value, isOptionId) {
40741
40752
  const v = value;
40742
40753
  if (!v) return false;
40743
40754
  if (typeof v.name !== "string") return false;
40744
40755
  if (!isValidDocsText(v.docsText)) return false;
40745
- if (!isEnumOptionsRecord(v.options)) return false;
40756
+ if (!isEnumOptionsRecordWithIds(v.options, isOptionId)) return false;
40746
40757
  if (!isOptionalOptionKeyOrder(v.optionKeyOrder)) return false;
40747
40758
  if (v.system !== void 0 && v.system !== null) {
40748
40759
  return isSystemMetadata(v.system);
@@ -40767,17 +40778,24 @@ function getEnumOptionKeyOrder(enumDef) {
40767
40778
  }
40768
40779
  return result;
40769
40780
  }
40770
- function isEnumProps(value) {
40781
+ function isEnumPropsWithIds(value, isOptionId) {
40771
40782
  const v = value;
40772
- return isEnumBase(value) && isWithId(value) && typeof v?.projectId === "string" && isEpochMillis(v?.createdAt) && isEpochMillis(v?.updatedAt);
40783
+ return isEnumBaseWithIds(value, isOptionId) && isWithId(value) && typeof v?.projectId === "string" && isEpochMillis(v?.createdAt) && isEpochMillis(v?.updatedAt);
40784
+ }
40785
+ function isEnumProps(value) {
40786
+ return isEnumPropsWithIds(value, isValidEnumOptionId);
40773
40787
  }
40774
40788
  function isEnum(value) {
40775
40789
  return isEnumProps(value);
40776
40790
  }
40791
+ function isProspectiveEnum(value) {
40792
+ return isEnumPropsWithIds(value, isProspectiveEnumOptionId);
40793
+ }
40777
40794
  var OPTION_ID_REGEX;
40778
40795
  var init_enum_types = __esm({
40779
40796
  "../src/models/enum/enum-types.ts"() {
40780
40797
  "use strict";
40798
+ init_src();
40781
40799
  init_core();
40782
40800
  init_schema_identifiers();
40783
40801
  init_system_record_id();
@@ -43144,12 +43162,10 @@ ${prefix}${renderType(context, member.returnType)} ${member.name}${body}`;
43144
43162
  (argument2) => `${renderType(context, argument2.type)} ${argument2.name}`
43145
43163
  ).join(", ")})`;
43146
43164
  const tracked = member.kind === "scriptFunction" ? member.script?.sourceText ?? uiFunctionSource(context, member) : null;
43147
- const body = tracked ? extractBody(tracked) ?? `{
43148
- ${indentNeoSourceNonEmptyLines(tracked.trim(), 2)}
43149
- }` : null;
43165
+ const body = tracked === null ? null : emitFunctionBody(tracked);
43150
43166
  const abstractContract = member.modifier === "abstract" || member.modifier === "abstractOverride";
43151
43167
  return `${annotations.join("\n")}
43152
- ${body ? `${signature} ${body}` : abstractContract ? `${signature};` : `native ${signature};`}`;
43168
+ ${body !== null ? `${signature} ${body}` : abstractContract ? `${signature};` : `native ${signature};`}`;
43153
43169
  }
43154
43170
  const type = renderMemberType(context, member, enclosingClassId);
43155
43171
  const initializer = initDefaultSource(member) ?? (member.isStatic ? context.staticInitializers.get(member.id) : void 0) ?? context.defaultInitializers.get(member.id) ?? renderDefault(context, member);
@@ -43951,10 +43967,12 @@ function systemAnnotation2(system) {
43951
43967
  ];
43952
43968
  return `@system(${args.join(", ")})`;
43953
43969
  }
43954
- function extractBody(source) {
43955
- const start = source.indexOf("{");
43956
- const end = source.lastIndexOf("}");
43957
- return start >= 0 && end > start ? source.slice(start, end + 1).trim() : null;
43970
+ function emitFunctionBody(sourceText) {
43971
+ const code = sourceText.trim();
43972
+ if (code.length === 0) return "{\n}";
43973
+ return `{
43974
+ ${indentNeoSourceNonEmptyLines(code, 2)}
43975
+ }`;
43958
43976
  }
43959
43977
  function id(value) {
43960
43978
  return `@id(${quote(value)})
@@ -48226,6 +48244,7 @@ function initializerRequiresEvaluation(index, expression, targetClassName, runti
48226
48244
  return true;
48227
48245
  }
48228
48246
  if (expression.kind === "call") return !isLiteralCall(expression);
48247
+ if (isEvaluatedOnlyExpression(expression)) return true;
48229
48248
  if (expression.kind !== "new") return false;
48230
48249
  const className = expression.className ?? targetClassName;
48231
48250
  if (!declaresConstructors(index, className)) return false;
@@ -48301,6 +48320,24 @@ function expressionReadsRuntimeIdentifier(expression, runtimeIdentifiers) {
48301
48320
  return false;
48302
48321
  }
48303
48322
  }
48323
+ function isEvaluatedOnlyExpression(expression) {
48324
+ switch (expression.kind) {
48325
+ case "annotated":
48326
+ return isEvaluatedOnlyExpression(expression.expression);
48327
+ case "binary":
48328
+ case "coalesce":
48329
+ case "index":
48330
+ case "is":
48331
+ case "force":
48332
+ case "litInterp":
48333
+ return true;
48334
+ case "unary":
48335
+ if (expression.op !== "-") return true;
48336
+ return isEvaluatedOnlyExpression(expression.operand);
48337
+ default:
48338
+ return false;
48339
+ }
48340
+ }
48304
48341
  function isLiteralCall(expression) {
48305
48342
  if (expression.callee.kind === "ident" && expression.callee.name === "Reference") {
48306
48343
  return true;
@@ -49232,10 +49269,13 @@ function memberCommon(context, ownerClass, declaration, id2, owner, base) {
49232
49269
  accessModifier: owner.kind === "classMember" ? effectiveSourceAccessModifier(declaration.modifiers, "class") : "public",
49233
49270
  modifier: memberModifier3(declaration.modifiers, base?.modifier),
49234
49271
  locked: hasAnnotation2(declaration.annotations, "locked"),
49235
- // Every declaration kind carries a type, and its nullability is the whole
49236
- // of what `required` means. Properties and getters used to fall back to
49237
- // the pulled value, which left a fresh workspace calling them optional.
49238
- required: !declaration.type.nullable,
49272
+ // Every value-bearing declaration carries a type, and its nullability is
49273
+ // the whole of what `required` means. Properties and getters used to fall
49274
+ // back to the pulled value, which left a fresh workspace calling them
49275
+ // optional. A callable owns no value, so `required` is fixed `false`
49276
+ // (nsfunction-member.md §1.2, function-member.md) and the declared
49277
+ // nullability travels in `returnTypeInfo.required` instead.
49278
+ required: declaration.kind === "function" ? false : !declaration.type.nullable,
49239
49279
  defaultValue: null,
49240
49280
  overrideOf: inheritedId,
49241
49281
  system: systemMetadata(declaration.annotations),
@@ -49955,15 +49995,7 @@ function lowerDefault(context, initializer, type, ownerClass, base, memberId, st
49955
49995
  return base?.defaultValue && "serverValueId" in base.defaultValue ? base.defaultValue : null;
49956
49996
  }
49957
49997
  const expression = parseExpression(initializer);
49958
- if (!isStoredDelegateLiteral(type, expression) && initializerRequiresEvaluation(
49959
- context.declaredConstructors,
49960
- expression,
49961
- type.name,
49962
- requiredConstructorParameterNames(
49963
- context.declaredConstructors,
49964
- ownerClass.name
49965
- )
49966
- )) {
49998
+ if (positionRequiresEvaluation(context, expression, type, ownerClass)) {
49967
49999
  return { init: { code: normalizeInitializerSource(initializer) } };
49968
50000
  }
49969
50001
  if (context.rowBackedDefaultMemberIds.has(memberId) && base?.defaultValue && !("serverValueId" in base.defaultValue)) {
@@ -50029,15 +50061,7 @@ function lowerExpressionValue(context, expression, declaredExpected, ownerClass,
50029
50061
  declaredExpected,
50030
50062
  genericEnvironment
50031
50063
  );
50032
- if (!isStoredDelegateLiteral(expected, expression) && initializerRequiresEvaluation(
50033
- context.declaredConstructors,
50034
- expression,
50035
- expected.name,
50036
- requiredConstructorParameterNames(
50037
- context.declaredConstructors,
50038
- ownerClass.name
50039
- )
50040
- )) {
50064
+ if (positionRequiresEvaluation(context, expression, expected, ownerClass)) {
50041
50065
  return null;
50042
50066
  }
50043
50067
  if (expression.kind === "annotated") {
@@ -50116,10 +50140,20 @@ function lowerExpressionValue(context, expression, declaredExpected, ownerClass,
50116
50140
  const valueType = expected.arguments[1] ?? expected;
50117
50141
  const value = {};
50118
50142
  for (const entry of expression.entries) {
50143
+ if (positionRequiresEvaluation(
50144
+ context,
50145
+ entry.key,
50146
+ DICTIONARY_KEY_TYPE,
50147
+ ownerClass
50148
+ )) {
50149
+ throw new Error(
50150
+ `Dictionary key at ${path} is a computed expression. Dictionary keys must be string literals.`
50151
+ );
50152
+ }
50119
50153
  const key = lowerExpressionValue(
50120
50154
  context,
50121
50155
  entry.key,
50122
- { name: "string", nullable: false, arguments: [] },
50156
+ DICTIONARY_KEY_TYPE,
50123
50157
  ownerClass,
50124
50158
  path,
50125
50159
  genericEnvironment,
@@ -50293,6 +50327,18 @@ function lowerExpressionValue(context, expression, declaredExpected, ownerClass,
50293
50327
  );
50294
50328
  }
50295
50329
  }
50330
+ function positionRequiresEvaluation(context, expression, expected, ownerClass) {
50331
+ if (isStoredDelegateLiteral(expected, expression)) return false;
50332
+ return initializerRequiresEvaluation(
50333
+ context.declaredConstructors,
50334
+ expression,
50335
+ expected.name,
50336
+ requiredConstructorParameterNames(
50337
+ context.declaredConstructors,
50338
+ ownerClass.name
50339
+ )
50340
+ );
50341
+ }
50296
50342
  function isStoredDelegateLiteral(type, expression) {
50297
50343
  if (type.name !== "NeoDelegate") return false;
50298
50344
  let current = expression;
@@ -50374,10 +50420,20 @@ function lowerStructuredLeafDefault(context, kind, expression, expectedType, own
50374
50420
  });
50375
50421
  }
50376
50422
  function requiredComponentNumber(context, expression, ownerClass, label, path) {
50423
+ if (positionRequiresEvaluation(
50424
+ context,
50425
+ expression,
50426
+ STRUCTURED_LEAF_COMPONENT_TYPE,
50427
+ ownerClass
50428
+ )) {
50429
+ throw new Error(
50430
+ `${label} has a computed component. Structured leaf components must be numeric literals.`
50431
+ );
50432
+ }
50377
50433
  const value = lowerExpressionValue(
50378
50434
  context,
50379
50435
  expression,
50380
- { name: "float", nullable: false, arguments: [] },
50436
+ STRUCTURED_LEAF_COMPONENT_TYPE,
50381
50437
  ownerClass,
50382
50438
  path
50383
50439
  );
@@ -50965,7 +51021,7 @@ function* zip(left, right) {
50965
51021
  yield [left[index], right[index]];
50966
51022
  }
50967
51023
  }
50968
- var UNSET_LIST_COLUMN_WIDTH, EMPTY_GENERIC_TYPE_ENVIRONMENT, primitiveMemberKinds;
51024
+ var UNSET_LIST_COLUMN_WIDTH, EMPTY_GENERIC_TYPE_ENVIRONMENT, DICTIONARY_KEY_TYPE, STRUCTURED_LEAF_COMPONENT_TYPE, primitiveMemberKinds;
50969
51025
  var init_lower_members = __esm({
50970
51026
  "src/project-source/lower-members.ts"() {
50971
51027
  "use strict";
@@ -50983,6 +51039,16 @@ var init_lower_members = __esm({
50983
51039
  init_key_reference_spelling();
50984
51040
  UNSET_LIST_COLUMN_WIDTH = -1;
50985
51041
  EMPTY_GENERIC_TYPE_ENVIRONMENT = /* @__PURE__ */ new Map();
51042
+ DICTIONARY_KEY_TYPE = {
51043
+ name: "string",
51044
+ nullable: false,
51045
+ arguments: []
51046
+ };
51047
+ STRUCTURED_LEAF_COMPONENT_TYPE = {
51048
+ name: "float",
51049
+ nullable: false,
51050
+ arguments: []
51051
+ };
50986
51052
  primitiveMemberKinds = /* @__PURE__ */ new Set([
50987
51053
  "null",
50988
51054
  "bool",
@@ -55239,9 +55305,14 @@ function buildEvaluatorBaseIndexes(members, values, valueById = new Map(
55239
55305
  parentLinksByChildId: /* @__PURE__ */ new Map(),
55240
55306
  indexedRowIds: /* @__PURE__ */ new Set(),
55241
55307
  indexedOverlayValues: /* @__PURE__ */ new Map(),
55308
+ indexedOverlayRowCount: 0,
55242
55309
  shadowedBaseRowIds: /* @__PURE__ */ new Set(),
55243
55310
  listIdentityByArray: /* @__PURE__ */ new WeakMap(),
55244
- declaredListByArray: /* @__PURE__ */ new WeakMap()
55311
+ declaredListByArray: /* @__PURE__ */ new WeakMap(),
55312
+ rowsBySourceValueId: /* @__PURE__ */ new Map(),
55313
+ ownershipRootIdsByRowId: /* @__PURE__ */ new Map(),
55314
+ ownedValueAttachmentsByValueId: /* @__PURE__ */ new Map(),
55315
+ memberByRowId: /* @__PURE__ */ new Map()
55245
55316
  };
55246
55317
  const membersByValueId = /* @__PURE__ */ new Map();
55247
55318
  for (const member of members) {
@@ -55260,7 +55331,8 @@ function buildEvaluatorBaseIndexes(members, values, valueById = new Map(
55260
55331
  memberByValueId: indexes.memberByValueId,
55261
55332
  membersByValueId,
55262
55333
  parentLinksByChildId: indexes.parentLinksByChildId,
55263
- indexedRowIds: indexes.indexedRowIds
55334
+ indexedRowIds: indexes.indexedRowIds,
55335
+ rowsBySourceValueId: indexes.rowsBySourceValueId
55264
55336
  };
55265
55337
  }
55266
55338
  function evaluatorIndexes(ctx) {
@@ -55280,10 +55352,16 @@ function evaluatorIndexes(ctx) {
55280
55352
  indexedRowIds: /* @__PURE__ */ new Set(),
55281
55353
  baseIndexedRowIds: base?.indexedRowIds,
55282
55354
  indexedOverlayValues: /* @__PURE__ */ new Map(),
55355
+ indexedOverlayRowCount: 0,
55283
55356
  shadowedBaseRowIds: /* @__PURE__ */ new Set(),
55284
55357
  overlayRevision,
55285
55358
  listIdentityByArray: liveListIndexes?.identity ?? /* @__PURE__ */ new WeakMap(),
55286
- declaredListByArray: liveListIndexes?.declared ?? /* @__PURE__ */ new WeakMap()
55359
+ declaredListByArray: liveListIndexes?.declared ?? /* @__PURE__ */ new WeakMap(),
55360
+ rowsBySourceValueId: /* @__PURE__ */ new Map(),
55361
+ baseRowsBySourceValueId: base?.rowsBySourceValueId,
55362
+ ownershipRootIdsByRowId: /* @__PURE__ */ new Map(),
55363
+ ownedValueAttachmentsByValueId: /* @__PURE__ */ new Map(),
55364
+ memberByRowId: /* @__PURE__ */ new Map()
55287
55365
  };
55288
55366
  if (base === void 0) {
55289
55367
  for (const member of ctx.vm.members) {
@@ -55308,19 +55386,46 @@ function evaluatorIndexes(ctx) {
55308
55386
  }
55309
55387
  function syncLazyOverlayReferences(indexes, overlay) {
55310
55388
  if (!(overlay instanceof LazyValueOverlay)) return;
55311
- for (const row of overlay.values()) {
55312
- if (indexes.indexedOverlayValues.get(row.id) === row.value) continue;
55389
+ const materializedRows = overlay.materializedRowsSince(
55390
+ indexes.indexedOverlayRowCount
55391
+ );
55392
+ for (const row of materializedRows) {
55313
55393
  indexes.indexedOverlayValues.set(row.id, row.value);
55314
55394
  if (typeof row.value === "object" && row.value !== null) {
55315
55395
  indexes.rowByValueReference.set(row.value, row);
55316
55396
  }
55317
55397
  }
55398
+ indexes.indexedOverlayRowCount += materializedRows.length;
55318
55399
  }
55319
55400
  function indexEvaluatorRow(indexes, row, allowBaseShadow = false) {
55401
+ indexes.ownedValueAttachmentsByValueId.delete(row.id);
55402
+ if (Array.isArray(row.value)) {
55403
+ for (const childId of row.value) {
55404
+ if (typeof childId === "string") {
55405
+ indexes.ownedValueAttachmentsByValueId.delete(childId);
55406
+ }
55407
+ }
55408
+ } else if (typeof row.value === "object" && row.value !== null) {
55409
+ for (const childId of Object.values(row.value)) {
55410
+ if (typeof childId === "string") {
55411
+ indexes.ownedValueAttachmentsByValueId.delete(childId);
55412
+ }
55413
+ }
55414
+ }
55415
+ indexes.ownershipRootIdsByRowId.clear();
55416
+ indexes.memberByRowId.clear();
55320
55417
  if (indexes.indexedRowIds.has(row.id) || !allowBaseShadow && indexes.baseIndexedRowIds?.has(row.id) === true) {
55321
55418
  return;
55322
55419
  }
55323
55420
  indexes.indexedRowIds.add(row.id);
55421
+ if (typeof row.sourceValueId === "string") {
55422
+ const rows = indexes.rowsBySourceValueId.get(row.sourceValueId) ?? [];
55423
+ rows.push(row);
55424
+ indexes.rowsBySourceValueId.set(row.sourceValueId, rows);
55425
+ }
55426
+ if (typeof row.containerId === "string") {
55427
+ addEvaluatorParentLink(indexes, row.id, row.containerId, "");
55428
+ }
55324
55429
  if (typeof row.value === "object" && row.value !== null) {
55325
55430
  indexes.rowByValueReference.set(row.value, row);
55326
55431
  }
@@ -55354,6 +55459,57 @@ function evaluatorParentLinks(indexes, childId) {
55354
55459
  if (local.length === 0) return base;
55355
55460
  return [...base, ...local];
55356
55461
  }
55462
+ function evaluatorRowsForSourceValueId(indexes, sourceValueId) {
55463
+ const base = (indexes.baseRowsBySourceValueId?.get(sourceValueId) ?? []).filter((row) => !indexes.shadowedBaseRowIds.has(row.id));
55464
+ const local = indexes.rowsBySourceValueId.get(sourceValueId) ?? [];
55465
+ if (base.length === 0) return local;
55466
+ if (local.length === 0) return base;
55467
+ return [...base, ...local];
55468
+ }
55469
+ function evaluatorOwnershipRootIds(rowId, indexes) {
55470
+ const cached = indexes.ownershipRootIdsByRowId.get(rowId);
55471
+ if (cached !== void 0) return cached;
55472
+ const roots = /* @__PURE__ */ new Set();
55473
+ const visiting = /* @__PURE__ */ new Set();
55474
+ const visit = (currentId) => {
55475
+ if (visiting.has(currentId)) return;
55476
+ visiting.add(currentId);
55477
+ const parents = evaluatorParentLinks(indexes, currentId);
55478
+ if (parents.length === 0) roots.add(currentId);
55479
+ else for (const parent of parents) visit(parent.parentId);
55480
+ visiting.delete(currentId);
55481
+ };
55482
+ visit(rowId);
55483
+ if (roots.size === 0) roots.add(rowId);
55484
+ indexes.ownershipRootIdsByRowId.set(rowId, roots);
55485
+ return roots;
55486
+ }
55487
+ function resolveRuntimeReferenceRow(sourceValueId, ctx) {
55488
+ const direct = evalValueById(
55489
+ ctx.vm,
55490
+ sourceValueId,
55491
+ ctx.__runtimeSessionValues,
55492
+ ctx.__valueOverlay
55493
+ );
55494
+ const receiver = trackedRowForValueReference(ctx.thisValue, ctx);
55495
+ if (receiver === null) return direct;
55496
+ const indexes = evaluatorIndexes(ctx);
55497
+ const receiverRoots = evaluatorOwnershipRootIds(receiver.id, indexes);
55498
+ const matches = evaluatorRowsForSourceValueId(indexes, sourceValueId).filter(
55499
+ (candidate) => {
55500
+ const candidateRoots = evaluatorOwnershipRootIds(candidate.id, indexes);
55501
+ for (const rootId of candidateRoots) {
55502
+ if (receiverRoots.has(rootId)) return true;
55503
+ }
55504
+ return false;
55505
+ }
55506
+ );
55507
+ if (matches.length === 0) return direct;
55508
+ if (matches.length === 1) return matches[0] ?? direct;
55509
+ throw new NSGetterRuntimeError(
55510
+ `Value reference '${sourceValueId}' is ambiguous within the constructed object graph.`
55511
+ );
55512
+ }
55357
55513
  function invalidateEvaluatorIndexes(ctx) {
55358
55514
  if (ctx.__valueOverlay instanceof LazyValueOverlay) {
55359
55515
  ctx.__valueOverlay.markMutated();
@@ -55564,6 +55720,8 @@ function withEvaluationRuntime(ctx, writes) {
55564
55720
  ownedValueAttachments: /* @__PURE__ */ new Map(),
55565
55721
  constructionStack: [],
55566
55722
  loopIterations: 0,
55723
+ budgetedConstructedRowIds: /* @__PURE__ */ new Set(),
55724
+ budgetedProducedEntriesByRowId: /* @__PURE__ */ new Map(),
55567
55725
  budget: createExecutionBudget(ctx.executionBudgetLimits)
55568
55726
  },
55569
55727
  __indexes: ctx.__executionState === void 0 || ctx.__valueOverlay === void 0 ? void 0 : ctx.__indexes
@@ -57211,12 +57369,7 @@ function evalPointer(pointer, scope, ctx) {
57211
57369
  return scope.get(pointer.variableId);
57212
57370
  }
57213
57371
  case "reference" /* reference */: {
57214
- const row = evalValueById(
57215
- ctx.vm,
57216
- pointer.valueId,
57217
- ctx.__runtimeSessionValues,
57218
- ctx.__valueOverlay
57219
- );
57372
+ const row = resolveRuntimeReferenceRow(pointer.valueId, ctx);
57220
57373
  if (!row) {
57221
57374
  throw new NSGetterRuntimeError(
57222
57375
  `Missing value reference: ${pointer.valueId}`
@@ -58432,7 +58585,16 @@ function resolveLocalizedRowValueForMember(row, member, ctx) {
58432
58585
  if (member.localizable === false) return value;
58433
58586
  return resolveLocalizedTextId(value, ctx);
58434
58587
  }
58435
- function memberForValueRow(row, ctx, visited = /* @__PURE__ */ new Set()) {
58588
+ function memberForValueRow(row, ctx) {
58589
+ const indexes = evaluatorIndexes(ctx);
58590
+ if (indexes.memberByRowId.has(row.id)) {
58591
+ return indexes.memberByRowId.get(row.id) ?? null;
58592
+ }
58593
+ const member = resolveMemberForValueRow(row, ctx, /* @__PURE__ */ new Set());
58594
+ indexes.memberByRowId.set(row.id, member);
58595
+ return member;
58596
+ }
58597
+ function resolveMemberForValueRow(row, ctx, visited) {
58436
58598
  if (visited.has(row.id)) return null;
58437
58599
  visited.add(row.id);
58438
58600
  const indexes = evaluatorIndexes(ctx);
@@ -58452,7 +58614,11 @@ function memberForValueRow(row, ctx, visited = /* @__PURE__ */ new Set()) {
58452
58614
  ctx.__valueOverlay
58453
58615
  );
58454
58616
  if (parent === null) continue;
58455
- const parentMember = memberForValueRow(parent, ctx, new Set(visited));
58617
+ const parentMember = resolveMemberForValueRow(
58618
+ parent,
58619
+ ctx,
58620
+ new Set(visited)
58621
+ );
58456
58622
  if (parentMember !== null && isMemberList(parentMember)) {
58457
58623
  return evalMemberById(ctx.vm, parentMember.entryMemberId);
58458
58624
  }
@@ -59482,23 +59648,29 @@ function publishConstructedRows(args) {
59482
59648
  "Class construction requires an effect-capable evaluator Session scope."
59483
59649
  );
59484
59650
  }
59651
+ let newlyConstructedRows = 0;
59652
+ let newlyProducedEntries = 0;
59653
+ for (const row of createdValues) {
59654
+ if (!state.budgetedConstructedRowIds.has(row.id)) {
59655
+ state.budgetedConstructedRowIds.add(row.id);
59656
+ newlyConstructedRows += 1;
59657
+ }
59658
+ const currentEntries = Array.isArray(row.value) ? row.value.length : typeof row.value === "object" && row.value !== null ? Object.keys(row.value).length : 0;
59659
+ const priorEntries = state.budgetedProducedEntriesByRowId.get(row.id) ?? 0;
59660
+ if (currentEntries <= priorEntries) continue;
59661
+ newlyProducedEntries += currentEntries - priorEntries;
59662
+ state.budgetedProducedEntriesByRowId.set(row.id, currentEntries);
59663
+ }
59485
59664
  consumeBudget(
59486
59665
  ctx,
59487
59666
  "constructedSessionRows",
59488
- createdValues.length,
59667
+ newlyConstructedRows,
59489
59668
  "constructed Session row"
59490
59669
  );
59491
- let producedEntries = 0;
59492
- for (const row of createdValues) {
59493
- if (Array.isArray(row.value)) producedEntries += row.value.length;
59494
- else if (typeof row.value === "object" && row.value !== null) {
59495
- producedEntries += Object.keys(row.value).length;
59496
- }
59497
- }
59498
59670
  consumeBudget(
59499
59671
  ctx,
59500
59672
  "producedCollectionEntries",
59501
- producedEntries,
59673
+ newlyProducedEntries,
59502
59674
  "produced collection entry"
59503
59675
  );
59504
59676
  const retained = retainedIds;
@@ -60186,15 +60358,16 @@ function evaluateInitializerInContext(init, member, ctx, createdValues, argument
60186
60358
  }
60187
60359
  if (rootRow === null) {
60188
60360
  const existingValueRow = trackedRowForValueReference(result.value, ctx);
60361
+ const existingRuntimeClassId = existingValueRow === null ? null : classIdForValueRow(existingValueRow, ctx) ?? null;
60189
60362
  const existingConstructorRootId = existingValueRow === null ? null : constructorGroupForRow(existingValueRow.id, ctx);
60190
60363
  if (existingValueRow !== null && existingConstructorRootId === existingValueRow.id) {
60191
60364
  return {
60192
60365
  value: result.value,
60193
- classId: existingValueRow.classId ?? null,
60366
+ classId: existingRuntimeClassId,
60194
60367
  existingValueRow
60195
60368
  };
60196
60369
  }
60197
- return { value: result.value, classId: null };
60370
+ return { value: result.value, classId: existingRuntimeClassId };
60198
60371
  }
60199
60372
  ctx.__runtimeSessionValues?.delete(rootRow.id);
60200
60373
  ctx.__executionState?.constructorGroups.delete(rootRow.id);
@@ -60537,6 +60710,9 @@ function assertOwnedValueAttachable(valueId, destination, destinationName2, ctx,
60537
60710
  }
60538
60711
  }
60539
60712
  function currentOwnedValueAttachments(valueId, ctx) {
60713
+ const indexes = evaluatorIndexes(ctx);
60714
+ const cached = indexes.ownedValueAttachmentsByValueId.get(valueId);
60715
+ if (cached !== void 0) return [...cached];
60540
60716
  const found = /* @__PURE__ */ new Map();
60541
60717
  const add = (attachment) => {
60542
60718
  found.set(attachment.identity, attachment);
@@ -60575,9 +60751,7 @@ function currentOwnedValueAttachments(valueId, ctx) {
60575
60751
  }
60576
60752
  }
60577
60753
  const parentIds = new Set(
60578
- evaluatorParentLinks(evaluatorIndexes(ctx), valueId).map(
60579
- (link) => link.parentId
60580
- )
60754
+ evaluatorParentLinks(indexes, valueId).map((link) => link.parentId)
60581
60755
  );
60582
60756
  for (const parentId of parentIds) {
60583
60757
  const parent = evalValueById(
@@ -60588,21 +60762,25 @@ function currentOwnedValueAttachments(valueId, ctx) {
60588
60762
  );
60589
60763
  if (parent === null) continue;
60590
60764
  if (parent.id === valueId) continue;
60591
- let member = activeStaticBindingRoot(parent.id, ctx)?.member ?? memberForValueRow(parent, ctx);
60592
- if (member !== null) {
60593
- try {
60594
- member = resolveMember2(member, ctx.vm.members);
60595
- if (parent.genericBindings !== void 0) {
60596
- member = substituteMember(
60597
- member,
60598
- envFromStamp(parent.genericBindings),
60599
- ctx.vm.members
60600
- );
60765
+ let member = null;
60766
+ let classId = parent.classId;
60767
+ if (classId === void 0) {
60768
+ member = activeStaticBindingRoot(parent.id, ctx)?.member ?? memberForValueRow(parent, ctx);
60769
+ if (member !== null) {
60770
+ try {
60771
+ member = resolveMember2(member, ctx.vm.members);
60772
+ if (parent.genericBindings !== void 0) {
60773
+ member = substituteMember(
60774
+ member,
60775
+ envFromStamp(parent.genericBindings),
60776
+ ctx.vm.members
60777
+ );
60778
+ }
60779
+ } catch {
60601
60780
  }
60602
- } catch {
60603
60781
  }
60782
+ classId = member !== null && isMemberClassBase(member) ? member.classId : void 0;
60604
60783
  }
60605
- const classId = parent.classId ?? (member !== null && isMemberClassBase(member) ? member.classId : void 0);
60606
60784
  if (classId !== void 0 && typeof parent.value === "object" && parent.value !== null && !Array.isArray(parent.value)) {
60607
60785
  for (const [key, childId] of Object.entries(parent.value)) {
60608
60786
  if (childId !== valueId) continue;
@@ -60633,7 +60811,9 @@ function currentOwnedValueAttachments(valueId, ctx) {
60633
60811
  }
60634
60812
  }
60635
60813
  }
60636
- return [...found.values()];
60814
+ const attachments = [...found.values()];
60815
+ indexes.ownedValueAttachmentsByValueId.set(valueId, attachments);
60816
+ return attachments;
60637
60817
  }
60638
60818
  function constructorArgumentStorage(valueId, ctx, visiting = /* @__PURE__ */ new Set()) {
60639
60819
  if (ctx.__runtimeSessionValues?.has(valueId)) return "session" /* Session */;
@@ -61143,13 +61323,15 @@ function cloneClassValueGraph(receiver, expectedClassId, ctx) {
61143
61323
  `Class.Clone source value '${sourceId3}' could not be read.`
61144
61324
  );
61145
61325
  }
61146
- const runtimeClassId = classIdForValueRow(source, ctx);
61147
- if (runtimeClassId === void 0) {
61326
+ const resolvedRuntimeClassId = classIdForValueRow(source, ctx);
61327
+ const runtimeClassId = resolvedRuntimeClassId ?? expectedClassId;
61328
+ if (runtimeClassId !== expectedClassId && !resolveInheritanceChain(runtimeClassId, ctx.vm.classes).some(
61329
+ (candidate) => candidate.id === expectedClassId
61330
+ )) {
61148
61331
  throw new NSGetterRuntimeError(
61149
- `Class.Clone source value '${sourceId3}' has no Class runtime type.`
61332
+ `Class.Clone source value '${sourceId3}' has runtime Class '${runtimeClassId}', which is not assignable to '${expectedClassId}'.`
61150
61333
  );
61151
61334
  }
61152
- void expectedClassId;
61153
61335
  const destination = ctx.__runtimeSessionValues ?? (() => {
61154
61336
  throw new NSGetterRuntimeError(
61155
61337
  "Class.Clone requires an evaluator Session value registry."
@@ -61272,7 +61454,10 @@ function cloneClassValueGraph(receiver, expectedClassId, ctx) {
61272
61454
  return clone;
61273
61455
  };
61274
61456
  const rootMember = memberForValueRow(source, ctx);
61275
- return cloneRow(source, rootMember).value;
61457
+ const typedSource = source.classId === void 0 ? { ...source, classId: runtimeClassId } : source;
61458
+ const clonedRoot = cloneRow(typedSource, rootMember);
61459
+ clonedRoot.classId ??= runtimeClassId;
61460
+ return clonedRoot.value;
61276
61461
  }
61277
61462
  function ownedObjectChildMember(row, sourceMember, key, ctx) {
61278
61463
  if (sourceMember !== null && isMemberDictionary(sourceMember)) {
@@ -61486,7 +61671,7 @@ var init_evaluateNSGetter = __esm({
61486
61671
  workUnits: 1e5,
61487
61672
  collectionVisits: 1e5,
61488
61673
  producedCollectionEntries: 1e4,
61489
- constructedSessionRows: 1e3,
61674
+ constructedSessionRows: 4096,
61490
61675
  producedStringCharacters: 1024 * 1024
61491
61676
  });
61492
61677
  liveListIndexesByProject = /* @__PURE__ */ new WeakMap();
@@ -61499,6 +61684,7 @@ var init_evaluateNSGetter = __esm({
61499
61684
  }
61500
61685
  vm;
61501
61686
  revision = 0;
61687
+ materializedRows = [];
61502
61688
  sourceIndexes;
61503
61689
  sourceValueByClone = /* @__PURE__ */ new WeakMap();
61504
61690
  get(id2) {
@@ -61514,8 +61700,12 @@ var init_evaluateNSGetter = __esm({
61514
61700
  this.sourceValueByClone.set(value, source.value);
61515
61701
  }
61516
61702
  super.set(id2, clone);
61703
+ this.materializedRows.push(clone);
61517
61704
  return clone;
61518
61705
  }
61706
+ materializedRowsSince(index) {
61707
+ return this.materializedRows.slice(index);
61708
+ }
61519
61709
  cloneForValueReference(value) {
61520
61710
  const source = this.ensureSourceIndexes().byReference.get(value);
61521
61711
  return source === void 0 ? void 0 : this.get(source.id)?.value;
@@ -63210,7 +63400,11 @@ function readProjectDocument(value, options = {}) {
63210
63400
  isInternalRecordRelation
63211
63401
  ),
63212
63402
  values: readArrayField(value, "values", isMemberValue),
63213
- enums: readArrayField(value, "enums", isEnum),
63403
+ enums: readArrayField(
63404
+ value,
63405
+ "enums",
63406
+ options.identities === "prospective" ? isProspectiveEnum : isEnum
63407
+ ),
63214
63408
  interfaces: readArrayField(value, "interfaces", isNeoInterface),
63215
63409
  dialogues: readArrayField(value, "dialogues", isDialogue),
63216
63410
  dialogueRecords: readOptionalArrayField(
@@ -81713,13 +81907,55 @@ function compilePulledMemberInitializerBodyV4(document, memberId, compilationPro
81713
81907
  });
81714
81908
  }
81715
81909
  function readPulledProjectDocumentV4(records2) {
81716
- return readProjectDocument(
81717
- // Replay compilation mutates authored `init` envelopes with transient IR.
81718
- // Keep that evaluator-local: the input records are also the source commit
81719
- // payload, where compiled bodies are deliberately absent.
81720
- structuredClone(pulledProjectDocumentRaw(replayWorkspace(records2))),
81721
- { constructors: "authored-or-compiled" }
81910
+ const raw = structuredClone(
81911
+ pulledProjectDocumentRaw(replayWorkspace(records2))
81912
+ );
81913
+ return readProjectDocument(completeProspectiveLocalizedTexts(raw), {
81914
+ constructors: "authored-or-compiled",
81915
+ identities: "prospective"
81916
+ });
81917
+ }
81918
+ function completeProspectiveLocalizedTexts(raw) {
81919
+ const localizedTexts = raw.localizedTexts;
81920
+ if (!Array.isArray(localizedTexts)) return raw;
81921
+ const incomplete = localizedTexts.find((text) => !isLocalizedText(text));
81922
+ if (incomplete === void 0) return raw;
81923
+ const config = localizedTextEnvelopeConfig(
81924
+ raw.localizationConfig,
81925
+ prospectiveLocalizedTextId(incomplete)
81722
81926
  );
81927
+ return {
81928
+ ...raw,
81929
+ localizedTexts: localizedTexts.map(
81930
+ (text) => isLocalizedText(text) ? text : completeLocalizedTextCreateEnvelope(text, config)
81931
+ )
81932
+ };
81933
+ }
81934
+ function localizedTextEnvelopeConfig(value, textId) {
81935
+ if (!isObjectRecord2(value)) {
81936
+ throw new Error(
81937
+ `Localized text "${textId}" needs the server create envelope, but the project carries no localization config record to mint it from.`
81938
+ );
81939
+ }
81940
+ if (typeof value.mainLocale !== "string") {
81941
+ throw new Error(
81942
+ `Localized text "${textId}" needs the server create envelope, but the project localization config declares no main locale.`
81943
+ );
81944
+ }
81945
+ if (typeof value.mainLocaleDefaultStatusId !== "string") {
81946
+ throw new Error(
81947
+ `Localized text "${textId}" needs the server create envelope, but the project localization config declares no main-locale default status.`
81948
+ );
81949
+ }
81950
+ return {
81951
+ mainLocale: value.mainLocale,
81952
+ mainLocaleDefaultStatusId: value.mainLocaleDefaultStatusId
81953
+ };
81954
+ }
81955
+ function prospectiveLocalizedTextId(value) {
81956
+ if (!isObjectRecord2(value)) return "<non-object>";
81957
+ if (typeof value.id !== "string") return "<unidentified>";
81958
+ return value.id;
81723
81959
  }
81724
81960
  function replayWorkspace(records2) {
81725
81961
  const stateRecords = {};
@@ -81758,6 +81994,7 @@ var init_initializer_replay = __esm({
81758
81994
  init_value_row_owner_members();
81759
81995
  init_inheritance();
81760
81996
  init_project_document_read();
81997
+ init_localization2();
81761
81998
  init_server_preparation_preflight();
81762
81999
  init_projection();
81763
82000
  init_constructors2();
@@ -82842,6 +83079,13 @@ function constructorArgumentValueSlices(authoredSlice) {
82842
83079
  );
82843
83080
  });
82844
83081
  }
83082
+ function unattachedAuthoredRowId(authoredSlice) {
83083
+ if (authoredSlice === void 0) return null;
83084
+ const match = LEADING_AUTHORED_ROW_ID.exec(
83085
+ normalizeInitializerSource(authoredSlice)
83086
+ );
83087
+ return match?.[1] ?? null;
83088
+ }
82845
83089
  function initializerExpressionSlice(authoredSlice) {
82846
83090
  if (authoredSlice === void 0) return void 0;
82847
83091
  let text = normalizeInitializerSource(authoredSlice);
@@ -83212,6 +83456,12 @@ function lowerSeedRow(context, member, sourceExpression, source, valueId, path,
83212
83456
  memberClassName(context, resolvedMember),
83213
83457
  bindingRuntimeIdentifiers(context, source)
83214
83458
  )) {
83459
+ const unattachedId = annotated.id === null ? unattachedAuthoredRowId(authoredSlice) : null;
83460
+ if (unattachedId !== null) {
83461
+ throw new Error(
83462
+ `Value ${path} in ${source.label} writes @id(${JSON.stringify(unattachedId)}) on part of a computed expression rather than on the row. Parenthesize the expression so the annotation names the whole row.`
83463
+ );
83464
+ }
83215
83465
  if (annotated.id === null && isPendingId(valueId) && !context.pendingValueIdentitySites.has(valueId)) {
83216
83466
  context.pendingValueIdentitySites.set(valueId, {
83217
83467
  id: valueId,
@@ -87233,7 +87483,7 @@ function memberDefaultBody(member) {
87233
87483
  if (!("value" in defaultValue)) return null;
87234
87484
  return defaultValue.value;
87235
87485
  }
87236
- var INFERRED_GENERIC_CLASS_PREFIX, MEMBER_KIND_DICTIONARY, MEMBER_KIND_LIST, MEMBER_KIND_CLASS, CANONICAL_CONSTRUCTOR_INLINE_WIDTH;
87486
+ var INFERRED_GENERIC_CLASS_PREFIX, MEMBER_KIND_DICTIONARY, MEMBER_KIND_LIST, MEMBER_KIND_CLASS, LEADING_AUTHORED_ROW_ID, CANONICAL_CONSTRUCTOR_INLINE_WIDTH;
87237
87487
  var init_value_sources = __esm({
87238
87488
  "src/project-source/value-sources.ts"() {
87239
87489
  "use strict";
@@ -87260,6 +87510,7 @@ var init_value_sources = __esm({
87260
87510
  MEMBER_KIND_DICTIONARY = 5;
87261
87511
  MEMBER_KIND_LIST = 6;
87262
87512
  MEMBER_KIND_CLASS = 7;
87513
+ LEADING_AUTHORED_ROW_ID = /^@id\(\s*"((?:[^"\\]|\\.)*)"\s*\)/;
87263
87514
  CANONICAL_CONSTRUCTOR_INLINE_WIDTH = 88;
87264
87515
  }
87265
87516
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@neocompose/cli",
3
- "version": "0.22.7",
3
+ "version": "0.23.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.22.7 -->
12
+ <!-- reviewed-through-cli: 0.23.0 -->
13
13
 
14
14
  # Neo Compose CLI
15
15
 
@@ -78,7 +78,7 @@ wrappers.
78
78
  The marker near the top of `SKILL.md` must exactly match the package version:
79
79
 
80
80
  ```html
81
- <!-- reviewed-through-cli: 0.22.7 -->
81
+ <!-- reviewed-through-cli: 0.23.0 -->
82
82
  ```
83
83
 
84
84
  The quoted version above is checked too, so this instruction cannot go stale
@@ -108,6 +108,14 @@ construction rather than stored as a closed literal. It runs for every newly
108
108
  constructed instance in push, web, and SDK contexts; it does not rewrite
109
109
  existing saved instances.
110
110
 
111
+ Operator expressions are valid member defaults and store as computed
112
+ initializers, the same way calls do — `public float Derived =
113
+ Flags.Start / Flags.Scale;` is fine. Positions that store no rows still
114
+ require literals: dictionary keys must be string literals and Vector/Color
115
+ components must be numeric literals. When a computed list or dictionary entry
116
+ carries an authored `@id`, parenthesize the expression — `@id("row")
117
+ (A.B / A.C)` — so the annotation names the row rather than its left operand.
118
+
111
119
  Declare overloadable constructors as members when each constructor body owns
112
120
  its parameter scope:
113
121