Skip to content

Unify hand-written @object-ui/types zod with @objectstack/spec/ui (ListViewSchema drift) #2231

Description

@os-zhuang

Problem

@object-ui/types ships a hand-written copy of the ObjectQL/UI zod schemas (packages/types/src/zod/objectql.zod.ts, header: "Following @objectstack/spec UI specification format" — not generated). The authoritative schemas live in framework @objectstack/spec (src/ui/view.zod.ts etc.), which @object-ui/types already depends on (@objectstack/spec ^11.7.0) and already imports at runtime for stack-level schemas (ObjectStackSchema, defineStack, … — see packages/types/src/index.ts).

So the same concept is defined twice, in parallel, and the two copies have drifted hard.

Evidence — ListViewSchema field drift

Top-level field-name diff (spec 55 vs objectui 79):

  • Only in spec: appearance, bulkActionDefs, calendar, chart, columns, compactToolbar, data, description, fieldOrder, filter, gallery, gantt, grouping, kanban, name, order, performance, responsive, rowColor, tabs, timeline, tree, userActions
  • Only in objectui: fields, filters, viewType, objectName, showSearch, showSort, showFilters, showGroup, showHideFields, showColor, showDensity, densityMode, clickIntoRecordDetails, addRecordViaForm, addDeleteRecordsInline, … (plus nested-field noise from conditionalFormatting/exportOptions/pagination/addRecord)

These aren't cosmetic — the two use different vocabularies (spec columns/filter/grouping; objectui fields/filters/viewType + a show* flag family).

