feat(types): NamedListView declares the 17 protocol members, each with a read point (objectui#8980) - #9534
Conversation
…ith a read point `NamedListView` declared none of the 17 live members the protocol declares on the same surface (`ObjectListViewSchema`, the value type of `ViewSchema.listViews` and `ObjectSchema.listViews`) — objectui narrower than the protocol, the direction the maintainer's standing principle forbids. All 17 are declared with their types taken from the protocol, and each gets a read point in the same delivery per the director-seat ruling's item 2: - the 8 view-kind blocks reach the renderer ObjectView dispatches to for that `type`, at the protocol's own top level; the legacy `options.<kind>` nesting keeps working and a canonical block wins key-by-key over it - `grouping` / `rowColor` reach ObjectGrid on the authored grid path - `fieldOrder` / `appearance` / `userActions` / `grouping` / `rowColor` / `data` reach ListView through the host delegation; `data` loses its `as any` cast, which answers objectui#7928's open half - `name` is read on the named-view tab strip `tabs` and `pageName` are declared and reported with their measurement — no renderer behaviour to attach on this surface — as the ruling requires. The objectui#7924 / objectui#7779 census ledger is re-taken with the new figures: 64 declared = 21 read + 43 unread. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L5xpA5q533BgTTNADibEFt
…stView` type The first cut routed it through a new `z.input<typeof ObjectListViewSchema>` alias in the zod mirror, which is a VALUE crossing of the objectui#8317 import boundary — `imported-defaults-8317.test.ts` red by name, correctly: a symbol a mirror imports must be in that file's differential. `@objectstack/spec/ui` already publishes the authored type (`ListView` = `z.input<typeof ListViewSchema>`), so the member indexes that instead, as a type-only import in `objectql.ts` beside the other spec types it already takes. No value crosses the boundary and the mirror is untouched. `name` is the same declaration on both spec shapes — `ObjectListViewSchema` omits and re-extends only `userFilters`. Also: the `gantt` and `chart` fixtures in the new pin carry the protocol's own required bindings, measured by tsc while writing them. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L5xpA5q533BgTTNADibEFt
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
|
Contract reviewIndependent contract-review seat, round 1, at head Verdict: PASS. 50 claims discharged, where one claim = one assertion carrying its own Population bound (PROVED before any counter was read)Whole tracked tree at this head, 7791 files, by two independent enumerations that agree
Corpus, with resolved paths printed. From Ruled item 1 — the 17, and their types — SATISFIEDMeasured at runtime against the resolved spec, not read off a
⭐ The tombstones do refuse — the load-bearing step that turns 22 into 17. All five The member census is a brace-walk of ownership, not proximity: the TypeScript AST's own Types are taken from the protocol. 16 members index this package's spec-derived Ruled item 2 — a read point each, exceptions REPORTED — SATISFIED15 of the 17 have a read point;
Ruled item 3 — which
|
Contract-review discharge — PR #9534 / card #8980Posted before the Verdict being discharged against
Read at Clause ② and why the carrier was hung Card #8980 declares The reviewer measured both limbs independently and reached
One limb firing is sufficient. Other gates at this same head
What is being removed, and from where
Label sets before removal — PR #9534: The two non-blocking wording observations the reviewer recorded (the Generated by Claude Code |
EnqueuedContinuation of the discharge record above. Every step below is a reading, with its instrument and time named.
The ordering the discharge record promised is visible in the PR's own timeline and can be checked without trusting this comment:
Generated by Claude Code |
Fixes #8980
NamedListView(@object-ui/types) declared none of the 17 live members theprotocol declares on the same surface. objectui was NARROWER than the protocol on
all 17 — the direction the maintainer's standing principle forbids. This declares
all 17 with their types taken from the protocol, and gives every one of them a
read point in the same delivery.
Governing ruling: director-seat class-one adjudication, objectui#8980 comment
5651818304, 2026-09-13. Its four items are executed as written; none isre-litigated here.
Premise, re-derived on this branch — the card still holds
Re-measured at
origin/mainbbc9dc34e3, non-shallow, against the resolved@objectstack/spec@17.4.0, with the instrument self-tested on a firing positivecontrol (two real interfaces, 9 and 2 members, both verified against source) and
a non-firing negative control (a nonexistent interface name, exit 3):
ObjectListViewSchemashape keysNamedListViewmembers, beforeborderedperformanceresponsivestripedvirtualScrollThe counting rule, published with the number — the seat deliberately
certified no total, so this is mine and is stated so it can be re-derived:
top-level property signatures at brace-depth 1 of the
export interface NamedListViewbody, line and block comments stripped,readonlyand?allowed; members of nested object-literal types are NOT counted. Under that rule
the interface reads 47 before and 64 after. The interface's own census pin
(
object-view-unmirrored-keys-7779.test.ts) walks the same population with theTypeScript parser and agrees.
The tombstones are measured, not assumed. Each refuses a plausible value with⚠️ Six other keys also refused my first
a message naming its 17.0.0 removal.
probe values (
namepageNameon a 2-character minimum,datakanbangantttimelinechartgroupingon missing required inner fields) — that is thePROBE's fault and not six more tombstones, the same self-correction the earlier
seat recorded. The discriminator is the refusal MESSAGE, not the refusal.
NamedListViewopensat
objectql.ts:2088, not:2073. Everything here binds to the SYMBOL.Ruled item 3 —
tabs, measured before it was declaredThe protocol spends the word
tabson two different keys, and this establisheswhich one the list shape carries:
ObjectListViewSchemaisListViewShapeSchema.omit({ userFilters }).extend({ userFilters: ObjectUserFiltersSchema }), so the top-leveltabs: z.array(ViewTabSchema)passes through untouched. That is the key declared here.userFilters.tabsis the oneObjectUserFiltersSchemaOMITS as page-only, in its own words: "an object view's tab bar is its saved-view switcher (ViewTabBar), and a second one would collide."objectui#9517's landing on this same file does not change that reading: it moved
the user-filter FIELD shape and declared the container a dialect, not the
container's
tabs. objectui'suserFiltersdialect keeps carrying its ownlegacy
tabsseparately, so the two keys now sit on one interface and thedeclaration says so.
What each of the 17 reaches
ObjectViewhas three exits and the members do not all leave by the same one.kanbancalendargallerytimelineganttmapgenerateViewSchema— the AUTHORED path — now resolves the protocol's top-level block and merges the legacyoptions.KINDnesting under it, so each reachesObjectKanban/ObjectCalendar/ObjectGallery/ObjectTimeline/ObjectGantt/ObjectMapcharttreegroupingrowColorObjectGridon the authored grid path (it already reads both), andListViewthrough the host delegationfieldOrderappearanceuserActionsListViewthrough the host delegation.fieldOrderis the live third key of the protocol'scolumnstimeshiddenFieldstimesfieldOrdercomposition — objectstack#15184 ruling B, read on the card rather than assumed: that card is OPEN, and its ruling B KEPT the keydataListView— and theas anycast the renderer reached this key through is gone, which is objectui#7928's open half answerednamelabeland the record keytabspageNameThe merge is per-KIND and per-KEY, not wholesale, and identity is preserved
when no canonical block is declared — which is the whole existing population,
since these keys could not be authored before this change. So the renderer
produces byte-identical nodes for every stored document, and there is an
absence-control case beside every fix arm asserting exactly that.
Ruled item 2's report — three members measured, none invented, none dropped
"A member for which the renderer has no behaviour to attach is REPORTED on this
card with the measurement." Posted in full on objectui#8980.
tabs— no reader on this surface. objectui's tab bar for an object is the HOST-owned saved-view switcher (ADR-0053); the onlytabsread underpackages/plugin-listisUserFilters'config.tabs, which is the page-only key, not this one.pageName—pageis not a member ofNamedListView['type'](seven values against the protocol's ten), so no authored named view can select the branch this key configures.chart/tree— READ, and the pin proves it, but objectui#5321 ruled both view KINDS host-composition-only. A named view reaches them only by declaring notypeof its own while a hostviewsentry selects the kind. The read is real; the authored route to it is not.All three stay declared. ⛔ No read point was invented to make the count come out
even, and ⛔ no member was dropped to avoid the report.
Types are taken from the protocol
Sixteen index this package's own spec-derived
list-viewnode type(
ListViewSchema), whose members arrive fromSpecListViewSchema.shapebyreference — the derivation
NamedListView.userFiltersalready used one memberup, and the one that keeps a named view and the
list-viewnode it is relayedINTO the same type for every key that crosses.
nameindexes the protocol's ownpublished authored type (
ListView,@objectstack/spec/ui) instead: on the nodethat spelling resolves through
BaseSchema.name, the component-name slot — adifferent contract wearing the same word.
by
imported-defaults-8317.test.ts: importing a spec schema into a mirror is acrossing of the objectui#8317 import boundary and must appear in that file's
differential. The type-only import in
objectql.tscrosses no boundary and themirror is untouched.
Two narrowings the protocol owns, found by tsc while writing the pins
Reported rather than worked around, because the declared face is now the
protocol's:
ganttrequirestitleFieldalongsidestartDateField/endDateField. Unlike the four blocks this package keeps a local.partial()dialect for,ganttflows into the mirror straight fromSpecListViewSchema.shape, so the protocol's required bindings are required here.chartis the ADR-0021 dataset-bound shape only (dataset+valuesrequired). The renderer still carries a legacy inline-aggregate branch below it (xAxisField/valueField/aggregation) that the declared face cannot express. ⛔ Not widened locally — that legacy shape still reaches the branch through the untypedoptions.chartbag, and narrowing an authoring surface toward the protocol is the direction the principle asks for.The census ledger is re-taken, not adjusted
object-view-unmirrored-keys-7779.test.tspinsNamedListView's population byname and by count, and its own failure message names the four files that carry
the figures together. All four are updated in this change:
64 declared = 21 read + 43 unread (was 47 = 6 + 41, plus one undeclared cast
read).
tabsandpageNameare two of the 43, and the ledger's docblock saysthat is the ruled outcome rather than an oversight. The instrument agreement is
re-pinned too: parser 64 = strict regex 64, loose regex 76 — still a gap of
exactly 12, because every new member is a single-line type reference that adds no
nested object literal.
⭐ One genuine ledger change beyond the numbers: the AST derivation and the
currentNamedViewConfig?.KEYregex it replaced now disagree by one NAME, notjust by sites —
nameis read only at the tab strip. The difference is assertedby name rather than smoothed over.
Verification
Run from the repo root in a dedicated worktree, every heavy run through the
shared verify lock.
pnpm exec vitest run packages/types/ packages/plugin-view/— 229 files / 4707 tests passed, lock VERDICT command-exit 0.pnpm --filter @object-ui/types --filter @object-ui/plugin-view type-check— exit 0. It covers the new test file: its first run reported the two fixture errors that produced thegantt/chartfindings above, so the coverage is a reading and not an assumption.pnpm --filter '@object-ui/plugin-view^...' build— exit 0, 16 projects. The@object-ui/typesdependency closure is EMPTY (it depends only on@objectstack/specandzod), so that package's own closure build is a no-op and is reported as such rather than as a pass.package.jsonand.github/workflows/, ⛔ not from the sibling repo'sdispatch-gates.mjs, which answers only about the tree it lives in:check:control-bytescheck:new-line-citationscheck:spec-symbolscheck:vi-mock-specifierscheck:vi-mock-inheritcheck:vi-mock-override-shapecheck:test-path-rootscheck:changeset-claimscheck:comment-mask-corpuscheck:unreferenced-sourcescheck:lint-rule-coveragecheck:handler-key-readscheck:changeset-presence— all exit 0.check:eager-closure,check:sdui-registration-pinsandcheck:readme-exportsare NOT MEASURED here, ⛔ not red: each refuses for a missing console/workspace build in its own words ("this is a broken gauge, not a passing budget"; "the population COLLAPSED — this run proves nothing", citing 21 unbuilt packages). CI builds and owns them.eslinton the six changed files: 0 errors, 115 pre-existing warnings, exit 0. Package-scopedlintfor both affected packages: 0 errors (286 + 348 warnings, none new-rule). The narrowing is a measurement, with its three pieces: the covered population iseslint .minus this config's ownignores; the file count is 6, read from--format json; and the invariance holds because the config configures NO type-aware linting (zeroproject/projectServiceentries), so this diff cannot move the verdict on any file it does not touch. Measured on final head0e69156.grep -naPover every changed file finds none, with a firing positive control on a synthetic file.Reverse verification — direction predicted before the run
The renderer alone was restored to the merge-base on disk (blob
b6c0cc566bto008aa55149, mutation proven by hash comparison AND by fivemarker counts going 4/1/1/1/1 to 0/0/0/0/0), the new pin re-run, then restored
under a
trap ... EXIT INT TERMand proven back by blob hash and an emptygit diff HEADon that path.Predicted: every FIX arm RED, every CONTROL arm GREEN. Measured: 13 failed, 7
passed — the 13 are exactly the fix arms (six view kinds, the chart/tree route,
the canonical-wins and per-key-merge arms, the grid pair, the delegation six, the
userActionsmerge, thenamefallback) and the 7 that stayed green are thecontrols (the legacy-nesting control, both absence controls, the
labelprecedence and record-key controls, the
tabs/pageNameabsence pin, and thedatabehavioural arm — which passes on both trees because the old cast relayedthe same value, exactly as that case's own comment says, its source half being
pinned in the types census instead).
distpreflight: this repo's root vitest config aliases every@object-ui/*specifier to that package'ssrc, so the suite runs from sourceand an on-disk proof is the right evidence here.
Scope
Clause-②: no, measured: the runtime accept set does not move.ObjectViewSchema.listViewsis UNMIRRORED on the zod side and ridesBaseSchema's passthrough, so no parse changes; the TS face gains optional members, which widens rather than narrows. The--pairexit code is reported to the PM seat.needs:contract-reviewincluded — labels are the PM seat's alone.NamedListViewdeclares beyond the protocol are objectui#7924's remedy and are untouched (ruled item 4). That card shares this file and is serial behind this one.packages/app-shellis NOT touched, though it is wherenameis written today (mergeViewsIntoObjectsstampsname: keyon every composed entry and primary-view promotion matches on it — recorded at the declaration as the evidence that this key has been travelling on this surface undeclared). It is outside the claim's declared file surface.Generated by Claude Code