Context
#2231 has been closing the @object-ui/types ↔ @objectstack/spec/ui fork in phases:
| phase |
PR |
what it closed |
| 1 |
#2622, #2636 |
ListViewSchema derived from the spec; the spec-bridge's third hand-written copy retired |
| 2 |
#2882 |
every remaining "Mirrors @objectstack/spec/ui X" sub-schema (ViewDataSchema, ListColumnSchema, SelectionConfigSchema, PaginationConfigSchema, HttpRequestSchema, the whole theme family) reduced to spec re-exports, guarded by a reference-identity test |
| 3 |
(in flight) |
the five per-view-type configs (kanban/calendar/gantt/gallery/timeline) derived from the spec, plus the two rendering bugs the fork had been hiding |
What remains is the part that was never a reference swap: objectui still uses a different vocabulary from the protocol for the same concepts at the ListView top level, and two view schemas have never been audited at all.
Scope A — the remaining legacy ListView vocabulary
ListViewSchema's drift guard (packages/types/src/__tests__/list-view-spec-parity.test.ts) sanctions ~50 local keys. Most are renames of a spec key, not genuine objectui concerns:
| objectui (legacy) |
spec (canonical) |
viewType |
type (the view kind; objectui's type is the component discriminator) |
fields |
columns |
filters (tuple/CEL array) |
filter (ViewFilterRuleSchema[]) |
showSearch / showSort / showFilters / showGroup / showHideFields / showColor / showDensity |
userActions |
densityMode |
rowHeight |
objectName |
data: { provider: 'object', object } |
aria: { label, describedBy, live } |
AriaPropsSchema (ariaLabel, ariaDescribedBy, role) |
sharing: { visibility, enabled, … } |
ViewSharingSchema ({ type, lockedBy }) |
conditionalFormatting, exportOptions |
the spec shapes are narrower |
Why this needs its own project. Unlike phases 1–3 this is not type-level. Read-sites consume the legacy names (packages/plugin-list/src/ListView.tsx, components/ViewSettingsPopover.tsx, packages/plugin-view/src/ObjectView.tsx, packages/app-shell/src/views/{ObjectDataPage,ObjectView,InterfaceListPage}.tsx, packages/react/src/hooks/useDensityMode.ts), and stored view metadata in user databases carries the legacy keys. A rename with no migration path silently invalidates or strips existing views.
Per AGENTS.md Commandment #0.1 the answer is not a renderer-side ?? dual-read. The sanctioned precedent is a normalizer at the contract, the way @objectstack/spec folds legacy filter-operator spellings on parse (VIEW_FILTER_OPERATOR_ALIASES + normalizeFilterOperator, documented there as "a migration bridge … may be dropped in a future major"). So each rename wants: spec-side preprocess → objectui read-sites moved to the canonical key → legacy key dropped from the sanctioned set.
Timing matters. @objectstack/spec is at 16.x RC. Vocabulary alignment is cheap inside the RC window and a breaking major afterwards.
Suggested order — one PR each, green build + consumer regression per step
fields → columns (pure alias, smallest read-site surface)
densityMode → rowHeight
- the
show* family → userActions
filters → filter
aria / sharing / conditionalFormatting / exportOptions → the spec sub-shapes
objectName → data.provider: 'object' (largest — touches every ObjectQL component)
Carried over from phase 3
- Drop the four deprecated aliases phase 3 kept for back-compat once stored metadata has been migrated:
kanban.groupField, kanban.cardFields, gallery.imageField, timeline.dateField.
- Promote
calendar.defaultView into @objectstack/spec — it is the one per-view-type field with no spec counterpart, so it is still a local extension.
Scope B — audit ObjectViewSchema / DetailViewSchema
Never touched by #2231. Both are still hand-written BaseSchema.extend({ … }):
ObjectViewSchema (packages/types/src/zod/objectql.zod.ts) — a composite (grid + form + toolbar flags). The nearest spec counterpart, ObjectListViewSchema (packages/spec/src/ui/view.zod.ts), is not 1:1.
DetailViewSchema (packages/types/src/zod/views.zod.ts) — nearest counterpart is spec FormViewSchema, also not 1:1.
The deliverable here is an audit, not a swap: field by field, decide promote-to-spec / keep-local / drop — the same triage that made the ListView derivation safe. Only then decide whether a derivation is even the right shape, or whether the spec should grow a genuine composite-view schema.
Also surfaced (small, independent)
useColumnSummary's computeAggregation (packages/plugin-grid/src/useColumnSummary.ts) implements only count / sum / avg / min / max. The spec's ColumnSummarySchema declares eleven functions, so count_empty, count_filled, count_unique, percent_empty, percent_filled and none currently compute null and render an empty footer — a declared ≠ enforced gap. Either implement the six or trim the spec enum.
Definition of done
- The sanctioned-local set in
list-view-spec-parity.test.ts contains only fields that are genuinely objectui renderer concerns (target: near-empty).
- No renderer reads a legacy key; whatever legacy acceptance remains lives in one documented normalizer, not scattered
?? fallbacks.
ObjectViewSchema / DetailViewSchema have a written field-level verdict.
Related
- objectstack#3761 promotes
ListColumn.prefix and the { type, field } summary form into the spec. When it releases, @object-ui/types drops its last ListColumnSchema.extend() and that schema becomes a plain re-export.
Context
#2231 has been closing the
@object-ui/types↔@objectstack/spec/uifork in phases:ListViewSchemaderived from the spec; the spec-bridge's third hand-written copy retiredViewDataSchema,ListColumnSchema,SelectionConfigSchema,PaginationConfigSchema,HttpRequestSchema, the whole theme family) reduced to spec re-exports, guarded by a reference-identity testkanban/calendar/gantt/gallery/timeline) derived from the spec, plus the two rendering bugs the fork had been hidingWhat remains is the part that was never a reference swap: objectui still uses a different vocabulary from the protocol for the same concepts at the ListView top level, and two view schemas have never been audited at all.
Scope A — the remaining legacy ListView vocabulary
ListViewSchema's drift guard (packages/types/src/__tests__/list-view-spec-parity.test.ts) sanctions ~50 local keys. Most are renames of a spec key, not genuine objectui concerns:viewTypetype(the view kind; objectui'stypeis the component discriminator)fieldscolumnsfilters(tuple/CEL array)filter(ViewFilterRuleSchema[])showSearch/showSort/showFilters/showGroup/showHideFields/showColor/showDensityuserActionsdensityModerowHeightobjectNamedata: { provider: 'object', object }aria: { label, describedBy, live }AriaPropsSchema(ariaLabel,ariaDescribedBy,role)sharing: { visibility, enabled, … }ViewSharingSchema({ type, lockedBy })conditionalFormatting,exportOptionsWhy this needs its own project. Unlike phases 1–3 this is not type-level. Read-sites consume the legacy names (
packages/plugin-list/src/ListView.tsx,components/ViewSettingsPopover.tsx,packages/plugin-view/src/ObjectView.tsx,packages/app-shell/src/views/{ObjectDataPage,ObjectView,InterfaceListPage}.tsx,packages/react/src/hooks/useDensityMode.ts), and stored view metadata in user databases carries the legacy keys. A rename with no migration path silently invalidates or strips existing views.Per AGENTS.md Commandment #0.1 the answer is not a renderer-side
??dual-read. The sanctioned precedent is a normalizer at the contract, the way@objectstack/specfolds legacy filter-operator spellings on parse (VIEW_FILTER_OPERATOR_ALIASES+normalizeFilterOperator, documented there as "a migration bridge … may be dropped in a future major"). So each rename wants: spec-side preprocess → objectui read-sites moved to the canonical key → legacy key dropped from the sanctioned set.Timing matters.
@objectstack/specis at16.xRC. Vocabulary alignment is cheap inside the RC window and a breaking major afterwards.Suggested order — one PR each, green build + consumer regression per step
fields→columns(pure alias, smallest read-site surface)densityMode→rowHeightshow*family →userActionsfilters→filteraria/sharing/conditionalFormatting/exportOptions→ the spec sub-shapesobjectName→data.provider: 'object'(largest — touches every ObjectQL component)Carried over from phase 3
kanban.groupField,kanban.cardFields,gallery.imageField,timeline.dateField.calendar.defaultViewinto@objectstack/spec— it is the one per-view-type field with no spec counterpart, so it is still a local extension.Scope B — audit
ObjectViewSchema/DetailViewSchemaNever touched by #2231. Both are still hand-written
BaseSchema.extend({ … }):ObjectViewSchema(packages/types/src/zod/objectql.zod.ts) — a composite (grid + form + toolbar flags). The nearest spec counterpart,ObjectListViewSchema(packages/spec/src/ui/view.zod.ts), is not 1:1.DetailViewSchema(packages/types/src/zod/views.zod.ts) — nearest counterpart is specFormViewSchema, also not 1:1.The deliverable here is an audit, not a swap: field by field, decide promote-to-spec / keep-local / drop — the same triage that made the ListView derivation safe. Only then decide whether a derivation is even the right shape, or whether the spec should grow a genuine composite-view schema.
Also surfaced (small, independent)
useColumnSummary'scomputeAggregation(packages/plugin-grid/src/useColumnSummary.ts) implements onlycount/sum/avg/min/max. The spec'sColumnSummarySchemadeclares eleven functions, socount_empty,count_filled,count_unique,percent_empty,percent_filledandnonecurrently computenulland render an empty footer — a declared ≠ enforced gap. Either implement the six or trim the spec enum.Definition of done
list-view-spec-parity.test.tscontains only fields that are genuinely objectui renderer concerns (target: near-empty).??fallbacks.ObjectViewSchema/DetailViewSchemahave a written field-level verdict.Related
ListColumn.prefixand the{ type, field }summaryform into the spec. When it releases,@object-ui/typesdrops its lastListColumnSchema.extend()and that schema becomes a plain re-export.