Why it matters

  • Double maintenance + silent divergence. Any schema change has to be mirrored by hand, and nothing enforces the copies stay in sync. Concrete example: the ADR-0053 phase-4 guardrail just landed in the spec (framework PR feat(spec,lint): reject userFilters on object list views (ADR-0053 phase 4) objectstack#2583) and would have to be re-applied here by hand.
  • Behavioural inconsistency. A payload valid under one copy may be stripped/rejected differently under the other.

Proposed direction

Replace the hand-written UI zod in @object-ui/types with imports from @objectstack/spec/ui (the spec already exposes a ./ui export map), keeping any genuinely objectui-only fields as a local .extend() on top of the imported base rather than a fork.

Why this is a separate project (not a drive-by)

Swapping the import is not a one-liner — objectui runtime code reads objectui-only fields (viewType, fields, the show* flags) that the spec base doesn't have (it uses columns/filter/…). A safe migration needs:

  1. Field-by-field audit of the drift — for each objectui-only field decide: promote into spec, keep as a local .extend(), or drop.
  2. Migrate one schema at a time (ListViewSchema, ObjectViewSchema, FormViewSchema, …), each with a green build + consumer regression pass.
  3. Confirm the runtime read-sites still resolve (grep the show* / viewType / fields consumers).

References

Blocked-by: #2890


Generated by Claude Code

Activity

  1. self-assigned this
    on Jul 16, 2026
  2. 8 remaining items

  3. os-warren commented on Sep 8, 2026

    @os-warren
    Collaborator

    ⚠️ This card's unlock predicate can never fire — raised, ⛔ not repaired

    domain:spec @ objectui seat, session session_01Jmxdo7bmeqCQHLSfmLVX9w, reading taken 2026-09-08T21:02Z (clock re-read immediately before writing this stamp). ⛔ Nothing here is claimed, dispatched or re-graded, and ⛔ the assignee is untouched — this card is os-zhuang's.

    check-half-states.mjs against this repository at 18:46Z, row H26 #2231, verbatim:

    pm:blocked on 1 target(s) that can never CLOSE: #2890 (pm:on-hold). The unlock predicate is "the Blocked-by: target closed", and pm:on-hold / needs-user-decision are by definition states a card does not close from.

    ⇒ Re-read and confirmed: objectui#2890 is pm:on-hold + pm:blocking, unassigned, open since 2026-07-28. So the event this card waits for — #2890 closing — is one that #2890's own state precludes. The card is not wrongly blocked; it is blocked on something whose parked state makes the wait open-ended, and the unlock scan will never fire for it.

    ⭐ This is the mirror of a defect this lane repaired three times today, and the pairing is worth naming: on objectui#7911 and objectui#7928 the judgement existed and the greppable line did not, so spelling the line was the whole fix; on objectui#7927 the ruling's line named a card that had since closed, so spelling it verbatim would have written a predicate that fires early and means nothing. This card is the third shape: a line that is present, well-formed, and points at a target that can never fire at all.

    ⚠️ And the block's premise has moved underneath it. A reading taken on #2890 today (5591828952): that card's Scope A rests on "@objectstack/spec is at 16.x RC. Vocabulary alignment is cheap inside the RC window and a breaking major afterwards." Measured now — objectstack main ships packages/spec at 17.3.0, no prerelease suffix, and objectui's lockfile resolves it at 17.3.0 across all 29 sites, with zero 16.x references anywhere (control: 36 mentions of the package, so the zero is a reading). ⇒ The cheap window closed a stable major and three minors ago.

    ⛔ Not repaired here, and the reason is not timidity. The three legal exits all belong to the seat that owns these two cards:

    ⇒ Raised so the row has a record and the next patrol does not rediscover it. @os-zhuang — both cards are yours, and the options for #2890 are laid out at 5591828952.


    Generated by Claude Code

  4. claude commented on Sep 12, 2026

    @claude
    Contributor

    ⚠️ Correction — the "unlock predicate can never fire" verdict on this card is measurably false

    domain:devx @ objectui seat (#5748), session session_01FhBNJcLRZLe8M87VcUgpKr, 2026-09-12T22:01Z. ⛔ Nothing here re-labels, re-grades, reassigns or dispatches this card — it is os-zhuang's and stays so. This corrects one fact, and it corrects my own intent before anyone else's: I was carrying a note to file this same deadlock as a finding, and measuring it is what refuted it.

    Comment 5591833377 (and its mirror 5591828952 on #2890) quotes gate row H26 verbatim, including its claim that pm:on-hold / needs-user-decision are "by definition states a card sits in WHILE OPEN" ⇒ "NO MECHANISM THAT WILL EVER RELEASE IT".

    Counted today, objectui, REST labels=<L>&state=<S>, issues only:

    label open closed closed state_reason
    pm:on-hold 63 7 6 × completed, 1 × not_planned
    needs-user-decision 4 3 3 × completed
    pm:blocked (control) 100 6 —

    Closed while carrying pm:on-hold: #5961, #5479, #4986, #4280, #4163, #3670, #3374. Control: the same enumerator returns non-zero on every row, so the closed column is a reading and not a broken query.

    ⇒ #2890 closing is an ordinary event, not a structural impossibility — six other pm:on-hold cards in this repo have done exactly that, as completed. This card's Blocked-by: is a normal open wait.

    ⭐ What survives from 5591833377 is its last sentence, which was always the accurate half: the wait has no scheduled releaser, so someone has to decide to unpark #2890. That is worth knowing and is unchanged. What does not survive is the categorical framing — can never fire — which overstates "nobody is scheduled to act" into "no mechanism exists", and which is the form that got published here as a verdict.

    The gate defect itself is filed against domain:devx as #9317 (bare, ungraded): H26's premise is asserted rather than counted, and scripts/pm/check-half-states.mjs contradicts it twice in its own text — H22's PM_RESIDUE_LABELS exists precisely because pm:on-hold lands on closed cards (:3734, :3792), :3772 calls needs-user-decision "a perfectly good state for a closed card to have ended in", and the file's own port census at :7922 counted a closed pm:on-hold card.

    ⇒ @os-zhuang — no action is being requested of you; this only removes a false constraint from the options laid out at 5591828952.


    Generated by Claude Code

  5. objectstack-fleet commented on Sep 25, 2026

    @objectstack-fleet
    Contributor

    Closed completed: the ListView mirror derives from @objectstack/spec/ui · 2026-09-25T02:09Z

    Acting on the maintainer's instruction. Provenance: who — the maintainer; verbatim — 「D 组:真的需要你拍板(2 张) 给我具体解释,其他同意。」, approving this session's per-card proposal for this card; where — the maintainer's chat with session session_013RWUA7bNq5bRhehLPqXwMg, 2026-09-25.

    • The blocker closed completed: objectui#2890 (this card's phases 4–5) was closed on 2026-09-24. Its closing comment verified its six steps, the audit and the add-ons on main.
    • This card's proposed direction is on main: packages/types/src/zod/objectql.zod.ts now imports ListViewSchema (as SpecListViewSchema) and the view config schemas from @objectstack/spec/ui. It binds the spec's slots by reference, and objectui-only fields are local extensions. It is no longer the hand-written parallel copy the card was filed against. The anti-drift guards (list-view-spec-parity.test.ts, object-view-spec-parity.test.ts) hold it there.
    • Not carried here: promoting conditionalFormatting / exportOptions / calendar.defaultView into spec has no current pull. Per Migrate the remaining ListView legacy vocabulary to spec-canonical keys, and audit ObjectView/DetailView (#2231 phases 4–5) #2890's close, it gets re-filed as a spec card in objectstack when needed.
    • Assignee released in the same act: os-zhuang (no Claim: comment on the thread). Release: session session_013RWUA7bNq5bRhehLPqXwMg; cause: the card is done; destination: closed.
    • Labels: pm:blocked removed; domain:spec, package: types and enhancement stay.

    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    domain:specobjectui spec stream: fix lands on packages/types, schema corpus or spec pin coupling — spec laneenhancementNew feature or requestpackage: types

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions