From cb91073f1fd97995def1f816ba44bd121e4b0c71 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 17 Jul 2026 10:00:58 +0000 Subject: [PATCH] refactor(spec-bridge): derive list-view bridge types from @objectstack/spec/ui (#2231 follow-up) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Retires the third hand-written copy of the ListView shape: the local ListViewSpec/ListColumn interfaces in the SpecBridge's list-view bridge are replaced by Partial and ListColumn imported from @objectstack/spec/ui, so the bridge input can no longer drift from the protocol. Behavior fix surfaced by the real types: spec `columns` is string[] | ListColumn[], but the old local interface only admitted ListColumn[] — a bare field-name column would have mapped to a broken { accessorKey: undefined }. String columns now map to a default column ({ accessorKey: field, header: field }). Verified: full build 43/43, @object-ui/react type-check 0 errors, react suite 310 tests green. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01CBAuAYP5j1j5MguKYM84dW --- .changeset/spec-bridge-listview-types.md | 15 ++++ .../src/spec-bridge/bridges/list-view.ts | 79 ++++--------------- 2 files changed, 29 insertions(+), 65 deletions(-) create mode 100644 .changeset/spec-bridge-listview-types.md diff --git a/.changeset/spec-bridge-listview-types.md b/.changeset/spec-bridge-listview-types.md new file mode 100644 index 000000000..fe60e9d08 --- /dev/null +++ b/.changeset/spec-bridge-listview-types.md @@ -0,0 +1,15 @@ +--- +"@object-ui/react": patch +--- + +refactor(spec-bridge): retire the hand-written `ListViewSpec`/`ListColumn` mirrors in the list-view bridge (#2231 follow-up) + +The SpecBridge's list-view bridge kept a third hand-written copy of the ListView shape +(after the zod schema and the TS interface unified in the previous #2231 PR). It now +derives its input type from `@objectstack/spec/ui` (`Partial`, spec `ListColumn`), +so the bridge can no longer drift from the protocol. + +Behavior fix surfaced by the real types: spec `columns` is `string[] | ListColumn[]`, but +the old local interface only admitted `ListColumn[]` — a bare field-name column would have +produced a broken `{ accessorKey: undefined }` mapping. String columns now map to a default +column (`{ accessorKey: field, header: field }`). diff --git a/packages/react/src/spec-bridge/bridges/list-view.ts b/packages/react/src/spec-bridge/bridges/list-view.ts index 111d2dbdf..69a58a96e 100644 --- a/packages/react/src/spec-bridge/bridges/list-view.ts +++ b/packages/react/src/spec-bridge/bridges/list-view.ts @@ -8,74 +8,23 @@ import type { SchemaNode } from '@object-ui/core'; import type { BridgeContext, BridgeFn } from '../types'; -import type { ListViewGalleryConfig, ListViewTimelineConfig } from '@object-ui/types'; +import type { ListView, ListColumn } from '@objectstack/spec/ui'; -interface ListColumn { - field: string; - label?: string; - width?: number; - align?: string; - hidden?: boolean; - sortable?: boolean; - resizable?: boolean; - wrap?: boolean; - type?: string; - pinned?: string; - summary?: any; - link?: any; - action?: any; -} +/** + * Bridge input: the spec-canonical ListView (#2231 — the former hand-written + * `ListViewSpec`/`ListColumn` mirrors are retired; the shape now derives from + * `@objectstack/spec/ui` and can no longer drift). Relaxed to `Partial` because + * hosts routinely hand the bridge fragmentary view-configs (every field was + * optional in the old mirror too, including spec-required `columns`). + */ +type ListViewSpec = Partial; -interface ListViewSpec { - name?: string; - label?: string; - type?: string; - data?: any; - columns?: ListColumn[]; - filter?: any; - sort?: any; - searchableFields?: string[]; - filterableFields?: string[]; - selection?: any; - pagination?: any; - rowHeight?: string; - resizable?: boolean; - striped?: boolean; - bordered?: boolean; - grouping?: any; - rowColor?: any; - navigation?: any; - kanban?: any; - calendar?: any; - gantt?: any; - gallery?: ListViewGalleryConfig; - timeline?: ListViewTimelineConfig; - // P1.1 additions - rowActions?: string[]; - bulkActions?: string[]; - bulkActionDefs?: any[]; - virtualScroll?: boolean; - conditionalFormatting?: Array<{ condition: string; style: Record }>; - inlineEdit?: boolean; - exportOptions?: any; - emptyState?: { title?: string; message?: string; icon?: string }; - userActions?: any; - appearance?: any; - /** UX-only hint: collapse appearance/grouping cluster into a single popover. */ - compactToolbar?: boolean; - tabs?: any[]; - addRecord?: any; - showRecordCount?: boolean; - allowPrinting?: boolean; - // P1.6 i18n & ARIA - aria?: { ariaLabel?: string; ariaDescribedBy?: string; role?: string }; - sharing?: any; - hiddenFields?: string[]; - fieldOrder?: string[]; - description?: string; -} +function mapColumn(col: ListColumn | string): Record { + // Spec-legacy shorthand: a bare field name stands for a default column. + if (typeof col === 'string') { + return { accessorKey: col, header: col }; + } -function mapColumn(col: ListColumn): Record { const mapped: Record = { accessorKey: col.field, header: col.label ?? col.field,