From 21ea083b064c525603afd9f22e045bb39d04b83c Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 25 Aug 2026 22:05:41 +0000 Subject: [PATCH] chore(app-shell): delete the dead object-fields-bridge module `previews/object-fields-bridge.ts` exported `bridgeFromDraft`, `commitToDraft` and `FieldsBridgeResult` and had zero importers. Re-measured on the merged base rather than inherited from the filing: the same command shape that returns 109 hits across 29 files for the live sibling `object-fields-io` returned 4 hits for this module, all of them prose in comments and none an import or a call. Deleting the module alone would have swapped dead code for false documentation: three comments cited the bridge as a live corroborating source. The two that named it as the consumer deriving an editable-subset check from `DESIGNER_FIELD_TYPES` now name `MetadataFieldsPage`, which does exactly that with the same idiom and the same objectui#3017 anchor. The third cited the bridge's `richtext` -> `html` mapping as one of three corroborations that `richtext` stores HTML; the other two are live and carry the point alone, so that clause is dropped rather than repointed. No behaviour changes. `@object-ui/app-shell` exports only `.` and `./styles.css`, so the module was not reachable as a deep import either. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_011SfZeFWrhGLHmfq61xbz4q --- .../6309-delete-object-fields-bridge.md | 28 +++ .../previews/object-fields-bridge.ts | 219 ------------------ .../richtext-cell-renderer-5452.test.tsx | 15 +- .../__tests__/designer-field-types.test.ts | 9 +- packages/types/src/designer.ts | 6 +- 5 files changed, 42 insertions(+), 235 deletions(-) create mode 100644 .changeset/6309-delete-object-fields-bridge.md delete mode 100644 packages/app-shell/src/views/metadata-admin/previews/object-fields-bridge.ts diff --git a/.changeset/6309-delete-object-fields-bridge.md b/.changeset/6309-delete-object-fields-bridge.md new file mode 100644 index 0000000000..18804b6669 --- /dev/null +++ b/.changeset/6309-delete-object-fields-bridge.md @@ -0,0 +1,28 @@ +--- +'@object-ui/app-shell': patch +'@object-ui/types': patch +'@object-ui/fields': patch +--- + +Delete the dead `metadata-admin/previews/object-fields-bridge.ts` module, and the three +prose references that still described it as wired. + +The module exported `bridgeFromDraft`, `commitToDraft` and `FieldsBridgeResult` and had +**zero importers** — re-measured on the merged base, not inherited from the filing. Nothing +in the repository could reach it either: `@object-ui/app-shell`'s `exports` map declares +only `.` and `./styles.css`, so the file was not addressable as a deep import even from +outside the workspace. + +Removing it is not the whole change. Three comments — in `types/src/designer.ts`, `types`' +`designer-field-types.test.ts` (twice) and `fields`' `richtext-cell-renderer-5452.test.tsx` +— cited the bridge as a live corroborating source. Left behind, they would have swapped +dead code for false documentation: three in-repo pointers telling a future reader that this +bridge mediates between the framework field record and `FieldDesigner`, and nothing telling +them it is unreachable. The two that named it as the consumer deriving an editable-subset +check from `DESIGNER_FIELD_TYPES` now name `MetadataFieldsPage`, which does exactly that +with the same idiom and the same `objectui#3017` anchor. The third cited the bridge's +`richtext` → `html` mapping as one of three corroborations that `richtext` stores HTML; the +other two (the showcase seed and the field-type decision tree) are live and carry the point +on their own, so that clause is dropped rather than repointed. + +No behaviour changes: nothing imported the module, so there is nothing to migrate. diff --git a/packages/app-shell/src/views/metadata-admin/previews/object-fields-bridge.ts b/packages/app-shell/src/views/metadata-admin/previews/object-fields-bridge.ts deleted file mode 100644 index 436cf37791..0000000000 --- a/packages/app-shell/src/views/metadata-admin/previews/object-fields-bridge.ts +++ /dev/null @@ -1,219 +0,0 @@ -// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license. - -/** - * Bridge between framework metadata `Object.fields` (a record of rich - * 48-type field definitions) and the lighter `DesignerFieldDefinition[]` - * shape consumed by `@object-ui/plugin-designer`'s `FieldDesigner`. - * - * Why this exists: the designer ships with a curated subset (27 types, - * no `master_detail`, no `tree`, no `multiselect`, etc.). Letting it - * own the full draft would silently drop properties on round-trip. So - * the bridge: - * - * - converts the supported subset for editing, - * - quarantines unknown types into a "preserved" bucket keyed by - * field name, and - * - reassembles the full record on commit, putting preserved entries - * back in their original order/shape so nothing the user can't see - * is destroyed. - */ - -import { DESIGNER_FIELD_TYPES } from '@object-ui/types'; -import type { - DesignerFieldDefinition, - DesignerFieldType, -} from '@object-ui/types'; - -/** - * Set of types the designer can edit losslessly — derived from the canonical - * `DESIGNER_FIELD_TYPES` vocabulary rather than restated (objectui#3017), so - * this bridge and `FieldDesigner`'s palette cannot drift by construction. - */ -const DESIGNER_TYPES: ReadonlySet = new Set(DESIGNER_FIELD_TYPES); - -interface FrameworkFieldDef { - type?: string; - label?: string; - required?: boolean; - unique?: boolean; - readonly?: boolean; - hidden?: boolean; - description?: string; - default?: unknown; - placeholder?: string; - options?: Array<{ label?: string; value: string; color?: string }>; - reference?: string; - // No declared `formula` (objectui#6043) — nothing reads or writes it any - // more. A legacy def that still carries one reaches this type through the - // index signature below, and a QUARANTINED field round-trips verbatim. - group?: string; - [k: string]: unknown; -} - -export interface FieldsBridgeResult { - /** Editable subset shaped for FieldDesigner. */ - designerFields: DesignerFieldDefinition[]; - /** - * Preserves the original framework definitions for fields the - * designer can't edit losslessly. Keyed by field name. Round-tripped - * verbatim on commit. - */ - preserved: Map; - /** - * Captured input shape (`'record'` or `'array'`) so we commit back - * in the same shape we received. - */ - inputShape: 'record' | 'array'; - /** - * Ordered list of all field names as they appeared on input; the - * commit function uses it to preserve user-visible ordering. - */ - originalOrder: string[]; -} - -/** Try to map a framework field type to a designer type; null if unsupported. */ -function mapFrameworkToDesignerType(t: string | undefined): DesignerFieldType | null { - if (!t) return null; - if (DESIGNER_TYPES.has(t as DesignerFieldType)) return t as DesignerFieldType; - // Best-effort fallbacks for the most common "close cousin" types. - switch (t) { - case 'richtext': - return 'html'; - case 'toggle': - return 'boolean'; - case 'multiselect': - case 'checkboxes': - case 'radio': - return 'select'; - case 'master_detail': - case 'tree': - return 'lookup'; - default: - return null; - } -} - -export function bridgeFromDraft(fieldsInput: unknown): FieldsBridgeResult { - const preserved = new Map(); - const designerFields: DesignerFieldDefinition[] = []; - const originalOrder: string[] = []; - - if (!fieldsInput || typeof fieldsInput !== 'object') { - return { designerFields, preserved, inputShape: 'record', originalOrder }; - } - - const isArray = Array.isArray(fieldsInput); - const inputShape: 'record' | 'array' = isArray ? 'array' : 'record'; - - const entries: Array<[string, FrameworkFieldDef]> = isArray - ? (fieldsInput as FrameworkFieldDef[]).map((def, i) => [ - String(def?.name ?? `field_${i + 1}`), - def, - ]) - : Object.entries(fieldsInput as Record); - - for (const [name, def] of entries) { - originalOrder.push(name); - const mapped = mapFrameworkToDesignerType(def?.type); - if (mapped === null) { - // Quarantine — preserve as-is, do not surface in the designer. - preserved.set(name, def); - continue; - } - designerFields.push({ - id: name, // FieldDesigner uses id as React key; using `name` is stable across edits. - name, - label: String(def?.label ?? name), - type: mapped, - required: !!def?.required, - unique: !!def?.unique, - readonly: !!def?.readonly, - hidden: !!def?.hidden, - description: typeof def?.description === 'string' ? def.description : undefined, - defaultValue: def?.default, - placeholder: typeof def?.placeholder === 'string' ? def.placeholder : undefined, - group: typeof def?.group === 'string' ? def.group : undefined, - options: Array.isArray(def?.options) - ? def!.options!.map((o) => ({ - label: String(o.label ?? o.value), - value: String(o.value), - color: o.color, - })) - : undefined, - referenceTo: typeof def?.reference === 'string' ? def.reference : undefined, - }); - } - - return { designerFields, preserved, inputShape, originalOrder }; -} - -/** - * Reassemble the framework-shape fields record from designer output. - * Preserved fields are spliced back in their original order. Newly - * added fields land at the end. - */ -export function commitToDraft( - designerFields: DesignerFieldDefinition[], - prev: FieldsBridgeResult, -): Record | FrameworkFieldDef[] { - const designerByName = new Map(); - for (const f of designerFields) designerByName.set(f.name, f); - - const writeName = (name: string, target: Record) => { - const preserved = prev.preserved.get(name); - if (preserved) { - target[name] = preserved; - return; - } - const f = designerByName.get(name); - if (f) { - target[name] = serializeDesignerField(f); - } - }; - - const result: Record = {}; - - // First emit fields in their original order to preserve user-visible - // ordering and keep diff noise low for unchanged drafts. - for (const name of prev.originalOrder) { - if (designerByName.has(name) || prev.preserved.has(name)) { - writeName(name, result); - } - } - // Then append any newly added designer fields (not in originalOrder). - for (const f of designerFields) { - if (!prev.originalOrder.includes(f.name)) { - result[f.name] = serializeDesignerField(f); - } - } - - if (prev.inputShape === 'array') { - return Object.entries(result).map(([name, def]) => ({ name, ...def })); - } - return result; -} - -function serializeDesignerField(f: DesignerFieldDefinition): FrameworkFieldDef { - const out: FrameworkFieldDef = { - type: f.type, - label: f.label, - }; - if (f.required) out.required = true; - if (f.unique) out.unique = true; - if (f.readonly) out.readonly = true; - if (f.hidden) out.hidden = true; - if (f.description) out.description = f.description; - if (f.defaultValue !== undefined) out.default = f.defaultValue; - if (f.placeholder) out.placeholder = f.placeholder; - if (f.group) out.group = f.group; - if (f.options && f.options.length > 0) out.options = f.options; - if (f.referenceTo) out.reference = f.referenceTo; - // No `formula` (objectui#6043). This bridge was a THIRD emit site for the - // key, named by neither the card nor `check-designer-field-key-parity.mjs` — - // `FrameworkFieldDef` is not one of that gate's declared `PAYLOAD_SHAPES`, so - // the gate was green over it. `FieldSchema` refuses `formula` BY NAME, and it - // is not renamed to `expression` here for the same reason as everywhere else - // on this card: the schema accepts the key without parsing the CEL, so a - // rename ships an unevaluatable expression under a valid name. - return out; -} diff --git a/packages/fields/src/__tests__/richtext-cell-renderer-5452.test.tsx b/packages/fields/src/__tests__/richtext-cell-renderer-5452.test.tsx index 5749105a04..ac7b9a4662 100644 --- a/packages/fields/src/__tests__/richtext-cell-renderer-5452.test.tsx +++ b/packages/fields/src/__tests__/richtext-cell-renderer-5452.test.tsx @@ -9,15 +9,14 @@ /** * objectui#5452: a POPULATED `richtext` field rendered as a completely empty * cell. `richtext` stores HTML — that is what the showcase seed carries - * (`examples/app-showcase/src/data/seed/index.ts`, `f_richtext`), what + * (`examples/app-showcase/src/data/seed/index.ts`, `f_richtext`) and what * `content/docs/data-modeling/field-type-decision-tree.mdx` documents - * ("Formatted content with HTML/WYSIWYG"), and what this repo's own designer - * bridge already assumes (`object-fields-bridge.ts` maps `richtext` to the - * designer's `html` type). The display registry nevertheless dispatched it to - * `MarkdownCellRenderer`, whose sanitizing GFM pipeline drops raw HTML — and - * since a richtext value is ENTIRELY HTML, everything was dropped and the cell - * came out blank. Failure direction is the bad one: no error, no fallback, no - * console warning, so a populated field reads as an empty field. + * ("Formatted content with HTML/WYSIWYG"). The display registry nevertheless + * dispatched it to `MarkdownCellRenderer`, whose sanitizing GFM pipeline drops + * raw HTML — and since a richtext value is ENTIRELY HTML, everything was + * dropped and the cell came out blank. Failure direction is the bad one: no + * error, no fallback, no console warning, so a populated field reads as an + * empty field. * * Two things are pinned here, and they pull in opposite directions on purpose: * diff --git a/packages/types/src/__tests__/designer-field-types.test.ts b/packages/types/src/__tests__/designer-field-types.test.ts index 4f6f4f1791..203c696900 100644 --- a/packages/types/src/__tests__/designer-field-types.test.ts +++ b/packages/types/src/__tests__/designer-field-types.test.ts @@ -11,8 +11,8 @@ * * `DesignerFieldType` is derived from this array, `FieldDesigner`'s * `FIELD_TYPE_META` must cover it (total `Record`, so tsc gates that side), - * and app-shell's `object-fields-bridge` builds its editable-subset check - * from it. The inventory below is a deliberate second statement of the list: + * and `MetadataFieldsPage` builds its editable-subset check from it. The + * inventory below is a deliberate second statement of the list: * growing or shrinking the vocabulary is a two-file edit by design, so it * cannot happen as a drive-by — the failure message routes the editor to the * consumers that must be reviewed alongside it. @@ -36,9 +36,8 @@ describe('DESIGNER_FIELD_TYPES (canonical Field Designer vocabulary)', () => { it('matches the checked-in inventory — vocabulary edits must be deliberate', () => { // If this fails you changed the designer's type vocabulary. That is fine, // but it is never only a types change: FieldDesigner needs presentation - // (FIELD_TYPE_META + FIELD_TYPE_CATEGORIES), the object-fields-bridge - // round-trip should be re-checked for the new/removed type, and this - // inventory re-pinned to acknowledge the review happened. + // (FIELD_TYPE_META + FIELD_TYPE_CATEGORIES), and this inventory must be + // re-pinned to acknowledge the review happened. expect([...DESIGNER_FIELD_TYPES].sort()).toEqual([...INVENTORY]); }); }); diff --git a/packages/types/src/designer.ts b/packages/types/src/designer.ts index 0b79130667..3028e201f4 100644 --- a/packages/types/src/designer.ts +++ b/packages/types/src/designer.ts @@ -711,9 +711,9 @@ export interface ObjectManagerSchema extends BaseSchema { * Single runtime source for every surface that enumerates the designer's * vocabulary: `FieldDesigner` renders its palette in exactly this order (its * `FIELD_TYPE_META` is a `Record`, so adding a member - * here without presentation is a compile error), and app-shell's - * `object-fields-bridge` derives its editable-subset check from it instead of - * restating the list (objectui#3017). + * here without presentation is a compile error), and `MetadataFieldsPage` + * derives its editable-subset check from it instead of restating the list + * (objectui#3017). */ export const DESIGNER_FIELD_TYPES = [ 'text',