From 4c3e73e2861c8796a0d4f91d1f9e6fe886cebace Mon Sep 17 00:00:00 2001 From: os-zhuang Date: Tue, 16 Jun 2026 23:56:54 +0800 Subject: [PATCH] fix(spec): rating/vector builders emit live props; promote flat `dimensions` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- .changeset/field-builders-emit-live-props.md | 26 +++++++++++++++++ packages/spec/liveness/field.json | 4 +++ packages/spec/src/data/field.test.ts | 11 +++---- packages/spec/src/data/field.zod.ts | 30 ++++++++++---------- 4 files changed, 51 insertions(+), 20 deletions(-) create mode 100644 .changeset/field-builders-emit-live-props.md diff --git a/.changeset/field-builders-emit-live-props.md b/.changeset/field-builders-emit-live-props.md new file mode 100644 index 0000000000..f474c55cd6 --- /dev/null +++ b/.changeset/field-builders-emit-live-props.md @@ -0,0 +1,26 @@ +--- +"@objectstack/spec": minor +--- + +fix(spec): `Field.rating` / `Field.vector` builders emit live props instead of dead ones + +The `Field.rating(n)` and `Field.vector(n)` 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`, but the rating renderer reads the flat + `max` prop (`RatingField.tsx:13`). The builder now emits `max`. +- `Field.vector(n)` emitted a nested `vectorConfig` block, but the renderer + reads the flat `dimensions` sibling (`VectorField.tsx:11`) and nothing + consumes `vectorConfig` (no vector-index DDL). The builder now emits the flat + `dimensions`. + +`dimensions` is also promoted to a **declared, live** top-level `FieldSchema` +property. It was previously only valid nested inside `vectorConfig`, so a flat +`dimensions` authored by hand was silently **stripped** during compile (Zod +drops unknown keys) — the renderer then saw no dimensionality. It now survives +compilation and is governed by the liveness gate. + +`maxRating` and `vectorConfig` remain accepted by the schema (still classified +`dead` + `authorWarn`) for back-compat, so hand-authored usages still surface +the advisory warning rather than type-erroring. diff --git a/packages/spec/liveness/field.json b/packages/spec/liveness/field.json index 02158d9b10..5f0d872a07 100644 --- a/packages/spec/liveness/field.json +++ b/packages/spec/liveness/field.json @@ -187,6 +187,10 @@ "evidence": "objectui: packages/fields/src/index.tsx:469 reads currencyConfig.defaultCurrency", "note": "LIVE via objectui renderer — the 2026-06 audit mis-classified as dead (renderer side not re-verified). Corrected after checking ../objectui." }, + "dimensions": { + "status": "live", + "note": "vector field — objectui VectorField.tsx:11 reads the flat `dimensions` prop. The live authoring path (cf. dead `vectorConfig`); `Field.vector(n)` emits this." + }, "vectorConfig": { "status": "dead", "evidence": "nested config — renderers read flat dimensions; no consumer, no vector-index DDL", diff --git a/packages/spec/src/data/field.test.ts b/packages/spec/src/data/field.test.ts index 1d3400cb50..fa2ef78477 100644 --- a/packages/spec/src/data/field.test.ts +++ b/packages/spec/src/data/field.test.ts @@ -581,7 +581,8 @@ describe('Field Factory Helpers', () => { }); expect(ratingField.type).toBe('rating'); - expect(ratingField.maxRating).toBe(10); + // Builder emits the live `max` prop (RatingField reads `max`, not `maxRating`). + expect(ratingField.max).toBe(10); }); it('should create signature field', () => { @@ -823,10 +824,10 @@ describe('Field Factory Helpers', () => { expect(vectorField.type).toBe('vector'); expect(vectorField.name).toBe('embedding'); expect(vectorField.label).toBe('Text Embedding'); - expect(vectorField.vectorConfig?.dimensions).toBe(1536); - expect(vectorField.vectorConfig?.distanceMetric).toBe('cosine'); - expect(vectorField.vectorConfig?.normalized).toBe(false); - expect(vectorField.vectorConfig?.indexed).toBe(true); + // Builder emits the live flat `dimensions` prop (renderer reads it); + // it no longer synthesizes the dead `vectorConfig` block. + expect(vectorField.dimensions).toBe(1536); + expect((vectorField as Record).vectorConfig).toBeUndefined(); }); it('should create vector field with custom config', () => { diff --git a/packages/spec/src/data/field.zod.ts b/packages/spec/src/data/field.zod.ts index d6bc233367..9a5d695d15 100644 --- a/packages/spec/src/data/field.zod.ts +++ b/packages/spec/src/data/field.zod.ts @@ -468,7 +468,13 @@ export const FieldSchema = lazySchema(() => z.object({ // Currency field config currencyConfig: CurrencyConfigSchema.optional().describe('Configuration for currency field type'), - // Vector field config + // Vector field — flat dimensionality (the live authoring path). + // The renderer reads this flat sibling (objectui VectorField.tsx:11); the + // nested `vectorConfig` below is retained for back-compat but is a runtime + // no-op (no vector-index DDL consumes it). + dimensions: z.number().int().min(1).max(10000).optional().describe('Vector dimensionality (e.g., 1536 for OpenAI embeddings)'), + + // Vector field config (DEAD — kept for back-compat; set the flat `dimensions` sibling instead) vectorConfig: VectorConfigSchema.optional().describe('Configuration for vector field type (AI/ML embeddings)'), // File attachment field config @@ -671,10 +677,10 @@ export const Field = { ...config } as const), - rating: (maxRating: number = 5, config: FieldInput = {}) => ({ - type: 'rating', - maxRating, - ...config + rating: (max: number = 5, config: FieldInput = {}) => ({ + type: 'rating', + max, + ...config } as const), signature: (config: FieldInput = {}) => ({ @@ -697,15 +703,9 @@ export const Field = { ...config } as const), - vector: (dimensions: number, config: FieldInput = {}) => ({ - type: 'vector', - vectorConfig: { - dimensions, - distanceMetric: 'cosine' as const, - normalized: false, - indexed: true, - ...config.vectorConfig - }, - ...config + vector: (dimensions: number, config: FieldInput = {}) => ({ + type: 'vector', + dimensions, + ...config } as const), };