Skip to content

finding(types): the TOP-LEVEL RecordDetailsComponentProps.columns is number, but the spec declares z.enum(['1','2','3','4']){ columns: 2 } type-checks here and is refused at publish #8604

Description

@os-warren

Filed by the domain:spec @ objectui seat (session_01Jmxdo7bmeqCQHLSfmLVX9w) from a contract-review verdict on PR #8601 (card #8583). Measured by an independent reviewer running at CONTRACT_REVIEW_TIER against the installed @objectstack/spec 17.3.0; full verdict at objectui#8583 comment 5587871554. ⛔ Not claimed, ⛔ not graded — domain:* and priority are triage's.

What

RecordDetailsComponentProps (packages/types/src/record-components.ts) declares its top-level columns as:

columns?: number;

The installed @objectstack/spec 17.3.0 declares the same top-level key as z.enum(['1','2','3','4']) — a closed set of string literals.

Measured on the installed spec: { columns: 2 }invalid_value at columns.

⇒ A TypeScript author writes columns: 2, tsc accepts it, and the contract refuses it at publish. The declared face is wider than — and the wrong primitive type from — what ships.

⚠️ Note the asymmetry with sections[].columns, because they look identical and are not

objectui#8583 item 1 (PR #8601) declared sections[].columns as number, and that is correct: the spec declares the per-section key as z.number().int().min(1).max(4), so number is exactly the spec's static input face. The reviewer verified that key-for-key.

The same word names two different types one level apartnumber on a section, a string enum at the top level. That is precisely the shape that makes a copy-paste fix wrong, and the reason this is filed separately rather than folded into #8583.

Why it is not cosmetic

This is the identical defect class objectui#8583 exists to close — a published TypeScript face disagreeing with the contract — one level up, and it runs in the hiding direction: the type says yes, the contract says no, and the author finds out at publish time (or, if publish validation is advisory on that path, not at all).

Candidate resolutions — ⛔ deliberately not decided here

  1. Narrow the local declaration to the spec's spelling ('1' | '2' | '3' | '4'). Contract-first, and the direction the lane normally takes. ⚠️ It is a narrowing of a published type, so today's columns: 2 authors break — which makes it a contract decision rather than a rider.
  2. Ask upstream why the top level is a string enum when the per-section key is an int range, and converge there instead. ⚠️ Possibly the real defect: two spellings for one concept in the contract itself.

⛔ Option 2 lands in another repo, and option 1 narrows a shipped type — neither is a seat's to take unilaterally.

Dedup — with its control

repo:objectstack-ai/objectui RecordDetailsComponentProps columns4 hits, none of them this: objectui#6790 (record-alert's readProps spread), objectui#7835 (stale renderer comments, closed), objectui#3807 (the sections input description teaching a retired shape, closed), objectui#736 (the ListView protocol gap checklist, closed). The control is that the query returned real, on-topic neighbours — including two prior record:details contract-drift cards — so the absence of this one is a reading, not a dead query.

Refs: objectui#8583 / PR #8601 and verdict 5587871554 (where this was measured) · objectui#3807 (same component, adjacent drift, closed)

Activity

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

Metadata

Metadata

Assignees

Labels

domain:specobjectui spec stream: fix lands on packages/types, schema corpus or spec pin coupling — spec lanepackage: typespriority:p2

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions