From e73d5cd57ce3b28489fc1650a45c9ba96e2e63de Mon Sep 17 00:00:00 2001 From: os-zhuang Date: Tue, 16 Jun 2026 14:06:39 +0800 Subject: [PATCH] chore(spec): prune 15 dead field display-config props (dead-surface plan, P2) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Removes FieldSchema enhanced-type *display* knobs that had no runtime reader and no renderer consumer — dead in both layers per the field liveness audit, and confirmed by greps (no object def / seed constructs them) + the full spec suite (6562 tests) staying green: code theme/lineNumbers · rating allowHalf · location displayMap/allowGeocoding · address addressFormat · color colorFormat/allowAlpha/presetColors · slider showValue/marks · barcode/qr barcodeFormat/qrErrorCorrection/displayValue/allowScanning Kept: the wired knobs (language, maxRating, step) and the functional nested configs (currencyConfig/vectorConfig/fileAttachmentConfig — they carry data the field type needs, e.g. vector dimensions; the audit's "flat sibling" claim lives in the objectui repo and can't be verified here, so they're left for that repo). Field *types* are unchanged; only unused optional config props are removed. field.zod.ts schema + field.test.ts (assertions/configs) + field.json ledger updated; reference docs regenerated; liveness gate green (field 34 live/24 dead). First executed batch of the dead-surface disposition plan. Pruning is a coupled refactor (schema → Field factory → tests → forms → translations), so it proceeds as small per-cluster verified PRs, not one sweep — see the plan doc. Co-Authored-By: Claude Opus 4.8 (1M context) --- .changeset/prune-field-display-configs.md | 5 + packages/spec/liveness/field.json | 315 +++++++++++++++++----- packages/spec/src/data/field.test.ts | 42 +-- packages/spec/src/data/field.zod.ts | 36 +-- 4 files changed, 261 insertions(+), 137 deletions(-) create mode 100644 .changeset/prune-field-display-configs.md diff --git a/.changeset/prune-field-display-configs.md b/.changeset/prune-field-display-configs.md new file mode 100644 index 0000000000..5299b32e75 --- /dev/null +++ b/.changeset/prune-field-display-configs.md @@ -0,0 +1,5 @@ +--- +"@objectstack/spec": minor +--- + +chore(spec): prune 15 dead field display-config properties (ADR-0049 / dead-surface plan). Removes `FieldSchema` enhanced-type *display* knobs that had no runtime reader and no renderer consumer (dead in both layers per the field liveness audit): code `theme`/`lineNumbers`, rating `allowHalf`, location `displayMap`/`allowGeocoding`, address `addressFormat`, color `colorFormat`/`allowAlpha`/`presetColors`, slider `showValue`/`marks`, barcode/qr `barcodeFormat`/`qrErrorCorrection`/`displayValue`/`allowScanning`. The wired knobs (`language`, `maxRating`, `step`) and the functional nested configs (`currencyConfig`/`vectorConfig`/`fileAttachmentConfig`) are kept. Field *types* are unchanged; only unused optional config props are removed. Narrows the false spec surface (narrow-and-true). diff --git a/packages/spec/liveness/field.json b/packages/spec/liveness/field.json index cad5b1eac1..6ab7fbcf95 100644 --- a/packages/spec/liveness/field.json +++ b/packages/spec/liveness/field.json @@ -2,78 +2,247 @@ "type": "field", "_note": "FieldSchema (flat — all field-type configs are top-level optional props). Seeded from docs/audits/2026-06-fieldschema-property-liveness.md. ~half dead. Nested config objects (currencyConfig/vectorConfig/fileAttachmentConfig/encryptionConfig/maskingRule/cached/dataQuality) are wholly dead → classified at top level. Naming-drift props are server-live but client-snake. Framework evidence cited with paths; objectui-renderer evidence as prose.", "props": { - "name": { "status": "live", "evidence": "packages/objectql/src/engine.ts" }, - "label": { "status": "live", "note": "renderers." }, - "type": { "status": "live", "evidence": "packages/objectql/src/engine.ts" }, - "description": { "status": "live", "note": "display." }, - "format": { "status": "live", "evidence": "packages/objectql/src/engine.ts" }, - "required": { "status": "live", "evidence": "packages/objectql/src/validation/record-validator.ts" }, - "multiple": { "status": "live", "evidence": "packages/objectql/src/engine.ts" }, - "defaultValue": { "status": "live", "evidence": "packages/objectql/src/engine.ts" }, - "min": { "status": "live", "evidence": "packages/objectql/src/validation/record-validator.ts" }, - "max": { "status": "live", "evidence": "packages/objectql/src/validation/record-validator.ts" }, - "options": { "status": "live", "note": "select options {label,value,color,default} — renderers + validation." }, - "deleteBehavior": { "status": "live", "evidence": "packages/objectql/src/engine.ts", "note": "reference cascade/restrict." }, - "expression": { "status": "live", "evidence": "packages/objectql/src/engine.ts", "note": "formula." }, - "summaryOperations": { "status": "live", "evidence": "packages/objectql/src/engine.ts", "note": "rollup {object,field,function,relationshipField}." }, - "requiredWhen": { "status": "live", "evidence": "packages/objectql/src/validation/record-validator.ts", "note": "CEL." }, - "readonlyWhen": { "status": "live", "note": "renderer CEL." }, - "visibleWhen": { "status": "live", "note": "renderer CEL." }, - "conditionalRequired": { "status": "live", "note": "deprecated alias of requiredWhen — plan removal." }, - "readonly": { "status": "live", "note": "renderer." }, - "hidden": { "status": "live", "note": "renderer." }, - "system": { "status": "live", "evidence": "packages/objectql/src/engine.ts" }, - "sortable": { "status": "live", "note": "grid." }, - "inlineEdit": { "status": "live", "note": "grid inline editing." }, - "language": { "status": "live", "note": "code field — CodeField.tsx:13 (only language is live; theme/lineNumbers dead)." }, - "step": { "status": "live", "note": "slider — SliderField.tsx (min/max/step live)." }, - "group": { "status": "live", "note": "field group/layout (renderer)." }, - "inlineHelpText": { "status": "live", "note": "display help (renderer)." }, - "unique": { "status": "live", "evidence": "packages/plugins/driver-sql/src/sql-driver.ts:1853", "note": "CAVEAT — DDL-only; NOT validated on the write path (violations surface as raw driver errors)." }, - "precision": { "status": "live", "note": "CAVEAT — UI display formatting only; DDL never sizes (maps to float). (NumberField.tsx:16)." }, - "scale": { "status": "live", "note": "CAVEAT — grid formatting only; DDL never sizes." }, - "reference": { "status": "live", "evidence": "packages/objectql/src/engine.ts:1672", "note": "CAVEAT — $expand/cascade/seed live; FK DDL reads reference_to (unmapped from reference)." }, - "autonumberFormat": { "status": "live", "evidence": "packages/objectql/src/engine.ts:765", "note": "CAVEAT — runtime sequence formatting live; UI AutoNumberField ignores it." }, - "maxLength": { "status": "live", "evidence": "packages/objectql/src/validation/record-validator.ts:127", "note": "CAVEAT — server validation honors camelCase, but the client form reads snake_case max_length." }, - "minLength": { "status": "live", "evidence": "packages/objectql/src/validation/record-validator.ts:130", "note": "CAVEAT — server camel; client form reads min_length." }, - "referenceFilters": { "status": "dead", "evidence": "lookup dialog reads lookup_filters (LookupField.tsx:171) — entirely dead as authored (naming drift)" }, - "maxRating": { "status": "dead", "evidence": "RatingField reads `max` (RatingField.tsx:13) — dead + redundant with max" }, - "columnName": { "status": "dead", "evidence": "resolveColumnName (spec system-names.ts:182) has ZERO call sites; SQL driver hardcodes column = field key", "note": "DANGEROUS — advertises custom physical columns the driver never honors." }, - "searchable": { "status": "dead", "evidence": "field-level — no DDL/query consumer; search is object-level / view searchableFields" }, - "index": { "status": "dead", "evidence": "field-level — driver reads object indexes[] (sql-driver.ts:1252); field bool unused" }, - "externalId": { "status": "dead", "evidence": "field-level — upsert keys off dataset-level externalId (seed-loader.ts:175)" }, - "currencyConfig": { "status": "dead", "evidence": "nested config — renderers read flat currency/precision (CurrencyField.tsx:40); no currencyConfig consumer" }, - "vectorConfig": { "status": "dead", "evidence": "nested config — renderers read flat dimensions; no consumer, no vector-index DDL" }, - "fileAttachmentConfig": { "status": "dead", "evidence": "nested config — renderers read flat multiple/accept/maxSize (FileField.tsx:16); no size/type/virus enforcement in write path" }, - "encryptionConfig": { "status": "dead", "evidence": "no consumer; the only at-rest protection is the separate type:'secret' channel (engine.ts encryptSecretFields)" }, - "maskingRule": { "status": "dead", "evidence": "no consumer" }, - "auditTrail": { "status": "dead", "evidence": "no consumer (aspirational governance)" }, - "dataQuality": { "status": "dead", "evidence": "no consumer (aspirational governance)" }, - "cached": { "status": "dead", "evidence": "no consumer" }, - "dependencies": { "status": "dead", "evidence": "no consumer" }, - "trackFeedHistory": { "status": "dead", "evidence": "no consumer" }, - "caseSensitive": { "status": "dead", "evidence": "no consumer" }, - "writeRequiresMasterRead": { "status": "dead", "evidence": "no consumer (governance)" }, - "theme": { "status": "dead", "evidence": "code field — only language is live" }, - "lineNumbers": { "status": "dead", "evidence": "code field — only language is live" }, - "allowHalf": { "status": "dead", "evidence": "rating — unread" }, - "displayMap": { "status": "dead", "evidence": "location — unread" }, - "allowGeocoding": { "status": "dead", "evidence": "location — unread" }, - "addressFormat": { "status": "dead", "evidence": "address — unread" }, - "colorFormat": { "status": "dead", "evidence": "color — ColorField uses a fixed hex input" }, - "allowAlpha": { "status": "dead", "evidence": "color — unread" }, - "presetColors": { "status": "dead", "evidence": "color — unread" }, - "showValue": { "status": "dead", "evidence": "slider — unread" }, - "marks": { "status": "dead", "evidence": "slider — unread" }, - "barcodeFormat": { "status": "dead", "evidence": "barcode/qr — unread" }, - "qrErrorCorrection": { "status": "dead", "evidence": "barcode/qr — unread" }, - "displayValue": { "status": "dead", "evidence": "barcode/qr — unread" }, - "allowScanning": { "status": "dead", "evidence": "barcode/qr — unread" }, - "inlineTitle": { "status": "dead", "evidence": "master-detail explicit override — auto-derivation works (deriveMasterDetail.ts); overrides unread" }, - "inlineColumns": { "status": "dead", "evidence": "master-detail explicit override — unread" }, - "inlineAmountField": { "status": "dead", "evidence": "master-detail explicit override — unread" }, - "relatedList": { "status": "dead", "evidence": "detail-page related lists come from view metadata, not FieldSchema" }, - "relatedListTitle": { "status": "dead", "evidence": "master-detail explicit override — unread" }, - "relatedListColumns": { "status": "dead", "evidence": "master-detail explicit override — unread" } + "name": { + "status": "live", + "evidence": "packages/objectql/src/engine.ts" + }, + "label": { + "status": "live", + "note": "renderers." + }, + "type": { + "status": "live", + "evidence": "packages/objectql/src/engine.ts" + }, + "description": { + "status": "live", + "note": "display." + }, + "format": { + "status": "live", + "evidence": "packages/objectql/src/engine.ts" + }, + "required": { + "status": "live", + "evidence": "packages/objectql/src/validation/record-validator.ts" + }, + "multiple": { + "status": "live", + "evidence": "packages/objectql/src/engine.ts" + }, + "defaultValue": { + "status": "live", + "evidence": "packages/objectql/src/engine.ts" + }, + "min": { + "status": "live", + "evidence": "packages/objectql/src/validation/record-validator.ts" + }, + "max": { + "status": "live", + "evidence": "packages/objectql/src/validation/record-validator.ts" + }, + "options": { + "status": "live", + "note": "select options {label,value,color,default} — renderers + validation." + }, + "deleteBehavior": { + "status": "live", + "evidence": "packages/objectql/src/engine.ts", + "note": "reference cascade/restrict." + }, + "expression": { + "status": "live", + "evidence": "packages/objectql/src/engine.ts", + "note": "formula." + }, + "summaryOperations": { + "status": "live", + "evidence": "packages/objectql/src/engine.ts", + "note": "rollup {object,field,function,relationshipField}." + }, + "requiredWhen": { + "status": "live", + "evidence": "packages/objectql/src/validation/record-validator.ts", + "note": "CEL." + }, + "readonlyWhen": { + "status": "live", + "note": "renderer CEL." + }, + "visibleWhen": { + "status": "live", + "note": "renderer CEL." + }, + "conditionalRequired": { + "status": "live", + "note": "deprecated alias of requiredWhen — plan removal." + }, + "readonly": { + "status": "live", + "note": "renderer." + }, + "hidden": { + "status": "live", + "note": "renderer." + }, + "system": { + "status": "live", + "evidence": "packages/objectql/src/engine.ts" + }, + "sortable": { + "status": "live", + "note": "grid." + }, + "inlineEdit": { + "status": "live", + "note": "grid inline editing." + }, + "language": { + "status": "live", + "note": "code field — CodeField.tsx:13 (only language is live; theme/lineNumbers dead)." + }, + "step": { + "status": "live", + "note": "slider — SliderField.tsx (min/max/step live)." + }, + "group": { + "status": "live", + "note": "field group/layout (renderer)." + }, + "inlineHelpText": { + "status": "live", + "note": "display help (renderer)." + }, + "unique": { + "status": "live", + "evidence": "packages/plugins/driver-sql/src/sql-driver.ts:1853", + "note": "CAVEAT — DDL-only; NOT validated on the write path (violations surface as raw driver errors)." + }, + "precision": { + "status": "live", + "note": "CAVEAT — UI display formatting only; DDL never sizes (maps to float). (NumberField.tsx:16)." + }, + "scale": { + "status": "live", + "note": "CAVEAT — grid formatting only; DDL never sizes." + }, + "reference": { + "status": "live", + "evidence": "packages/objectql/src/engine.ts:1672", + "note": "CAVEAT — $expand/cascade/seed live; FK DDL reads reference_to (unmapped from reference)." + }, + "autonumberFormat": { + "status": "live", + "evidence": "packages/objectql/src/engine.ts:765", + "note": "CAVEAT — runtime sequence formatting live; UI AutoNumberField ignores it." + }, + "maxLength": { + "status": "live", + "evidence": "packages/objectql/src/validation/record-validator.ts:127", + "note": "CAVEAT — server validation honors camelCase, but the client form reads snake_case max_length." + }, + "minLength": { + "status": "live", + "evidence": "packages/objectql/src/validation/record-validator.ts:130", + "note": "CAVEAT — server camel; client form reads min_length." + }, + "referenceFilters": { + "status": "dead", + "evidence": "lookup dialog reads lookup_filters (LookupField.tsx:171) — entirely dead as authored (naming drift)" + }, + "maxRating": { + "status": "dead", + "evidence": "RatingField reads `max` (RatingField.tsx:13) — dead + redundant with max" + }, + "columnName": { + "status": "dead", + "evidence": "resolveColumnName (spec system-names.ts:182) has ZERO call sites; SQL driver hardcodes column = field key", + "note": "DANGEROUS — advertises custom physical columns the driver never honors." + }, + "searchable": { + "status": "dead", + "evidence": "field-level — no DDL/query consumer; search is object-level / view searchableFields" + }, + "index": { + "status": "dead", + "evidence": "field-level — driver reads object indexes[] (sql-driver.ts:1252); field bool unused" + }, + "externalId": { + "status": "dead", + "evidence": "field-level — upsert keys off dataset-level externalId (seed-loader.ts:175)" + }, + "currencyConfig": { + "status": "dead", + "evidence": "nested config — renderers read flat currency/precision (CurrencyField.tsx:40); no currencyConfig consumer" + }, + "vectorConfig": { + "status": "dead", + "evidence": "nested config — renderers read flat dimensions; no consumer, no vector-index DDL" + }, + "fileAttachmentConfig": { + "status": "dead", + "evidence": "nested config — renderers read flat multiple/accept/maxSize (FileField.tsx:16); no size/type/virus enforcement in write path" + }, + "encryptionConfig": { + "status": "dead", + "evidence": "no consumer; the only at-rest protection is the separate type:'secret' channel (engine.ts encryptSecretFields)" + }, + "maskingRule": { + "status": "dead", + "evidence": "no consumer" + }, + "auditTrail": { + "status": "dead", + "evidence": "no consumer (aspirational governance)" + }, + "dataQuality": { + "status": "dead", + "evidence": "no consumer (aspirational governance)" + }, + "cached": { + "status": "dead", + "evidence": "no consumer" + }, + "dependencies": { + "status": "dead", + "evidence": "no consumer" + }, + "trackFeedHistory": { + "status": "dead", + "evidence": "no consumer" + }, + "caseSensitive": { + "status": "dead", + "evidence": "no consumer" + }, + "writeRequiresMasterRead": { + "status": "dead", + "evidence": "no consumer (governance)" + }, + "inlineTitle": { + "status": "dead", + "evidence": "master-detail explicit override — auto-derivation works (deriveMasterDetail.ts); overrides unread" + }, + "inlineColumns": { + "status": "dead", + "evidence": "master-detail explicit override — unread" + }, + "inlineAmountField": { + "status": "dead", + "evidence": "master-detail explicit override — unread" + }, + "relatedList": { + "status": "dead", + "evidence": "detail-page related lists come from view metadata, not FieldSchema" + }, + "relatedListTitle": { + "status": "dead", + "evidence": "master-detail explicit override — unread" + }, + "relatedListColumns": { + "status": "dead", + "evidence": "master-detail explicit override — unread" + } } } diff --git a/packages/spec/src/data/field.test.ts b/packages/spec/src/data/field.test.ts index 7290db6207..94c31deb90 100644 --- a/packages/spec/src/data/field.test.ts +++ b/packages/spec/src/data/field.test.ts @@ -532,24 +532,19 @@ describe('Field Factory Helpers', () => { it('should create location field with config', () => { const locationField = Field.location({ label: 'Office Location', - displayMap: true, - allowGeocoding: true, }); - + expect(locationField.type).toBe('location'); expect(locationField.label).toBe('Office Location'); - expect(locationField.displayMap).toBe(true); }); it('should create address field with format', () => { const addressField = Field.address({ label: 'Mailing Address', - addressFormat: 'us', required: true, }); - + expect(addressField.type).toBe('address'); - expect(addressField.addressFormat).toBe('us'); expect(addressField.required).toBe(true); }); @@ -566,37 +561,28 @@ describe('Field Factory Helpers', () => { it('should create code field with language', () => { const codeField = Field.code('javascript', { label: 'Script', - lineNumbers: true, - theme: 'dark', }); - + expect(codeField.type).toBe('code'); expect(codeField.language).toBe('javascript'); - expect(codeField.lineNumbers).toBe(true); }); it('should create color field with format', () => { const colorField = Field.color({ label: 'Brand Color', - colorFormat: 'hex', - allowAlpha: false, - presetColors: ['#FF0000', '#00FF00', '#0000FF'], }); - + expect(colorField.type).toBe('color'); - expect(colorField.colorFormat).toBe('hex'); - expect(colorField.presetColors).toHaveLength(3); + expect(colorField.label).toBe('Brand Color'); }); it('should create rating field with max rating', () => { const ratingField = Field.rating(10, { label: 'Customer Satisfaction', - allowHalf: true, }); - + expect(ratingField.type).toBe('rating'); expect(ratingField.maxRating).toBe(10); - expect(ratingField.allowHalf).toBe(true); }); it('should create signature field', () => { @@ -616,34 +602,22 @@ describe('Field Factory Helpers', () => { max: 100, step: 5, defaultValue: 50, - showValue: true, - marks: { '0': 'Silent', '50': 'Medium', '100': 'Loud' }, }); - + expect(sliderField.type).toBe('slider'); expect(sliderField.label).toBe('Volume'); expect(sliderField.min).toBe(0); expect(sliderField.max).toBe(100); expect(sliderField.step).toBe(5); - expect(sliderField.showValue).toBe(true); - expect(sliderField.marks).toEqual({ '0': 'Silent', '50': 'Medium', '100': 'Loud' }); }); it('should create qrcode field with barcode format', () => { const qrcodeField = Field.qrcode({ label: 'Product Barcode', - barcodeFormat: 'qr', - qrErrorCorrection: 'M', - displayValue: true, - allowScanning: true, }); - + expect(qrcodeField.type).toBe('qrcode'); expect(qrcodeField.label).toBe('Product Barcode'); - expect(qrcodeField.barcodeFormat).toBe('qr'); - expect(qrcodeField.qrErrorCorrection).toBe('M'); - expect(qrcodeField.displayValue).toBe(true); - expect(qrcodeField.allowScanning).toBe(true); }); }); diff --git a/packages/spec/src/data/field.zod.ts b/packages/spec/src/data/field.zod.ts index cdbc276b7d..2aa746308e 100644 --- a/packages/spec/src/data/field.zod.ts +++ b/packages/spec/src/data/field.zod.ts @@ -458,39 +458,15 @@ export const FieldSchema = lazySchema(() => z.object({ }).optional().describe('Roll-up summary definition. The engine recomputes the value when child records are inserted/updated/deleted.'), /** Enhanced Field Type Configurations */ - // Code field config + // Pruned 2026-06 — per-type *display* knobs that were dead in both layers (no + // runtime reader; renderers ignore them). See + // docs/audits/2026-06-dead-surface-disposition-plan.md (P2 field prune): code + // theme/lineNumbers, rating allowHalf, location displayMap/allowGeocoding, address + // addressFormat, color colorFormat/allowAlpha/presetColors, slider showValue/marks, + // barcode/qr barcodeFormat/qrErrorCorrection/displayValue/allowScanning. language: z.string().optional().describe('Programming language for syntax highlighting (e.g., javascript, python, sql)'), - theme: z.string().optional().describe('Code editor theme (e.g., dark, light, monokai)'), - lineNumbers: z.boolean().optional().describe('Show line numbers in code editor'), - - // Rating field config maxRating: z.number().optional().describe('Maximum rating value (default: 5)'), - allowHalf: z.boolean().optional().describe('Allow half-star ratings'), - - // Location field config - displayMap: z.boolean().optional().describe('Display map widget for location field'), - allowGeocoding: z.boolean().optional().describe('Allow address-to-coordinate conversion'), - - // Address field config - addressFormat: z.enum(['us', 'uk', 'international']).optional().describe('Address format template'), - - // Color field config - colorFormat: z.enum(['hex', 'rgb', 'rgba', 'hsl']).optional().describe('Color value format'), - allowAlpha: z.boolean().optional().describe('Allow transparency/alpha channel'), - presetColors: z.array(z.string()).optional().describe('Preset color options'), - - // Slider field config step: z.number().optional().describe('Step increment for slider (default: 1)'), - showValue: z.boolean().optional().describe('Display current value on slider'), - marks: z.record(z.string(), z.string()).optional().describe('Custom marks/labels at specific values (e.g., {0: "Low", 50: "Medium", 100: "High"})'), - - // QR Code / Barcode field config - // Note: qrErrorCorrection is only applicable when barcodeFormat='qr' - // Runtime validation should enforce this constraint - barcodeFormat: z.enum(['qr', 'ean13', 'ean8', 'code128', 'code39', 'upca', 'upce']).optional().describe('Barcode format type'), - qrErrorCorrection: z.enum(['L', 'M', 'Q', 'H']).optional().describe('QR code error correction level (L=7%, M=15%, Q=25%, H=30%). Only applicable when barcodeFormat is "qr"'), - displayValue: z.boolean().optional().describe('Display human-readable value below barcode/QR code'), - allowScanning: z.boolean().optional().describe('Enable camera scanning for barcode/QR code input'), // Currency field config currencyConfig: CurrencyConfigSchema.optional().describe('Configuration for currency field type'),