Repository navigation
fix(types): spec-derived ListViewSchema and PageNodeSchema carry the spec's object-level checks (objectui#7715) - #10421
Conversation
…fusal must reach objectui's mirror (red at base) The objectui#7122 item-6 option-A control was not on main. It is added here first, asserting parity: the spec refuses `appearance.allowedVisualizations: ['calendar']` with no `calendar:` block, and objectui's `ListViewSchema` and `safeValidateSchema` must refuse it with the spec's own issue. At this commit the objectui leg is red (the mirror accepts), which is the divergence the next commit closes. Claude-Session: https://claude.ai/code/session_014mXUNuFomfj24w7s1pZzhN Co-authored-by: Claude <noreply@anthropic.com>
…ported object-level checks The six `specFieldsExcept(...)` mirrors rebuild a fresh object from the spec's `.shape`, carrying fields and dropping the checks the spec attaches to the object. Per ruling B1 each site now attaches the spec's exported check for the fields it carries: - ListViewSchema: `.superRefine(checkListViewCalendarVisualization)`. `checkListViewPageMount` is not attachable: it reads `type`, which the list-view node spends on its component discriminator (the spec's view kind rides as `viewType`), so as-is it would refuse every `pageName`. - PageNodeSchema: `.superRefine(checkPageSourceCompleteness)` — `kind` and `source` are both carried by reference. - NavigationArea, App, DashboardWidget, Dashboard: the spec objects carry no object-level check at the resolved release; nothing to attach. The tripwire from the previous commit turns green. A census pin accounts for every object-level check on the six spec objects and every exported `check*`, so a check the spec adds later fails by site name. Claude-Session: https://claude.ai/code/session_014mXUNuFomfj24w7s1pZzhN Co-authored-by: Claude <noreply@anthropic.com>
… mirrors now chain `checkListViewCalendarVisualization` and `checkPageSourceCompleteness` are spec CHECK FUNCTIONS mounted with `.superRefine`, not schemas crossing the boundary: they have no Zod graph and write no value into a document, the case `REFINEMENT_EXCEPTIONS` exists for (objectui#8563). Claude-Session: https://claude.ai/code/session_014mXUNuFomfj24w7s1pZzhN Co-authored-by: Claude <noreply@anthropic.com>
…surface, not a restricted namespace import A namespace import of `@objectstack/spec/ui` is refused by this repository's `no-restricted-imports` rule. The census now imports each schema and check by name and reads the `check*` function list from the spec's own `api-surface/ui.json`, with a control that the surface file was read. Claude-Session: https://claude.ai/code/session_014mXUNuFomfj24w7s1pZzhN Co-authored-by: Claude <noreply@anthropic.com>
…ord, not an angle-bracket tag Claude-Session: https://claude.ai/code/session_014mXUNuFomfj24w7s1pZzhN Co-authored-by: Claude <noreply@anthropic.com>
|
changeset-claim-re-read
|
✅ 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
|
…c-refinements Claude-Session: https://claude.ai/code/session_014mXUNuFomfj24w7s1pZzhN Co-authored-by: Claude <noreply@anthropic.com>
Contract reviewServed-tier: Inputs read: card #7715 body and 5 comments (ruling 5564943016 = B1; unlock comment 5819418589 authorises adding the option-A tripwire red-first in the same PR); PR #10421 body and 5-file list; ① Derived judgmentsCensus — RIGHT. Measured on the published 17.4.0 package (scratch checkListViewCalendarVisualization — RIGHT, same semantics. The spec function returns unless checkPageSourceCompleteness — RIGHT, fields unchanged. Reads checkListViewPageMount refusal — DEFENSIBLE under the ruling's words; residual disclosed, not a defect. The function sets zod 4 hazard — NO BREAK. Measured at zod 4.4.3 on an object carrying a superRefine: Census pin on the next bump — sensible as a vitest pin, but it carries a compile-level TRAP that has already sprung (③-1). The vitest rows will redden by site name (DashboardWidget 0 to 2, ListView 2 to 1, export-set mismatch), as designed. But the file also has a static value import of Tripwire. Base ② Semver level
③ Boundary flags
Implemented-by: VERDICT: FAIL — ③-1: the new census pin's static import of |
…y name at run time, so a spec that drops one still compiles `Spec Main Shape Gate` compiles objectui against @objectstack/spec built from objectstack main, where `checkListViewPageMount` is gone (objectstack#17063). The census imported it by name, so that compile failed with TS2305 before any census row could report which site moved. The census now imports schemas only. It reads every `check*` function from the entry point's runtime namespace, by name, through a dynamic `import()` (check functions are values, so the namespace sees them; the static namespace form stays a restricted import). Against spec main this file now compiles, and at run time the DashboardWidget row, the ListView row, the export-set pin and the page-mount measurement fail by name. Claude-Session: https://claude.ai/code/session_014mXUNuFomfj24w7s1pZzhN Co-authored-by: Claude <noreply@anthropic.com>
✅ 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 reviewServed-tier: Inputs read: card #7715 body and 5 comments (ruling 5564943016 = B1; unlock 5819418589); PR #10421 body (updated for round 1), 5-file list, 5 comments including prior record 5824593274; ① Derived judgmentsMerge commit Zod sources and changeset unchanged since Census — RIGHT, re-measured on the packed 17.4.0. The two attached checks — RIGHT, unchanged since round 0. Probed on 17.4.0: spec refuses
Round-1 change: run-time read of Same-module control — SOUND. objectui#3090 rule — intent HONOURED, convention SIDESTEPPED (non-blocking). The rule is 8317 census entries and the mirrors' own static imports name only ② Semver level
③ Boundary flags
Implemented-by: VERDICT: PASS |
Fixes #7715
Clause-②: yes
Implements ruling B1 (director seat, comment 5564943016, decision batch #67, maintainer 「同意」): objectui's spec-derived zod mirrors re-attach the spec's exported object-level checks. This is an accept-set narrowing of
@object-ui/types, so the contract review runs atCONTRACT_REVIEW_TIER; theneeds:contract-reviewlabel is the seat's to hang (this PR writes no labels). Changeset:.changeset/7715-mirrors-carry-spec-object-checks.md(@object-ui/types: minor, breaking semantics in the body).Implemented by the
os-devagent for thedomain:uiseat 2 dispatch, sessionhttps://claude.ai/code/session_014mXUNuFomfj24w7s1pZzhN. Head is65df5b3, which mergesorigin/mainatf475557(merge commit8d62d1b) and adds one test-only commit,65df5b3, answering theSpec Main Shape Gatefailure and the at-tier review's item ③-1. Round-0 code and test measurements below were taken on843e125; the zod sources and the changeset have not changed since. The round-1 readings are on65df5b3and are listed under Local verification.What changed
packages/types/src/zod/objectql.zod.ts:ListViewSchemaends in.superRefine(checkListViewCalendarVisualization), imported from@objectstack/spec/ui.packages/types/src/zod/layout.zod.ts:PageNodeSchemaends in.superRefine(checkPageSourceCompleteness).packages/types/src/__tests__/spec-object-refinements-7715.test.ts(new): the tripwire, the Page pin and the census described below. It imports spec SCHEMAS only. Every speccheck*function is read at run time, by name, from the entry point's module namespace, so a spec that adds or drops a check still compiles and the census fails by row name instead.packages/types/src/__tests__/imported-defaults-8317.test.ts: the two check functions are added toREFINEMENT_EXCEPTIONS(the import-boundary census reads every spec value a mirror imports; a chained check FUNCTION is the case that list exists for).Both attachments call the spec's own function, so the refusal is the spec's text, byte for byte. No check body is copied into objectui.
Step zero: the objectui#7122 option-A tripwire was not on
maingit grepover the tree for a list-view parse carrying'calendar'inallowedVisualizationswithout acalendarblock: no such test. The singlepackages/typeshit,p2-spec-exports.test.ts, parses the spec's ownAppearanceConfigSchemaand never touches objectui'sListViewSchema, so it is not the tripwire.b46d8b3(test only). On that tree it was red in the "spec refuses, objectui accepts" direction:Tests 1 failed | 2 passed (3), and the failure wasexpected true to be falseonListViewSchema.safeParse(...).success. After0731fa0it is green.Per-site enumeration (the contract-review claim)
Object-level checks are read from each upstream spec object's
_zod.def.checksat the resolved@objectstack/spec17.4.0. The export list comes from the installed package (thecheck*functions of@objectstack/spec/ui:checkGlobalFilterDateDefaultValue,checkListViewCalendarVisualization,checkListViewPageMount,checkPageSourceCompleteness), which verifies H1.NavigationAreaSchema(app.zod.ts)NavigationAreaSchemaSpecAppFields→AppComponentSchema(app.zod.ts)AppSchemaDashboardWidgetSchema(complex.zod.ts)DashboardWidgetSchemaSpecDashboardFields→DashboardComponentSchema(complex.zod.ts)DashboardSchemaglobalFiltersitems are the separate row below)SpecPageFields→PageNodeSchema(layout.zod.ts)PageSchemacheckPageSourceCompletenesskindandsource; the node carries both by reference (neither is inPAGE_SPEC_EXCLUDEDor overridden)ListViewSchema(objectql.zod.ts)ListViewSchemacheckListViewCalendarVisualizationappearance.allowedVisualizations(carried by reference) and only whethercalendaris present; the localCalendarConfigoverride keepscalendaroptional, so "absent" means the same on both facesListViewSchema(objectql.zod.ts)checkListViewPageMounttype, which on this node is the component discriminator'list-view'; the spec's view kind rides asviewType. Attached as-is it would refuse everypageName, including a valid page mount (pinned: the spec accepts its own page mount, and the check run on objectui's spelling of it refuses atpageName), and its remedy tells the author to writetype: 'page', which the node's literal refuses. Thepagelist-view kind is retired upstream (objectstack#17063, noted in core'sUNDRAWABLE_VIEW_KINDS), and this check is gone from objectstackmainat5581d30GlobalFilterSchema(complex.zod.ts, a.shapespread and not aspecFieldsExceptsite)GlobalFilterSchemasuperRefinere-parses the spec-owned keys through the spec schema. Pinned: objectui refusesdefaultValue: 'last_7_dayz'with the spec's message. Left unchangedThe six spec objects carry no unexported object-level check: every check they carry at 17.4.0 has a named export (H1's per-site question).
The census pin (
spec-object-refinements-7715.test.ts) keeps this table live. For each site, the attached and not-attachable lists must add up to the spec object's own check count. Thecheck*functions the spec'suientry point exports, read from its runtime module namespace, must equal the names in the table. A check the spec adds later therefore fails by site name. Objectstackmainalready carriescheckDashboardWidgetStageOrderandcheckDashboardWidgetMetricMeasureArity, which are not in 17.4.0, so the next spec bump will turn row 3 red. That is intentional.Verdict-change measurement (authored documents)
apps/examples/content/allowedVisualizations, andgit grep -n allowedVisualizations -- apps examples contentfinds 0 hits. The controls in the same corpora and on the same channel do hit:appearancein 3 files, alist-viewliteral in 6 files. The page check can only fire on a literalkindofhtml,reactorjsx. Across the three corpora those spell 8 document literals. Only 1 of them is atype: 'page'node (the 6678 console test, which is always passed a non-empty source), and all 8 carrysource. The other hits are prose and comments..jsonfile (471) and everyjson/jsoncfence in.md/.mdx(210) under the three corpora was parsed, and every nested object withtype: 'list-view'ortype: 'page'was walked. That found 2 list-view nodes and 16 page nodes, and neither newly attached check fires on any of them. 30 fences do not parse as JSON; leg 1 covers them. Positive control: one list-view node and one page node, both synthetic, were injected into the same walk, and both checks fired on them (2 of 2).Reverse verification (both legs, on committed heads)
Each run mutated one line through
ablation-replace.mjsin WRAP mode (anchor hit exactly 1, landing proven by marker count and blob hash, restore proven byblob == HEADand an emptygit diff HEAD), then ran the 7715 test file. The test imports the mirror sources directly, and the root vitest config aliases@object-ui/typestosrc, so nodistrebuild sits between the mutation and the run..superRefine(checkListViewCalendarVisualization): the tripwire went red (Tests 1 failed | 14 passed (15)) and was restored (4f7e91951ff1). The direction is the expected one, red..superRefine(checkPageSourceCompleteness): both Page pins went red (2 failed | 13 passed) and were restored (2ade6eeb933e).checkListViewPageMountcensus entry: the ListView census row and the export-set pin went red (2 failed | 13 passed), which proves the census can fail. Restored.;, which also occurs elsewhere in the file. The tool refused it before any test ran (replacement count did not rise) and restored the file. That attempt took no measurement; the next attempt used a distinct marker.All three legs were re-run at
65df5b3with the same results (1 failed | 14 passed,2 failed | 13 passed,2 failed | 13 passed), because round 1 changed how the census reads the export set.Local verification (round 0 at
843e125, round 1 at65df5b3)65df5b3:pnpm exec vitest run packages/types/gaveTest Files 230 passed (230)andTests 5133 passed (5133);pnpm --filter @object-ui/types type-checkexited 0; the downstream sweep, re-enumerated on the merged tree, gaveTest Files 83 passed (83)andTests 1622 passed (1622).843e125:check-changeset-presence,check-changeset-no-major,check:changeset-claims(report-only; it flags 27 pending changesets that nameobjectql.zod.tsorlayout.zod.ts, and the ones that mention these two schemas were re-read, none falsified),check:new-line-citations(0 new),check:control-bytes,check:esm-specifiers,check:self-import,check:phantom-deps,check:spec-symbols,check:component-surface-parity,check:installed-pin-claims,check:pending-changeset-literals,check:test-path-roots.Spec Main Shape Gate(compiles objectui against@objectstack/specbuilt from objectstackmain): red ata4835b6(CI job 107888507933, one diagnostic: TS2305,spec-object-refinements-7715.test.tsline 47, no exported membercheckListViewPageMount). The test imported that check by name, and objectstackmainremoved the export (objectstack#17063). Reproduced locally against7e6ca1787aa9with the workflow's recipe (sparse build,npm pack,spec-main-shape-gate.mjs inject) and the type-check leg narrowed to@object-ui/types: the same single diagnostic,reportexit 1. Green at65df5b3against the same commit:reportexit 0 with '✅ objectui type-checks against@objectstack/specat that commit.'. Against spec main the census now fails at RUN time, not compile time, by row name: DashboardWidgetSchema, ListViewSchema, the export-set pin and the page-mount measurement (4 failed | 11 passed). At the pinned 17.4.0 it is15 passed.check-governed-queue-guard --testover the 5 changed paths reports NOT GOVERNED.check:spec-floorsis NOT MEASURED as a gate: it exits 1 on 17no-artifactfindings, all on packages this PR does not touch, because it needs the whole workspace built, which is CI's run. Afterpnpm --filter @object-ui/types build, it judged@object-ui/types(whose dist now references both new imports) and reported no finding for it. The declared floor^17.4.0carries both names indist/ui/index.d.mts.eslint --no-inline-configover the 4 touched source and test files: 0 errors, 1 warning. The warning (no-explicit-anyon the existingoptionsbag check) was already on the base, one line higher. The repo-widepnpm lintis CI's run.Acceptance notes (observations, not filed)
navigation[](App and NavigationArea),regions[].components[].visibleWhen(Page), anduserFilters.tabs[].filter[]andconditionalFormatting[].condition(ListView). objectui replaces each of those keys with a local shape that is declared broader than the spec's. Whether any of them accepts something the spec refuses was not measured. The census reading is recorded here so a future card can start from it.GlobalFilterSchemacould switch from its delegating re-parse to.superRefine(checkGlobalFilterDateDefaultValue). No accept set would change, and it is not aspecFieldsExceptsite, so it is left as is.content/authors either refused shape (leg 1), so no doc text changes.Generated by Claude Code