Skip to content

Commit d5774b5

Browse files
os-zhuangclaude
andauthored
fix(spec): rating/vector builders emit live props; promote flat dimensions (#1976)
The Field.rating / Field.vector builders emitted properties the spec-liveness ledger classifies as dead (silent runtime no-ops), so every field authored through them tripped the `liveness-dead-property` author lint: - Field.rating(n) emitted `maxRating`; the rating renderer reads the flat `max` (RatingField.tsx:13). Builder now emits `max`. - Field.vector(n) emitted a nested `vectorConfig` block; the renderer reads the flat `dimensions` sibling (VectorField.tsx:11) and nothing consumes vectorConfig (no vector-index DDL). Builder now emits flat `dimensions`. `dimensions` is promoted to a declared, LIVE top-level FieldSchema property and classified in the liveness ledger. It was previously valid only nested inside vectorConfig, so a hand-authored flat `dimensions` was silently stripped during compile (Zod drops unknown keys) — the renderer then saw no dimensionality. This retroactively makes the app-showcase fix (#1973) actually carry its value into the artifact, verified: f_vector.dimensions=1536, f_rating.max=5. maxRating and vectorConfig remain accepted by the schema (still dead + authorWarn) for back-compat, so hand-authored usages still warn rather than type-error. Verification: liveness gate green (field 53 classified, all classified), field.test.ts 115/115 pass, showcase compile exit 0 with zero liveness-dead-property warnings. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent e43eb0c commit d5774b5

4 files changed

Lines changed: 51 additions & 20 deletions

File tree

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
---
2+
"@objectstack/spec": minor
3+
---
4+
5+
fix(spec): `Field.rating` / `Field.vector` builders emit live props instead of dead ones
6+
7+
The `Field.rating(n)` and `Field.vector(n)` builders emitted properties the
8+
spec-liveness ledger classifies as **dead** (silent runtime no-ops), so every
9+
field authored through them tripped the `liveness-dead-property` author lint:
10+
11+
- `Field.rating(n)` emitted `maxRating`, but the rating renderer reads the flat
12+
`max` prop (`RatingField.tsx:13`). The builder now emits `max`.
13+
- `Field.vector(n)` emitted a nested `vectorConfig` block, but the renderer
14+
reads the flat `dimensions` sibling (`VectorField.tsx:11`) and nothing
15+
consumes `vectorConfig` (no vector-index DDL). The builder now emits the flat
16+
`dimensions`.
17+
18+
`dimensions` is also promoted to a **declared, live** top-level `FieldSchema`
19+
property. It was previously only valid nested inside `vectorConfig`, so a flat
20+
`dimensions` authored by hand was silently **stripped** during compile (Zod
21+
drops unknown keys) — the renderer then saw no dimensionality. It now survives
22+
compilation and is governed by the liveness gate.
23+
24+
`maxRating` and `vectorConfig` remain accepted by the schema (still classified
25+
`dead` + `authorWarn`) for back-compat, so hand-authored usages still surface
26+
the advisory warning rather than type-erroring.

packages/spec/liveness/field.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,10 @@
187187
"evidence": "objectui: packages/fields/src/index.tsx:469 reads currencyConfig.defaultCurrency",
188188
"note": "LIVE via objectui renderer — the 2026-06 audit mis-classified as dead (renderer side not re-verified). Corrected after checking ../objectui."
189189
},
190+
"dimensions": {
191+
"status": "live",
192+
"note": "vector field — objectui VectorField.tsx:11 reads the flat `dimensions` prop. The live authoring path (cf. dead `vectorConfig`); `Field.vector(n)` emits this."
193+
},
190194
"vectorConfig": {
191195
"status": "dead",
192196
"evidence": "nested config — renderers read flat dimensions; no consumer, no vector-index DDL",

packages/spec/src/data/field.test.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -581,7 +581,8 @@ describe('Field Factory Helpers', () => {
581581
});
582582

583583
expect(ratingField.type).toBe('rating');
584-
expect(ratingField.maxRating).toBe(10);
584+
// Builder emits the live `max` prop (RatingField reads `max`, not `maxRating`).
585+
expect(ratingField.max).toBe(10);
585586
});
586587

587588
it('should create signature field', () => {
@@ -823,10 +824,10 @@ describe('Field Factory Helpers', () => {
823824
expect(vectorField.type).toBe('vector');
824825
expect(vectorField.name).toBe('embedding');
825826
expect(vectorField.label).toBe('Text Embedding');
826-
expect(vectorField.vectorConfig?.dimensions).toBe(1536);
827-
expect(vectorField.vectorConfig?.distanceMetric).toBe('cosine');
828-
expect(vectorField.vectorConfig?.normalized).toBe(false);
829-
expect(vectorField.vectorConfig?.indexed).toBe(true);
827+
// Builder emits the live flat `dimensions` prop (renderer reads it);
828+
// it no longer synthesizes the dead `vectorConfig` block.
829+
expect(vectorField.dimensions).toBe(1536);
830+
expect((vectorField as Record<string, unknown>).vectorConfig).toBeUndefined();
830831
});
831832

832833
it('should create vector field with custom config', () => {

packages/spec/src/data/field.zod.ts

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -468,7 +468,13 @@ export const FieldSchema = lazySchema(() => z.object({
468468
// Currency field config
469469
currencyConfig: CurrencyConfigSchema.optional().describe('Configuration for currency field type'),
470470

471-
// Vector field config
471+
// Vector field — flat dimensionality (the live authoring path).
472+
// The renderer reads this flat sibling (objectui VectorField.tsx:11); the
473+
// nested `vectorConfig` below is retained for back-compat but is a runtime
474+
// no-op (no vector-index DDL consumes it).
475+
dimensions: z.number().int().min(1).max(10000).optional().describe('Vector dimensionality (e.g., 1536 for OpenAI embeddings)'),
476+
477+
// Vector field config (DEAD — kept for back-compat; set the flat `dimensions` sibling instead)
472478
vectorConfig: VectorConfigSchema.optional().describe('Configuration for vector field type (AI/ML embeddings)'),
473479

474480
// File attachment field config
@@ -671,10 +677,10 @@ export const Field = {
671677
...config
672678
} as const),
673679

674-
rating: (maxRating: number = 5, config: FieldInput = {}) => ({
675-
type: 'rating',
676-
maxRating,
677-
...config
680+
rating: (max: number = 5, config: FieldInput = {}) => ({
681+
type: 'rating',
682+
max,
683+
...config
678684
} as const),
679685

680686
signature: (config: FieldInput = {}) => ({
@@ -697,15 +703,9 @@ export const Field = {
697703
...config
698704
} as const),
699705

700-
vector: (dimensions: number, config: FieldInput = {}) => ({
701-
type: 'vector',
702-
vectorConfig: {
703-
dimensions,
704-
distanceMetric: 'cosine' as const,
705-
normalized: false,
706-
indexed: true,
707-
...config.vectorConfig
708-
},
709-
...config
706+
vector: (dimensions: number, config: FieldInput = {}) => ({
707+
type: 'vector',
708+
dimensions,
709+
...config
710710
} as const),
711711
};

0 commit comments

Comments
 (0)