@neocompose/cli 0.9.3 → 0.10.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
@@ -7,6 +7,27 @@
7
7
  - Document the final relation-only world layer-link contract and keep the
8
8
  format-4 Hello World corpus free of the retired value-side binding field.
9
9
 
10
+ ## [0.10.0] - 2026-07-25
11
+
12
+ ### Added
13
+
14
+ - Declare List indexes and column settings in source with `@index(member: ...)`
15
+ and `@column(member: ...)`. Both were already in the schema contract and
16
+ offered by the language server, but lowering carried them from the manifest
17
+ cache and the emitter never wrote them back, so a workspace that pushed
18
+ without `neo/obj/` silently lost them.
19
+ - Declare a class hidden from the member selector and classes tree with
20
+ `@hidden`, the class-level counterpart to `@locked`. It maps to
21
+ `NeoSchemaClassAttribute.Hidden`, which the contract already carried; absent
22
+ means visible.
23
+
24
+ ## [0.9.4] - 2026-07-25
25
+
26
+ ### Fixed
27
+
28
+ - Preserve provisional list-item subtree identities when canonical source
29
+ reorders an unordered list during trusted server verification.
30
+
10
31
  ## [0.9.3] - 2026-07-25
11
32
 
12
33
  ### Fixed
package/README.md CHANGED
@@ -141,8 +141,23 @@ abstract class InventoryItem<
141
141
  The source identifier is the persisted name. `@settings` and `@storage` are
142
142
  typed by their declaration context; invalid fields and enum cases are
143
143
  diagnostics. Swift-like `.EnumCase` syntax works whenever the expected enum
144
- type is known. Focused annotations such as `@id`, `@locked`, `@system`, and
145
- `@relations` carry cross-cutting behavior.
144
+ type is known. Focused annotations such as `@id`, `@locked`, `@hidden`,
145
+ `@system`, and `@relations` carry cross-cutting behavior. `@hidden` marks a
146
+ class the member selector and classes tree leave out; absent means visible.
147
+
148
+ Lists take repeatable `@index` and `@column` annotations naming a field on the
149
+ entry class. An index requires a non-localizable `string` or a single-select
150
+ enum; a column may also target the reserved `__other__` key, which carries the
151
+ default layout for every field a subclass adds.
152
+
153
+ ```neo
154
+ @id("outpost-list-id")
155
+ @settings(kind: .Unordered)
156
+ @index(member: Slug, unique: true)
157
+ @column(member: Slug, width: 158, frozen: true)
158
+ @column(member: Notes, hidden: true)
159
+ public List<Outpost> Outposts = [];
160
+ ```
146
161
 
147
162
  Stable IDs preserve identity across rename, reorder, and file moves. Omit an
148
163
  `@id` only to create something. A successful real push assigns the ID and
package/dist/neo.mjs CHANGED
@@ -14285,6 +14285,9 @@ function validateMember(uri, member, documentKind, ownerScope, environment, diag
14285
14285
  diagnostics
14286
14286
  );
14287
14287
  validateTypeAnnotations(uri, member.type, environment, diagnostics);
14288
+ if (documentKind !== "flow") {
14289
+ validateListAnnotations(uri, member, environment, diagnostics);
14290
+ }
14288
14291
  const scope = new Map(ownerScope);
14289
14292
  if (member.kind === "function") {
14290
14293
  for (const parameter3 of member.parameters) {
@@ -14435,6 +14438,9 @@ function validateAnnotations(uri, annotations, context, environment, diagnostics
14435
14438
  continue;
14436
14439
  }
14437
14440
  seen.add(field.name);
14441
+ if (field.name === "member" && (annotation2.name === "index" || annotation2.name === "column")) {
14442
+ continue;
14443
+ }
14438
14444
  validateExpressionText(
14439
14445
  uri,
14440
14446
  argument2.text,
@@ -14458,6 +14464,7 @@ function fieldsForAnnotation(name, context, environment) {
14458
14464
  if (name === "locked") {
14459
14465
  return context.kind === "member" || context.kind === "flowBinding" ? [] : null;
14460
14466
  }
14467
+ if (name === "hidden") return context.kind === "class" ? [] : null;
14461
14468
  if (name === "settings") return settingsFields(context);
14462
14469
  if (name === "storage") return storageFields(context);
14463
14470
  if (name === "relations") return relationFields(context, environment);
@@ -15489,6 +15496,121 @@ function contextualAnnotationEnum(annotations, name, field) {
15489
15496
  const value = annotations.find((annotation2) => annotation2.name === name)?.arguments.find((argument2) => argument2.name === field)?.text.trim();
15490
15497
  return value?.startsWith(".") ? value.slice(1) : null;
15491
15498
  }
15499
+ function validateListAnnotations(uri, member, environment, diagnostics) {
15500
+ const declared = member.annotations.filter(
15501
+ (annotation2) => annotation2.name === "index" || annotation2.name === "column"
15502
+ );
15503
+ if (declared.length === 0) return;
15504
+ const entryClass = listEntryClass(member.type, environment);
15505
+ const seen = /* @__PURE__ */ new Set();
15506
+ for (const annotation2 of declared) {
15507
+ const target = memberKeyArgument(annotation2);
15508
+ if (target === null) {
15509
+ pushDiagnostic(
15510
+ diagnostics,
15511
+ uri,
15512
+ annotation2.range,
15513
+ "missing-list-annotation-member",
15514
+ `@${annotation2.name} on '${member.name}' requires a member naming a field on the list entry, such as @${annotation2.name}(member: Name).`
15515
+ );
15516
+ continue;
15517
+ }
15518
+ if (target.key === null) {
15519
+ pushDiagnostic(
15520
+ diagnostics,
15521
+ uri,
15522
+ target.range,
15523
+ "invalid-list-annotation-member",
15524
+ `@${annotation2.name} on '${member.name}' must name a list entry field directly, such as @${annotation2.name}(member: Name).`
15525
+ );
15526
+ continue;
15527
+ }
15528
+ if (seen.has(`${annotation2.name} ${target.key}`)) {
15529
+ pushDiagnostic(
15530
+ diagnostics,
15531
+ uri,
15532
+ target.range,
15533
+ "duplicate-list-annotation",
15534
+ `@${annotation2.name}(member: ${target.key}) is declared more than once on '${member.name}'.`
15535
+ );
15536
+ continue;
15537
+ }
15538
+ seen.add(`${annotation2.name} ${target.key}`);
15539
+ if (annotation2.name === "column" && target.key === LIST_COLUMN_INHERITANCE_KEY) {
15540
+ continue;
15541
+ }
15542
+ if (entryClass === null) continue;
15543
+ const field = resolveEntryField(entryClass, target.key, environment);
15544
+ if (field === null) {
15545
+ pushDiagnostic(
15546
+ diagnostics,
15547
+ uri,
15548
+ target.range,
15549
+ "unresolved-list-annotation-member",
15550
+ `@${annotation2.name}(member: ${target.key}) on '${member.name}' does not resolve on list entry class '${entryClass.name}'.`
15551
+ );
15552
+ continue;
15553
+ }
15554
+ if (annotation2.name !== "index") continue;
15555
+ const domainError = indexKeyDomainError(field, environment);
15556
+ if (domainError !== null) {
15557
+ pushDiagnostic(
15558
+ diagnostics,
15559
+ uri,
15560
+ target.range,
15561
+ "invalid-list-index-member",
15562
+ `@index(member: ${target.key}) on '${member.name}' is invalid: ${domainError}.`
15563
+ );
15564
+ }
15565
+ }
15566
+ }
15567
+ function memberKeyArgument(annotation2) {
15568
+ const positional = annotation2.arguments[0];
15569
+ const argument2 = annotation2.arguments.find((entry) => entry.name === "member") ?? (positional !== void 0 && positional.name === void 0 ? positional : void 0);
15570
+ if (argument2 === void 0) return null;
15571
+ const text = argument2.text.trim();
15572
+ return {
15573
+ key: IDENTIFIER_PATTERN2.test(text) ? text : null,
15574
+ range: argument2.range
15575
+ };
15576
+ }
15577
+ function listEntryClass(type, environment) {
15578
+ if (type.name !== "List" && type.name !== "Set") return null;
15579
+ const entry = type.typeArguments[0];
15580
+ if (entry === void 0) return null;
15581
+ return environment.types.get(entry.name) ?? null;
15582
+ }
15583
+ function resolveEntryField(entryClass, key, environment) {
15584
+ const visited = /* @__PURE__ */ new Set();
15585
+ let current = entryClass;
15586
+ while (current !== void 0 && !visited.has(current.name)) {
15587
+ visited.add(current.name);
15588
+ const match = current.members.find((entry) => entry.name === key);
15589
+ if (match !== void 0) return match;
15590
+ current = current.baseTypes.map((base) => environment.types.get(base.name)).find((base) => base !== void 0);
15591
+ }
15592
+ return null;
15593
+ }
15594
+ function indexKeyDomainError(field, environment) {
15595
+ if (field.kind !== "field") {
15596
+ return `'${field.name}' is a ${field.kind}, and index keys require a stored field`;
15597
+ }
15598
+ if (field.type.name === "string") {
15599
+ return annotationBoolean(field.annotations, "settings", "localizable") === false ? null : `'${field.name}' is localizable, and String index keys must be non-localizable`;
15600
+ }
15601
+ if (environment.enums.has(field.type.name)) return null;
15602
+ const entry = field.type.typeArguments[0];
15603
+ if (field.type.name === "List" && entry !== void 0 && environment.enums.has(entry.name) && annotationBoolean(field.annotations, "settings", "multiselect") === true) {
15604
+ return `'${field.name}' is a multiselect Enum, and index keys require a single-select Enum`;
15605
+ }
15606
+ return `'${field.name}' is a ${field.type.name} field, and index keys require a non-localizable String or a single-select Enum`;
15607
+ }
15608
+ function annotationBoolean(annotations, name, field) {
15609
+ const value = annotations.find((annotation2) => annotation2.name === name)?.arguments.find((argument2) => argument2.name === field)?.text.trim();
15610
+ if (value === "true") return true;
15611
+ if (value === "false") return false;
15612
+ return null;
15613
+ }
15492
15614
  function isListContext(context) {
15493
15615
  return (context.kind === "member" || context.kind === "type") && (context.type.name === "List" || context.type.name === "Set");
15494
15616
  }
@@ -15533,7 +15655,7 @@ function pushDiagnostic(diagnostics, uri, range2, code, message) {
15533
15655
  message
15534
15656
  });
15535
15657
  }
15536
- var primitiveType;
15658
+ var primitiveType, IDENTIFIER_PATTERN2, LIST_COLUMN_INHERITANCE_KEY;
15537
15659
  var init_project_source_semantics = __esm({
15538
15660
  "../packages/neoscript-language/src/project-source-semantics.ts"() {
15539
15661
  "use strict";
@@ -15542,6 +15664,8 @@ var init_project_source_semantics = __esm({
15542
15664
  init_strict_parser();
15543
15665
  init_project_root();
15544
15666
  primitiveType = (name) => ({ name });
15667
+ IDENTIFIER_PATTERN2 = /^[A-Za-z_][A-Za-z0-9_]*$/;
15668
+ LIST_COLUMN_INHERITANCE_KEY = "__other__";
15545
15669
  }
15546
15670
  });
15547
15671
 
@@ -21308,6 +21432,16 @@ function annotationSignatureParameters(name) {
21308
21432
  return ["relation descriptors"];
21309
21433
  case "incomplete":
21310
21434
  return ["string reason"];
21435
+ case "index":
21436
+ return ["entry member", "bool unique"];
21437
+ case "column":
21438
+ return [
21439
+ "entry member",
21440
+ "int width",
21441
+ "bool hidden",
21442
+ "bool frozen",
21443
+ "bool wrapContent"
21444
+ ];
21311
21445
  default:
21312
21446
  return null;
21313
21447
  }
@@ -28783,7 +28917,7 @@ function lowerClass(context, declaration) {
28783
28917
  schemaKeyOrder: persistedSchemaOrder,
28784
28918
  extendsClassId,
28785
28919
  implementsInterfaceIds,
28786
- hiddenInMemberSelector: base?.hiddenInMemberSelector ?? false,
28920
+ hiddenInMemberSelector: hasAnnotation(declaration.annotations, "hidden"),
28787
28921
  system: systemMetadata(declaration.annotations),
28788
28922
  allowedStorage: storage ? storageValue(storage, "allowed") : null,
28789
28923
  // Class storage keys never exist in source. Retain only the historical
@@ -28800,6 +28934,11 @@ function lowerClass(context, declaration) {
28800
28934
  };
28801
28935
  }
28802
28936
  function lowerMember(context, ownerClass, declaration, id2, owner) {
28937
+ const member = lowerMemberKind(context, ownerClass, declaration, id2, owner);
28938
+ assertListAnnotationPlacement(ownerClass, declaration, member.kind);
28939
+ return member;
28940
+ }
28941
+ function lowerMemberKind(context, ownerClass, declaration, id2, owner) {
28803
28942
  const base = context.baseMembers.get(id2);
28804
28943
  const common = memberCommon(
28805
28944
  context,
@@ -29139,8 +29278,8 @@ function lowerFieldMember(context, ownerClass, declaration, id2, commonInput, ba
29139
29278
  kind: "list",
29140
29279
  entryMemberId: entryId,
29141
29280
  listKind: contextualEnumArgument(settings, "kind") === "Unordered" ? "unordered" : "ordered",
29142
- indexes: base?.kind === "list" ? base.indexes : [],
29143
- columns: base?.kind === "list" ? base.columns : []
29281
+ indexes: lowerListIndexes(ownerClass, declaration),
29282
+ columns: lowerListColumns(ownerClass, declaration)
29144
29283
  };
29145
29284
  }
29146
29285
  if (name === "Dictionary") {
@@ -29210,6 +29349,70 @@ function lowerFieldMember(context, ownerClass, declaration, id2, commonInput, ba
29210
29349
  `Unsupported field type ${JSON.stringify(name)} on ${ownerClass.name}.${declaration.name}.`
29211
29350
  );
29212
29351
  }
29352
+ function lowerListIndexes(ownerClass, declaration) {
29353
+ const declared = /* @__PURE__ */ new Set();
29354
+ return declaration.annotations.filter((entry) => entry.name === "index").map((entry) => {
29355
+ const schemaKey = memberKeyArgument2(entry, ownerClass, declaration);
29356
+ if (declared.has(schemaKey)) {
29357
+ throw new Error(
29358
+ `@index(member: ${schemaKey}) is declared more than once on ${ownerClass.name}.${declaration.name}.`
29359
+ );
29360
+ }
29361
+ declared.add(schemaKey);
29362
+ return { schemaKey, unique: booleanArgument(entry, "unique") ?? false };
29363
+ });
29364
+ }
29365
+ function lowerListColumns(ownerClass, declaration) {
29366
+ const declared = /* @__PURE__ */ new Set();
29367
+ return declaration.annotations.filter((entry) => entry.name === "column").map((entry) => {
29368
+ const memberKey = memberKeyArgument2(entry, ownerClass, declaration);
29369
+ if (declared.has(memberKey)) {
29370
+ throw new Error(
29371
+ `@column(member: ${memberKey}) is declared more than once on ${ownerClass.name}.${declaration.name}.`
29372
+ );
29373
+ }
29374
+ declared.add(memberKey);
29375
+ const width = numberArgument(entry, "width");
29376
+ return {
29377
+ memberKey,
29378
+ // `-1` is the generated C# marker's "unset" sentinel for an int
29379
+ // property that has no nullable form; the record spells that as null.
29380
+ width: width === null || width === UNSET_LIST_COLUMN_WIDTH ? null : width,
29381
+ hidden: booleanArgument(entry, "hidden") ?? false,
29382
+ frozen: booleanArgument(entry, "frozen") ?? false,
29383
+ wrapContent: booleanArgument(entry, "wrapContent") ?? false
29384
+ };
29385
+ });
29386
+ }
29387
+ function memberKeyArgument2(value, ownerClass, declaration) {
29388
+ const positional = value.arguments[0];
29389
+ const raw = argument(value, "member") ?? (positional !== void 0 && positional.name === void 0 ? positional.expression : null);
29390
+ if (raw === null) {
29391
+ throw new Error(
29392
+ `@${value.name} on ${ownerClass.name}.${declaration.name} requires a member naming a field on the list entry, such as @${value.name}(member: Name).`
29393
+ );
29394
+ }
29395
+ const expression = parseAnnotationExpression(raw);
29396
+ if (expression.kind !== "ident") {
29397
+ throw new Error(
29398
+ `@${value.name}(member: ${raw.trim()}) on ${ownerClass.name}.${declaration.name} must name a list entry field directly, such as @${value.name}(member: Name).`
29399
+ );
29400
+ }
29401
+ return expression.name;
29402
+ }
29403
+ function assertListAnnotationPlacement(ownerClass, declaration, loweredKind) {
29404
+ if (loweredKind === "list") return;
29405
+ if (hasAnnotation(declaration.annotations, "index")) {
29406
+ throw new Error(
29407
+ `@index on ${ownerClass.name}.${declaration.name} is only valid on a List member; this declaration lowers to a ${loweredKind} member.`
29408
+ );
29409
+ }
29410
+ if (hasAnnotation(declaration.annotations, "column")) {
29411
+ throw new Error(
29412
+ `@column on ${ownerClass.name}.${declaration.name} is only valid on a List member; this declaration lowers to a ${loweredKind} member.`
29413
+ );
29414
+ }
29415
+ }
29213
29416
  function lowerCollectionEntry(context, ownerClass, entryType, entryId, parentMemberId) {
29214
29417
  if (context.loweredMembers.has(entryId)) return;
29215
29418
  const base = context.baseMembers.get(entryId);
@@ -30041,13 +30244,14 @@ function* zip(left, right) {
30041
30244
  yield [left[index], right[index]];
30042
30245
  }
30043
30246
  }
30044
- var primitiveMemberKinds;
30247
+ var UNSET_LIST_COLUMN_WIDTH, primitiveMemberKinds;
30045
30248
  var init_lower_members = __esm({
30046
30249
  "src/project-source/lower-members.ts"() {
30047
30250
  "use strict";
30048
30251
  init_src();
30049
30252
  init_lower_support();
30050
30253
  init_ui_action_source();
30254
+ UNSET_LIST_COLUMN_WIDTH = -1;
30051
30255
  primitiveMemberKinds = /* @__PURE__ */ new Set([
30052
30256
  "null",
30053
30257
  "bool",
@@ -30752,6 +30956,7 @@ ${namedArguments(
30752
30956
  function emitClass(context, schemaClass2, memberIds) {
30753
30957
  const annotations = [
30754
30958
  id(schemaClass2.id).trimEnd(),
30959
+ ...schemaClass2.hiddenInMemberSelector ? ["@hidden"] : [],
30755
30960
  ...schemaClass2.allowedStorage ? [`@storage(allowed: .${enumCase(schemaClass2.allowedStorage)})`] : [],
30756
30961
  ...schemaClass2.system ? [systemAnnotation(schemaClass2.system)] : [],
30757
30962
  ...relationsAnnotations(context, schemaClass2)
@@ -30883,6 +31088,7 @@ function emitMember(context, member) {
30883
31088
  ].join(", ")})`
30884
31089
  ] : [],
30885
31090
  ...memberSettings(context, member),
31091
+ ...listAnnotations(member),
30886
31092
  ...member.system ? [systemAnnotation(member.system)] : []
30887
31093
  ];
30888
31094
  const prefix = memberModifier3(member);
@@ -31024,6 +31230,28 @@ function memberSettings(context, member) {
31024
31230
  }
31025
31231
  return settings.length ? [`@settings(${settings.join(", ")})`] : [];
31026
31232
  }
31233
+ function listAnnotations(member) {
31234
+ if (member.kind !== "list") return [];
31235
+ return [
31236
+ ...member.indexes.map((entry) => {
31237
+ assertIdentifier(entry.schemaKey, "list index member");
31238
+ return `@index(${[
31239
+ `member: ${entry.schemaKey}`,
31240
+ ...entry.unique ? ["unique: true"] : []
31241
+ ].join(", ")})`;
31242
+ }),
31243
+ ...member.columns.map((entry) => {
31244
+ assertIdentifier(entry.memberKey, "list column member");
31245
+ return `@column(${[
31246
+ `member: ${entry.memberKey}`,
31247
+ ...entry.width === null ? [] : [`width: ${entry.width}`],
31248
+ ...entry.hidden ? ["hidden: true"] : [],
31249
+ ...entry.frozen ? ["frozen: true"] : [],
31250
+ ...entry.wrapContent ? ["wrapContent: true"] : []
31251
+ ].join(", ")})`;
31252
+ })
31253
+ ];
31254
+ }
31027
31255
  function renderMemberType(context, member) {
31028
31256
  const nullable = !member.required;
31029
31257
  let value;
@@ -40297,11 +40525,43 @@ var init_dialogue_db_response_types = __esm({
40297
40525
  }
40298
40526
  });
40299
40527
 
40528
+ // ../src/common/benchmark.ts
40529
+ var init_benchmark = __esm({
40530
+ "../src/common/benchmark.ts"() {
40531
+ "use strict";
40532
+ }
40533
+ });
40534
+
40535
+ // ../src/common/error-message.ts
40536
+ var init_error_message = __esm({
40537
+ "../src/common/error-message.ts"() {
40538
+ "use strict";
40539
+ }
40540
+ });
40541
+
40542
+ // ../src/common/retree-input-diagnostics.ts
40543
+ var init_retree_input_diagnostics = __esm({
40544
+ "../src/common/retree-input-diagnostics.ts"() {
40545
+ "use strict";
40546
+ }
40547
+ });
40548
+
40549
+ // ../src/common/index.ts
40550
+ var init_common = __esm({
40551
+ "../src/common/index.ts"() {
40552
+ "use strict";
40553
+ init_benchmark();
40554
+ init_error_message();
40555
+ init_retree_input_diagnostics();
40556
+ }
40557
+ });
40558
+
40300
40559
  // ../src/models/dialogue/dialogue-triggering.ts
40301
40560
  var DEFAULT_RECENCY_HALF_LIFE_SECONDS;
40302
40561
  var init_dialogue_triggering = __esm({
40303
40562
  "../src/models/dialogue/dialogue-triggering.ts"() {
40304
40563
  "use strict";
40564
+ init_common();
40305
40565
  init_dialogue_group_types();
40306
40566
  DEFAULT_RECENCY_HALF_LIFE_SECONDS = 60 * 60 * 24;
40307
40567
  }
@@ -43271,12 +43531,16 @@ function lowerListValue(context, member, expression, base, source, inheritedEnvi
43271
43531
  function lowerPendingListItem(context, member, expression, source, path, containerId, environment) {
43272
43532
  const rows = /* @__PURE__ */ new Map();
43273
43533
  const localizedTexts = /* @__PURE__ */ new Map();
43274
- const valueId = lowerSeedChild(
43534
+ const annotatedId = annotatedValue(expression).id;
43535
+ const valueId = annotatedId ?? pendingNestedValueId(source, path);
43536
+ const stablePath = isPendingId(valueId) ? valueId : path;
43537
+ lowerSeedRow(
43275
43538
  context,
43276
43539
  member,
43277
43540
  expression,
43278
43541
  source,
43279
- path,
43542
+ valueId,
43543
+ stablePath,
43280
43544
  rows,
43281
43545
  localizedTexts,
43282
43546
  containerId ?? void 0,
@@ -43882,9 +44146,13 @@ function pendingValueId(binding, name) {
43882
44146
  return `__pending__:value:${encodeURIComponent(binding.source.uri)}:${binding.source.range.start.line}:${binding.source.range.start.character}:${encodeURIComponent(name)}`;
43883
44147
  }
43884
44148
  function pendingNestedValueId(binding, path) {
44149
+ if (path.startsWith("__pending__:value:")) return path;
43885
44150
  return `__pending__:value:${encodeURIComponent(binding.source.uri)}:${binding.source.range.start.line}:${binding.source.range.start.character}:${encodeURIComponent(path)}`;
43886
44151
  }
43887
44152
  function pendingLocalizedTextId(binding, path) {
44153
+ if (path.startsWith("__pending__:value:")) {
44154
+ return `__pending__:localized-text:${path.slice("__pending__:value:".length)}`;
44155
+ }
43888
44156
  return `__pending__:localized-text:${encodeURIComponent(binding.source.uri)}:${binding.source.range.start.line}:${binding.source.range.start.character}:${encodeURIComponent(path)}`;
43889
44157
  }
43890
44158
  function classMemberByName(context, classId, name) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@neocompose/cli",
3
- "version": "0.9.3",
3
+ "version": "0.10.0",
4
4
  "description": "Neo Compose native project-source CLI with bidirectional sync.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -196,8 +196,14 @@ abstract class InventoryItem<
196
196
  - `@settings(...)` and `@storage(...)` are context-aware typed contracts.
197
197
  Numeric limits are numeric literals. A class supports only
198
198
  `@storage(allowed: ...)`; storage keys are member-only.
199
- - Keep focused annotations such as `@locked`, `@system`, and `@relations`
200
- separate from contextual settings.
199
+ - Keep focused annotations such as `@locked`, `@hidden`, `@system`, and
200
+ `@relations` separate from contextual settings. `@hidden` is class-only and
201
+ hides the class from the member selector and classes tree; absent is visible.
202
+ - `@index(member: Field, unique: ...)` and
203
+ `@column(member: Field, width: ..., hidden: ..., frozen: ..., wrapContent: ...)`
204
+ are repeatable List annotations. `member:` is a bare field name on the entry
205
+ class — an index needs a non-localizable `string` or a single-select enum,
206
+ and a column may also target the reserved `__other__` key.
201
207
  - `.EnumCase` works only when one expected enum type is known. Use explicit
202
208
  `EnumType.Case` when context is ambiguous.
203
209
  - Defaults and settings must be statically analyzable. Project source is parsed