Skip to content

fix(types): spec-derived ListViewSchema and PageNodeSchema carry the spec's object-level checks (objectui#7715) - #10421

Merged
os-elon-musk merged 7 commits into
mainfrom
claude/issue-7715-mirror-carries-spec-refinements
Sep 25, 2026
Merged

os-elon-musk merged 7 commits into
mainfrom
claude/issue-7715-mirror-carries-spec-refinements

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Sep 25, 2026 •

Copy link
Copy Markdown
Contributor

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 at CONTRACT_REVIEW_TIER; the needs:contract-review label 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-dev agent for the domain:ui seat 2 dispatch, session https://claude.ai/code/session_014mXUNuFomfj24w7s1pZzhN. Head is 65df5b3, which merges origin/main at f475557 (merge commit 8d62d1b) and adds one test-only commit, 65df5b3, answering the Spec Main Shape Gate failure and the at-tier review's item ③-1. Round-0 code and test measurements below were taken on 843e125; the zod sources and the changeset have not changed since. The round-1 readings are on 65df5b3 and are listed under Local verification.

What changed

  • packages/types/src/zod/objectql.zod.ts: ListViewSchema ends in .superRefine(checkListViewCalendarVisualization), imported from @objectstack/spec/ui.
  • packages/types/src/zod/layout.zod.ts: PageNodeSchema ends 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 spec check* 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 to REFINEMENT_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 main

  • git grep over the tree for a list-view parse carrying 'calendar' in allowedVisualizations without a calendar block: no such test. The single packages/types hit, p2-spec-exports.test.ts, parses the spec's own AppearanceConfigSchema and never touches objectui's ListViewSchema, so it is not the tripwire.
  • The file list of PR chore(deps): resolve @objectstack/spec at 17.3.0 in the lockfile #7685 (64 files, read through REST) contains no ListView tripwire. The objectui#7122 dev report records the leg-6 pin as "written and green (WIP, unpushed)".
  • So the pin landed first, as 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 was expected true to be false on ListViewSchema.safeParse(...).success. After 0731fa0 it is green.

Per-site enumeration (the contract-review claim)

Object-level checks are read from each upstream spec object's _zod.def.checks at the resolved @objectstack/spec 17.4.0. The export list comes from the installed package (the check* functions of @objectstack/spec/ui: checkGlobalFilterDateDefaultValue, checkListViewCalendarVisualization, checkListViewPageMount, checkPageSourceCompleteness), which verifies H1.

# objectui site spec object object-level checks verdict why
1 NavigationAreaSchema (app.zod.ts) NavigationAreaSchema 0 nothing to attach none upstream
2 SpecAppFields → AppComponentSchema (app.zod.ts) AppSchema 0 nothing to attach none upstream
3 DashboardWidgetSchema (complex.zod.ts) DashboardWidgetSchema 0 nothing to attach none upstream at 17.4.0
4 SpecDashboardFields → DashboardComponentSchema (complex.zod.ts) DashboardSchema 0 nothing to attach none upstream (the globalFilters items are the separate row below)
5 SpecPageFields → PageNodeSchema (layout.zod.ts) PageSchema 1 attached: checkPageSourceCompleteness reads kind and source; the node carries both by reference (neither is in PAGE_SPEC_EXCLUDED or overridden)
6a ListViewSchema (objectql.zod.ts) ListViewSchema 2 attached: checkListViewCalendarVisualization reads appearance.allowedVisualizations (carried by reference) and only whether calendar is present; the local CalendarConfig override keeps calendar optional, so "absent" means the same on both faces
6b ListViewSchema (objectql.zod.ts) (same) (same) not attachable: checkListViewPageMount it reads type, which on this node is the component discriminator 'list-view'; the spec's view kind rides as viewType. Attached as-is it would refuse every pageName, including a valid page mount (pinned: the spec accepts its own page mount, and the check run on objectui's spelling of it refuses at pageName), and its remedy tells the author to write type: 'page', which the node's literal refuses. The page list-view kind is retired upstream (objectstack#17063, noted in core's UNDRAWABLE_VIEW_KINDS), and this check is gone from objectstack main at 5581d30
— GlobalFilterSchema (complex.zod.ts, a .shape spread and not a specFieldsExcept site) GlobalFilterSchema 1 already carried its own superRefine re-parses the spec-owned keys through the spec schema. Pinned: objectui refuses defaultValue: 'last_7_dayz' with the spec's message. Left unchanged

The 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. The check* functions the spec's ui entry 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. Objectstack main already carries checkDashboardWidgetStageOrder and checkDashboardWidgetMetricMeasureArity, 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)

corpus changed verdict how measured
apps/ 0 legs 1 and 2 below
examples/ 0 legs 1 and 2 below
content/ 0 legs 1 and 2 below
hotcrm NOT MEASURED not reachable from this container: not in this session's repository scope, and no checkout is present. ⛔ Not asserted as zero
  • Leg 1: trigger census (all file types, tracked files only). The calendar check can only fire on a document that spells allowedVisualizations, and git grep -n allowedVisualizations -- apps examples content finds 0 hits. The controls in the same corpora and on the same channel do hit: appearance in 3 files, a list-view literal in 6 files. The page check can only fire on a literal kind of html, react or jsx. Across the three corpora those spell 8 document literals. Only 1 of them is a type: 'page' node (the 6678 console test, which is always passed a non-empty source), and all 8 carry source. The other hits are prose and comments.
  • Leg 2: parse census. Every tracked .json file (471) and every json/jsonc fence in .md/.mdx (210) under the three corpora was parsed, and every nested object with type: 'list-view' or type: '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.mjs in WRAP mode (anchor hit exactly 1, landing proven by marker count and blob hash, restore proven by blob == HEAD and an empty git diff HEAD), then ran the 7715 test file. The test imports the mirror sources directly, and the root vitest config aliases @object-ui/types to src, so no dist rebuild sits between the mutation and the run.

  • Removing .superRefine(checkListViewCalendarVisualization): the tripwire went red (Tests 1 failed | 14 passed (15)) and was restored (4f7e91951ff1). The direction is the expected one, red.
  • Removing .superRefine(checkPageSourceCompleteness): both Page pins went red (2 failed | 13 passed) and were restored (2ade6eeb933e).
  • Deleting the checkListViewPageMount census entry: the ListView census row and the export-set pin went red (2 failed | 13 passed), which proves the census can fail. Restored.
  • The first ListView attempt used the replacement ;, 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 65df5b3 with 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 at 65df5b3)

  • At 65df5b3: pnpm exec vitest run packages/types/ gave Test Files 230 passed (230) and Tests 5133 passed (5133); pnpm --filter @object-ui/types type-check exited 0; the downstream sweep, re-enumerated on the merged tree, gave Test Files 83 passed (83) and Tests 1622 passed (1622).
  • Gates, each exit 0 at 843e125: check-changeset-presence, check-changeset-no-major, check:changeset-claims (report-only; it flags 27 pending changesets that name objectql.zod.ts or layout.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/spec built from objectstack main): red at a4835b6 (CI job 107888507933, one diagnostic: TS2305, spec-object-refinements-7715.test.ts line 47, no exported member checkListViewPageMount). The test imported that check by name, and objectstack main removed the export (objectstack#17063). Reproduced locally against 7e6ca1787aa9 with 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, report exit 1. Green at 65df5b3 against the same commit: report exit 0 with '✅ objectui type-checks against @objectstack/spec at 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 is 15 passed.
  • check-governed-queue-guard --test over the 5 changed paths reports NOT GOVERNED.
  • check:spec-floors is NOT MEASURED as a gate: it exits 1 on 17 no-artifact findings, all on packages this PR does not touch, because it needs the whole workspace built, which is CI's run. After pnpm --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.0 carries both names in dist/ui/index.d.mts.
  • eslint --no-inline-config over the 4 touched source and test files: 0 errors, 1 warning. The warning (no-explicit-any on the existing options bag check) was already on the base, one line higher. The repo-wide pnpm lint is CI's run.

Acceptance notes (observations, not filed)

  • Nested spec checks under locally overridden keys fall outside B1. They are not object-level checks of the six objects, and none is exported. The spec has checks inside navigation[] (App and NavigationArea), regions[].components[].visibleWhen (Page), and userFilters.tabs[].filter[] and conditionalFormatting[].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.
  • GlobalFilterSchema could switch from its delegating re-parse to .superRefine(checkGlobalFilterDateDefaultValue). No accept set would change, and it is not a specFieldsExcept site, so it is left as is.
  • Docs: no page in content/ authors either refused shape (leg 1), so no doc text changes.

Generated by Claude Code

…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>
@github-actions

github-actions Bot commented Sep 25, 2026 •

Copy link
Copy Markdown
Contributor

changeset-claim-re-read

⚠️ 27 pending changeset(s) describe a file this change touches

Their bodies publish verbatim into the CHANGELOG at the next release, so this is a request to re-read them against your diff — addressed here because you are the one seat that can answer it without re-deriving anything.

⛔ Nothing here blocks, and nothing here is a verdict on your change. This gate exits 0, is not a required context, and judges name resolution, never meaning: it asked whether a pending body names a file you touched. "Is this sentence still true?" is the one question it will not answer, and the one you are being asked to answer.

.changeset/5903-objectgantt-declared-keys.md

  • names src/zod/objectql.zod.ts → packages/types/src/zod/objectql.zod.ts — edited by this change

    Both halves move together. The TS declaration (packages/types/src/objectql.ts) and its zod mirror (src/zod/objectql.zod.ts) gain the same ten keys at the same requiredness — all optional — and no KnownDrift entry is added. navigation is taken from @objectstack/spec's NavigationConfigSchema by reference rather than restated, matching ObjectGridSchema.navigation.

.changeset/6940-rowactions-boolean-mirror.md

  • names zod/objectql.zod.ts → packages/types/src/zod/objectql.zod.ts — edited by this change

    The list view's same-named rowActions in zod/objectql.zod.ts — z.array(z.string()), the legacy bare-name action list on ObjectGridSchema — is a different key that is correct as it stands, is in parity with its own TS twin (rowActions?: string[]), and is not touched.

.changeset/6951-text-value-retired.md

  • names layout.zod.ts → packages/types/src/zod/layout.zod.ts — edited by this change

    Two published faces, one retirement. The TypeScript interface TextSchema (@object-ui/types, layout.ts) declares value?: never; the Zod mirror TextSchema (@object-ui/types/zod, layout.zod.ts) declares value as a retirementTombstone(), so the key stays DECLARED and is refused BY NAME — a plain deletion would have let an authored value ride BaseSchema's .passthrough() into a silent blank, which is worse than the tolerated fallback it replaces. The value?: string members of TextSpanSchema and TabsSchema in the same file are other schemas' contracts and are unchanged.

.changeset/7113-chart-data-model.md

  • names objectql.zod.ts → packages/types/src/zod/objectql.zod.ts — edited by this change

    .extend() with a NEW key still works and preserves the fold and the refinement; .optional(), z.discriminatedUnion, z.toJSONSchema and safeValidateSchema are all unaffected. Nothing in this repository calls the throwing combinators on either const, and the published surface already ships refined mirrors (objectql.zod.ts, complex.zod.ts, form.zod.ts, app.zod.ts), so the class is not new — but it is a real behaviour change on a published export and it belongs in the release note rather than in a reviewer's file.

.changeset/7200-object-form-section-style-keys-undeclared.md

  • names zod/objectql.zod.ts → packages/types/src/zod/objectql.zod.ts — edited by this change

    The authored-metadata type now agrees with @objectstack/spec, whose FormSectionSchema is a strict object declaring neither key, and with the ruling's rationale (maintainer 2026-09-01, verbatim): "retire the reads … Declaring the keys was weighed and not adopted: it would formally invite free Tailwind strings into authored metadata, the exact class the boundary exists to keep out." A ?: never tombstone was not used: ObjectFormSection has no zod mirror (ObjectFormSchema in zod/objectql.zod.ts does not declare sections), so there is no parse door to refuse at, and a tombstone is still a declaration in completion and in the published .d.ts.

.changeset/7265-types-user-filter-field-derives.md

  • names zod/objectql.zod.ts → packages/types/src/zod/objectql.zod.ts — edited by this change

    zod/objectql.zod.ts declared two schemas under names @objectstack/spec/ui already exports. They were triaged separately, by reading their sites, and went different ways.

.changeset/7322-object-kanban-group-by-limit.md

  • names zod/objectql.zod.ts → packages/types/src/zod/objectql.zod.ts — edited by this change

    Breaking for authored metadata: ObjectKanbanSchema.groupField is RETIRED (objectui#7322, ADR-0049 enforce-or-remove), and the two keys the object-kanban renderer actually reads — groupBy and limit — are now DECLARED and validated on both published faces: the TypeScript interface in objectql.ts and the Zod mirror in zod/objectql.zod.ts.

.changeset/7352-drill-down-config-mirror.md

  • names zod/objectql.zod.ts → packages/types/src/zod/objectql.zod.ts — edited by this change

    DrillDownConfigSchema is the zod mirror of DrillDownConfig, and both declarations that carry drillDown reference it — ChartSchema (zod/data-display.zod.ts) and ObjectDataTableSchema (zod/objectql.zod.ts) — so the published validator under @object-ui/types/zod reads the key for the first time (objectui#7352).

.changeset/7363-objectql-union-arms.md

  • names zod/objectql.zod.ts → packages/types/src/zod/objectql.zod.ts — edited by this change

    ObjectGallerySchema and ObjectDataTableSchema are members of ObjectQLComponentSchema on both faces — the TS union in objectql.ts and the zod union in zod/objectql.zod.ts — so AnyComponentSchema, and with it validateSchema / safeValidateSchema / objectui validate, has an arm for object-gallery and object-data-table nodes (objectui#7363).

.changeset/7735-zod-mirrors-stop-authoring-defaults.md

  • names layout.zod.ts → packages/types/src/zod/layout.zod.ts — edited by this change

    What changed. All 41 .default() call sites under packages/types/src/zod/ are removed — layout.zod.ts 22, crud.zod.ts 11, form.zod.ts 5, views.zod.ts 2, app.zod.ts 1. @object-ui/components reconciles the third face objectui#8229 found: flex's registration defaultProps.align seeded 'center', the value its own renderer never applies, so a designer-made node laid out differently from a hand-authored one; it now seeds 'start'.

.changeset/7917-export-breadcrumb-object-tree-zod-schemas.md

  • names objectql.zod.ts → packages/types/src/zod/objectql.zod.ts — edited by this change

    AnyComponentSchema declares 107 node component types. 105 of them could be named on the ./zod barrel — ButtonSchema.safeParse(node), which is what a designer, a form builder or a targeted test needs. The arms declaring type: 'breadcrumb' (navigation.zod.ts) and type: 'object-tree' (objectql.zod.ts) could not: both were already export const in their own module, but index.zod.ts — the package's only zod entry point — did not re-export them, so the schemas existed, were maintained, and were applied by the union while no consumer could name them.

.changeset/8478-describe-line-addresses.md

.changeset/8478-zod-pins-form-layout.md

.changeset/8499-node-slot-registered-arms.md

  • names zod/layout.zod.ts → packages/types/src/zod/layout.zod.ts — edited by this change

    • SemanticElementSchema (zod/layout.zod.ts) — the seven HTML sectioning tags renderers/layout/semantic.tsx registers: aside main header nav footer section article. - HtmlElementSchema (zod/layout.zod.ts) — the 37 safe flow/inline tags renderers/basic/html-elements.tsx registers (h1…h6, p, a, ul, img, …), plus the per-tag keys that module forwards to the DOM (href, target, rel, title, src, alt, width, height, dateTime, cite). - InputShorthandSchema (zod/form.zod.ts) — email / password, the two aliases renderers/form/input.tsx registers onto the input renderer with inputType pinned. inputType is deliberately NOT declared on this arm: the wrapper spreads its own value last, so an authored one is overwritten. - UiCalendarSchema (zod/form.zod.ts) — ui:calendar, the date-picker primitive renderers/form/calendar.tsx registers under exactly that key (skipFallback, because bare calendar belongs to the plugin-calendar view).

.changeset/8505-grid-columns-breakpoint-narrowing.md

.changeset/8516-8556-mirror-partial-record-narrowing.md

  • names zod/layout.zod.ts → packages/types/src/zod/layout.zod.ts — edited by this change

    | key | mirror was | mirror is | | :-- | :--------- | :-------- | | GridSchema.columns (zod/layout.zod.ts) | z.record(z.string(), z.number()) | a PARTIAL record over the six breakpoints | | ReportComponentSchema.exportConfigs (zod/reports.zod.ts) | z.record(z.string(), ReportExportConfigSchema) | a PARTIAL record over ReportExportFormat |

.changeset/8735-objectql-mirror-docblocks-not-defaulted.md

  • names zod/objectql.zod.ts → packages/types/src/zod/objectql.zod.ts — edited by this change

    Correct four zod/objectql.zod.ts docblocks that described the behaviour objectui#8317 removed. Since that change the zod mirrors strip imported @objectstack/spec defaults at this package's import boundary, but the docblocks on HttpRequestSchema, ListColumnSchema, SelectionConfigSchema and PaginationConfigSchema still said, in the present tense, that method, prefix.type, type and pageSize are defaulted on parse — the opposite of what each export does. Each now says the key is declared and accepted but NOT defaulted on parse.

.changeset/8801-object-kanban-allow-collapse-retired.md

  • names packages/types/src/zod/objectql.zod.ts → packages/types/src/zod/objectql.zod.ts — edited by this change

    • the declarations retired here — packages/types/src/objectql.ts and its mirror packages/types/src/zod/objectql.zod.ts; - the pins that assert the retirement — object-kanban-allow-collapse-retired-8801.test.ts and bare-kanban-node-key-retired-8802.test.ts; - a comment in packages/types/src/zod/complex.zod.ts, recording that the deleted retiredZeroReadKanbanKey helper once carried this spelling on the SIBLING arm; - one row of content/docs/api/schema-reference.md; - the .changeset/ release notes that discuss it — this one, the two historical entries covering the sibling arm's own spelling, and objectui#9629's note recording the correction to this paragraph.

.changeset/8871-page-node-refuses-breadcrumbs.md

  • names zod/layout.zod.ts → packages/types/src/zod/layout.zod.ts — edited by this change

    What was measured, on this branch's base 93127bd6f. Zero readers, with a point-access probe rather than a bare word: on that base \.breadcrumbs scores 0 tree-wide (exit 1) against \.breadcrumb\b's 12 files tree-wide (10 under packages/) as the lit control. At head the same two probes read 16 and 13 and \.breadcrumbs is exit 0 over 4 files — every hit one of this branch's own four files (this changeset, the refusal pin, layout.ts, zod/layout.zod.ts) quoting the probe string, and the pin's own exclusions put head back at exit 1. The base reading is the measurement; the head reading is this branch's echo of it. The bare word would have lied — it also names Sentry's own unrelated concept (app-shell/src/observability/sentry.ts) and appears in two comments listing UI surfaces (core/src/utils/record-title.ts, layout/src/NavigationRenderer.tsx), so a bare probe reports five readers that do not exist.

.changeset/8885-object-chart-drilldown-title-compareto.md

  • names packages/types/src/zod/objectql.zod.ts → packages/types/src/zod/objectql.zod.ts — edited by this change

    ObjectChart.tsx reads all three off schema, and until now neither published copy declared any of them: not the TS interface (packages/types/src/objectql.ts) and not the zod mirror (packages/types/src/zod/objectql.zod.ts). They rode BaseSchema's index signature / .passthrough() and arrived unvalidated. drillDown was the sharpest case — this component's registry inputs advertise it to the designer palette, and @objectstack/spec publishes ChartDrillDownSchema for exactly this carrier, so an author was offered a key that neither published shape mentioned.

.changeset/8913-object-kanban-columns-declared.md

  • names zod/objectql.zod.ts → packages/types/src/zod/objectql.zod.ts — edited by this change

    What moved. ObjectKanbanSchema gains columns on both halves that move together — the TypeScript interface (objectql.ts) and its Zod mirror (zod/objectql.zod.ts). Retiring the bare kanban node type key (objectui#8802) removed the only face that judged a lane, and object-kanban had never declared the key, so it rode BaseSchema's [key: string]: any / .passthrough(): read by the renderer at three sites, named by no published face.

.changeset/8990-object-kanban-groupby-optional.md

  • names packages/types/src/zod/objectql.zod.ts → packages/types/src/zod/objectql.zod.ts — edited by this change

    @objectstack/spec declares the key optional — groupBy: z.string().optional() on ObjectKanbanPropsSchema — while this package required it on the TypeScript declaration (packages/types/src/objectql.ts) and on the Zod mirror (packages/types/src/zod/objectql.zod.ts). objectui was therefore narrower than the protocol on a published key: ObjectKanbanSchema.safeParse and safeValidateSchema refused an object-kanban node the protocol accepts, and such a node could not be annotated with its own type.

.changeset/8992-user-actions-collapse-and-docblock.md

  • names objectql.zod.ts → packages/types/src/zod/objectql.zod.ts — edited by this change

    objectql.zod.ts's UserActionsSchema read stripImportedDefaults(Spec).extend({ group, hideFields, rowColor }), an extension that existed only because @objectstack/spec did not declare those three keys while normalizeListViewSchema folded objectui's legacy showGroup / showHideFields / showColor onto them. The protocol adopted all three in 17.3.0 (objectui#5435's ruling), so the extension is now a second local copy of a protocol declaration — the shape two faces start drifting from — and it collapses into the plain by-reference re-export its own note always said it would become.

.changeset/9309-object-gallery-filter-destination-typed.md

  • names zod/objectql.zod.ts → packages/types/src/zod/objectql.zod.ts — edited by this change

    ObjectGallerySchema.filter is typed as the destination its own docblock names — QueryParams['$filter'] — on both faces, the TS interface in objectql.ts and the zod mirror in zod/objectql.zod.ts (objectui#9309).

.changeset/9511-record-id-is-a-string.md

  • names zod/objectql.zod.ts → packages/types/src/zod/objectql.zod.ts — edited by this change

    The three authorable keys, each on BOTH faces. ObjectFormSchema.recordId (objectql.ts + zod/objectql.zod.ts), DetailViewSchema.resourceId (views.ts + zod/views.zod.ts) and DetailSchema.resourceId (crud.ts + zod/crud.zod.ts). ⚠️ The crud pair is DetailSchema, not DetailViewSchema, and it reaches the same renderer — not by symbol but by data flow: plugin-detail registers the 'detail' node type onto DetailView. A read that follows TypeScript symbols alone finds two keys and is incomplete.

.changeset/9549-tree-filter-declared.md

  • names zod/objectql.zod.ts → packages/types/src/zod/objectql.zod.ts — edited by this change

    ObjectTreeSchema.filter is declared on both faces, in the shape objectui#9309 settled for ObjectGallerySchema.filter: QueryParams['$filter'] by indexed access on the TS interface in objectql.ts, and the same two-arm union (array first) on the zod mirror in zod/objectql.zod.ts (objectui#9549).

.changeset/9606-object-kanban-card-title.md

  • names zod/objectql.zod.ts → packages/types/src/zod/objectql.zod.ts — edited by this change

    Both published faces of the object-kanban arm now name the key: the zod mirror ObjectKanbanSchema in zod/objectql.zod.ts and its TypeScript twin, the ObjectKanbanSchema interface in objectql.ts. Both declare it OPTIONAL, at the same requiredness the other face uses, so the two faces accept and refuse the same documents. (Located and cited by SYMBOL: line addresses in zod/objectql.zod.ts have drifted before, and this change is itself about a drifted mirror.)

Read the paragraph, not the line: both false halves of the objectui#8617 claim sat in one paragraph, and correcting either alone would have left it asserting the same wrong thing.

If a claim did go false, correct the body. That is precedented and prose-only, frontmatter untouched; check-changeset-overwrite.mjs will report the correction as its own case 2 ("correcting a declaration on purpose … legitimate"), which is the intended shape — one gate asks for the read, the other records the write.

Not covered, stated so nobody reads this as more: a born-false claim that spells no line address at all (objectui#9495 coordinated one by ORDINAL — "a grep finds that member first" — and deciding that means reading what the sentence means), a claim spelled as a symbol or a package rather than a backticked file name, and a file named ambiguously.

Compared the checked-out tree with 627647874 (merge-base with origin/main): 4 file(s) changed outside .changeset/, read against 1370 pending declaration(s) that publish a body (1945 pending in total). · run

@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 329 chunks) 3043.7 KB 3104.5 KB
Main entry chunk (gzip) 148.3 KB 350 KB
Entry file index-wmZ2lQj3.js —
Status PASS —

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

Package Size Gzipped
app-shell (consoleActionDispatch.js) 0.20KB 0.19KB
app-shell (index.js) 16.68KB 6.20KB
app-shell (runtime-config.js) 20.68KB 7.36KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.06KB 3.86KB
auth (ActiveOrganizationStorage.js) 27.95KB 10.04KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.18KB 10.59KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.15KB 5.39KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.65KB 2.22KB
auth (SocialSignInButtons.js) 9.61KB 3.89KB
auth (UserMenu.js) 3.41KB 1.23KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 40.21KB 10.80KB
auth (createAuthenticatedFetch.js) 8.52KB 3.45KB
auth (index.js) 3.63KB 1.64KB
auth (invitation-status.js) 1.22KB 0.70KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 5.30KB 1.02KB
auth (useWorkspaceAdminStatus.js) 11.08KB 4.58KB
collaboration (CommentThread.js) 26.08KB 7.56KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.49KB 2.64KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.05KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 542.47KB 129.64KB
core (index.js) 8.94KB 3.59KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 224.12KB 62.26KB
fields (index.js) 255.56KB 64.73KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 1.22KB 0.64KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 8.87KB 3.64KB
i18n (index.js) 5.24KB 2.27KB
i18n (pickLocalized.js) 9.86KB 3.95KB
i18n (provider.js) 39.40KB 12.91KB
i18n (useDisplayLocale.js) 3.52KB 1.76KB
i18n (useObjectLabel.js) 34.34KB 9.17KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 39.28KB 11.09KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.75KB
mobile (index.js) 1.99KB 0.87KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 6.62KB 2.45KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 5.52KB 2.10KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 13.52KB 4.88KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.24KB 2.16KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 8.39KB 3.10KB
permissions (index.js) 0.93KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.53KB
permissions (usePermissions.js) 4.83KB 2.27KB
plugin-ai (index.js) 16.01KB 3.93KB
plugin-calendar (index.js) 51.44KB 14.62KB
plugin-charts (index.js) 72.54KB 20.26KB
plugin-chatbot (index.js) 198.27KB 47.18KB
plugin-dashboard (index.js) 133.45KB 35.33KB
plugin-designer (index.js) 216.12KB 44.37KB
plugin-detail (index.js) 260.94KB 67.93KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 146.34KB 37.47KB
plugin-gantt (index.js) 168.47KB 41.53KB
plugin-grid (index.js) 215.22KB 58.85KB
plugin-kanban (index.js) 49.30KB 15.39KB
plugin-list (index.js) 114.36KB 28.24KB
plugin-map (index.js) 22.05KB 7.14KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 43.55KB 11.99KB
plugin-timeline (index.js) 30.72KB 8.96KB
plugin-tree (index.js) 10.74KB 3.76KB
plugin-view (index.js) 85.86KB 21.38KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 11.66KB 3.50KB
providers (index.js) 0.45KB 0.23KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.62KB 2.34KB
react (LazyPluginLoader.js) 4.47KB 1.63KB
react (SchemaRenderer.js) 114.58KB 37.60KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.63KB 2.18KB
react (schema-input.js) 4.25KB 2.04KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (body-dialect.js) 4.78KB 2.09KB
sdui-parser (codegen.js) 6.58KB 2.74KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 5.78KB 2.56KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (kanban-quick-add.js) 3.89KB 1.87KB
sdui-parser (parse.js) 25.28KB 7.80KB
sdui-parser (provenance.js) 3.66KB 1.82KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 18.27KB 6.20KB
types (ai.js) 4.11KB 2.06KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 2.87KB 1.00KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 2.93KB 1.49KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (data-display.js) 3.75KB 1.85KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.85KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (expression.js) 0.20KB 0.18KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (http-inflight.js) 8.87KB 3.73KB
types (http-retry.js) 4.32KB 2.02KB
types (icon-key-migration.js) 4.26KB 1.63KB
types (index.js) 4.74KB 2.25KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 5.00KB 2.39KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 0.20KB 0.18KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (select-option.js) 0.20KB 0.19KB
types (spec-report.js) 5.05KB 1.93KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (strict-authoring-face.js) 17.15KB 6.32KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.28KB 2.87KB
types (ui-action.js) 8.11KB 3.32KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: a4835b65b6ddadb898546581e24fa7bb0af1fb88

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; git diff a707197..a4835b6 (5 files, +298/-2; git diff --stat 843e125 a4835b6 = 1 changeset line); @objectstack/spec@17.4.0 from npm pack, executed with zod@4.4.3 in scratch; objectstack 5581d30 via git show, read-only; check-runs on the head re-read at the end (43 total: 39 success, 3 skipped, 1 failure).

① Derived judgments

Census — RIGHT. Measured on the published 17.4.0 package (scratch census.mjs, reading _zod.def.checks.length): NavigationAreaSchema 0, AppSchema 0, DashboardWidgetSchema 0, DashboardSchema 0, PageSchema 1, ListViewSchema 2, GlobalFilterSchema 1. api-surface/ui.json lists exactly four check* (function) entries: checkGlobalFilterDateDefaultValue, checkListViewCalendarVisualization, checkListViewPageMount, checkPageSourceCompleteness. Source cross-check (grep -n "superRefine\|\.refine(" src/ui/{view,page,app,dashboard}.zod.ts): app.zod.ts's only superRefine is nested inside navigation (objectNavTargetExclusivity, line 727); dashboard.zod.ts's only one is on GlobalFilterSchema (line 876); no object-level check on the four zero rows. The PR table matches the spec.

checkListViewCalendarVisualization — RIGHT, same semantics. The spec function returns unless appearance.allowedVisualizations is an array containing 'calendar', then refuses at path calendar iff view.calendar === undefined. On objectui's node appearance flows by reference through specFieldsExcept (not in LIST_VIEW_LOCAL_OVERRIDES); the local override is calendar: CalendarConfig.optional() where CalendarConfig = stripImportedDefaults(SpecCalendarConfigSchema).partial().extend({...}).passthrough() — no default, so absence stays undefined after parse; BaseSchemaCore declares neither calendar nor appearance. Probe under those field shapes: absent calendar refused, calendar: {} accepted. Residual, pre-existing, FIELD-level and outside B1: calendar: {} passes objectui (partial) but the spec refuses it (startDateField required); the changeset's remedy names startDateField, which is the spec-correct fix.

checkPageSourceCompleteness — RIGHT, fields unchanged. Reads kind and source. PAGE_SPEC_EXCLUDED = name, label, description, type, regions; the PageNodeSchema extend body redeclares type, actions, breadcrumbs, title, icon, description, pageType, object, template, variables, regions, body, children, isDefault, assignedProfiles — neither kind nor source; BaseSchemaCore overlaps only on type. The spec's kind default 'full' is stripped by stripImportedDefaults; the function returns early on kind === undefined, so an absent kind gives the spec's own verdict. Probe: {} ok, {source:''} ok, {kind:'html'} refused at source, {kind:'full'} ok.

checkListViewPageMount refusal — DEFENSIBLE under the ruling's words; residual disclosed, not a defect. The function sets isPageMount = view.type === 'page'; on objectui's node type is the literal 'list-view' and the spec kind rides as viewType, so attached as-is it can never see a page mount and refuses every non-empty pageName (the PR's own pin measures this). The node does not carry type in the spec's sense, so "re-attaches the checks whose fields it carries" is satisfied by not attaching. A one-line adapter (viewType renamed to type before the call) would have carried it faithfully, but the kind it polices is retired on objectstack main: at 5581d30 git grep -c "export function checkListViewPageMount" -- packages/spec/src = 0 (one docblock mention remains: 'page' leaves the list-view type enum in 17.5.0), objectstack main's api-surface/ui.json lists 5 check functions without it, and objectui's UNDRAWABLE_VIEW_KINDS (packages/core/src/utils/normalize-list-view.ts) already classes page as undrawable. Residual at 17.4.0 (spec refuses, objectui accepts): viewType 'page' with no pageName; pageName on a non-page viewType; a page mount with non-empty columns. Leaving it is correct for this card.

zod 4 hazard — NO BREAK. Measured at zod 4.4.3 on an object carrying a superRefine: .pick(), .omit(), .partial(), .merge() and key-overwriting .extend() THROW ("cannot be used on object schemas containing refinements"); non-overwriting .extend(), .safeExtend(), .shape, .strict(), .passthrough(), .loose(), .keyof(), .catchall(), .required(), discriminatedUnion membership and z.toJSONSchema all work. git grep -E "\b(ListViewSchema|PageNodeSchema)\s*\.\s*(pick|omit|partial|merge|extend|safeExtend|required|strict|passthrough|loose|keyof|catchall|refine|superRefine|transform|pipe|shape|def|_def|_zod)\b" over packages/, apps/, examples/, scripts/ (tests included) hits only .shape reads (Object.keys(PageNodeSchema.shape) in two tests; ListViewSchema.shape.exportOptions in comments). Value imports outside packages/types: 3 tests plus packages/runner (type positions only). .superRefine is the last call in both chains; AnyComponentSchema is a z.discriminatedUnion('type', ...); metadata-admin's z.toJSONSchema calls take the spec's PageSchema/DashboardSchema, not objectui's. The three pick/omit sites in the zod dir (ChatbotSchema.pick, InputSchema.omit, ObjectGridSchema.omit(...).partial()) are on unrelated schemas.

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 checkListViewPageMount from @objectstack/spec/ui (line 47), a symbol objectstack main has already removed, so against main the file does not compile (TS2305) — a red that is not "by site name" and lands before any bump. imported-defaults-8317.test.ts is safe: both names it adds still exist on main (export-def-count 2 each at 5581d30).

Tripwire. Base a707197 has no ListView tripwire (git grep allowedVisualizations -- packages/types/src/__tests__ hits only p2-spec-exports, which parses the spec's AppearanceConfigZod, and p1-spec-alignment with no 'calendar'); commit order b46d8b3 (test only) then 0731fa0 (fix) matches the unlock comment's red-first instruction. At base ListViewSchema carried no object check, so the pin's expect(success).toBe(false) necessarily failed there. Not re-run.

② Semver level

minor is the correct declaration. AGENTS.md §版本号策略 (line 261 at head): changesets must not declare major; objectui's own breaking changes are marked minor with the breaking semantics stated in the body — enforced by scripts/check-changeset-no-major.mjs; check-run "Changeset Bump Policy" success, "Changeset Declaration" success. @object-ui/types is in the fixed group of .changeset/config.json (39 packages), so the minor lifts the group as the rule intends. Changeset prose verified true point by point: safeValidateSchema and validateSchema exist (index.zod.ts lines 541, 515); both shapes were already refused by spec 17.4.0 (checks attached at view.zod.ts line 2139 and page.zod.ts line 787); paths calendar / source correct; the four zero rows and the GlobalFilter row correct; the remedy calendar: { startDateField: 'DATE_FIELD' } names the spec's one required key. One over-claim: the last paragraph says a later-added check "fails there by site name" — true of the vitest census, not of the compile-level dependency in ③-1.

③ Boundary flags

  1. Spec Main Shape Gate turned green to red by this PR, undeclared — BLOCKING. On head a4835b6 the check-run is completed / failure (job 107888507933); on main f475557 and on merge-base a707197 it is success. Annotation: packages/types/src/__tests__/spec-object-refinements-7715.test.ts:47 TS2305: Module '"@objectstack/spec/ui"' has no exported member 'checkListViewPageMount'. — compiled against objectstack-ai/objectstack@7e6ca1787aa9. The gate runs pnpm type-check, which for @object-ui/types includes tsc -p tsconfig.test.json (include src/**/*.test.ts). It is not in ruleset 11776024's required set (Lint, Type Check, Build & E2E, Build Docs, Changeset Declaration, Test), but scripts/dependabot-merge-gate.mjs calls it "an ordinary blocking check" whose enrolment waits on "the day-one break clears"; merging this re-creates that break on every objectui PR until the 17.5.0 bump, and attributes it to an objectstack commit that predates this PR. The PR body lists 13 gates with open_questions: [] and never names this one, although the dev cites 5581d30 as the commit where the export is gone. The fix is confined to the test file: remove the static import of the retired symbol (resolve the binding at runtime, or measure the not-attachable row without importing it) and keep the census rows.
  2. hotcrm NOT MEASURED. Ruling precondition 3 names hotcrm, and the calendar rule's origin measurement WAS hotcrm (spec docblock: measured on hotcrm pinned at @objectstack/* 17.1.0, all 9 leave requests on one cell), so the one corpus known to have authored the refused shape is the one without a count. Declared honestly; not reachable from my inputs either. The narrowing only moves a refusal hotcrm already meets at the spec's publish door, so a flag for the maintainer, not a stop.
  3. apps/examples/content = 0 is consistent with git grep -n allowedVisualizations -- apps examples content (0 hits at head); the parse leg was not independently re-run.
  4. Declared deviations (2 of 6 sites attach; a test-file edit inside the claimed surface) are correct. PR is draft; base is 5 commits behind main, 0 of them touching the 5 changed paths; main's spec pin unchanged at ^17.4.0 / lock 17.4.0; needs:contract-review not yet on the PR (labels: package: types, tests). All other 39 check-runs completed success, 3 skipped (coverage shards, dependabot).

Implemented-by: claude/issue-7715-mirror-carries-spec-refinements
Reviewed-by: session_014mXUNuFomfj24w7s1pZzhN

VERDICT: FAIL — ③-1: the new census pin's static import of checkListViewPageMount (a symbol objectstack main has retired) turns the Spec Main Shape Gate from success on main to failure on this head, undeclared in the PR; the derived accept-set changes, the semver level and the changeset prose are otherwise correct, and the fix is limited to the test file's import.

…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>
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 329 chunks) 3043.7 KB 3104.5 KB
Main entry chunk (gzip) 148.3 KB 350 KB
Entry file index-CTs6DAv4.js —
Status PASS —

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

Package Size Gzipped
app-shell (consoleActionDispatch.js) 0.20KB 0.19KB
app-shell (index.js) 16.68KB 6.20KB
app-shell (runtime-config.js) 20.68KB 7.36KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.06KB 3.86KB
auth (ActiveOrganizationStorage.js) 27.95KB 10.04KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.18KB 10.59KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.15KB 5.39KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.65KB 2.22KB
auth (SocialSignInButtons.js) 9.61KB 3.89KB
auth (UserMenu.js) 3.41KB 1.23KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 40.21KB 10.80KB
auth (createAuthenticatedFetch.js) 8.52KB 3.45KB
auth (index.js) 3.63KB 1.64KB
auth (invitation-status.js) 1.22KB 0.70KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 5.30KB 1.02KB
auth (useWorkspaceAdminStatus.js) 11.08KB 4.58KB
collaboration (CommentThread.js) 26.08KB 7.56KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.49KB 2.64KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.05KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 542.46KB 129.64KB
core (index.js) 8.94KB 3.59KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 224.12KB 62.26KB
fields (index.js) 255.56KB 64.73KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 1.22KB 0.64KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 8.87KB 3.64KB
i18n (index.js) 5.24KB 2.27KB
i18n (pickLocalized.js) 9.86KB 3.95KB
i18n (provider.js) 39.40KB 12.91KB
i18n (useDisplayLocale.js) 3.52KB 1.76KB
i18n (useObjectLabel.js) 34.34KB 9.17KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 39.28KB 11.09KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.75KB
mobile (index.js) 1.99KB 0.87KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 6.62KB 2.45KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 5.52KB 2.10KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 13.52KB 4.88KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.24KB 2.16KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 8.39KB 3.10KB
permissions (index.js) 0.93KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.53KB
permissions (usePermissions.js) 4.83KB 2.27KB
plugin-ai (index.js) 16.01KB 3.93KB
plugin-calendar (index.js) 51.44KB 14.62KB
plugin-charts (index.js) 72.54KB 20.26KB
plugin-chatbot (index.js) 198.27KB 47.18KB
plugin-dashboard (index.js) 133.45KB 35.33KB
plugin-designer (index.js) 216.12KB 44.37KB
plugin-detail (index.js) 260.94KB 67.93KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 146.34KB 37.47KB
plugin-gantt (index.js) 168.47KB 41.53KB
plugin-grid (index.js) 215.43KB 58.90KB
plugin-kanban (index.js) 49.30KB 15.39KB
plugin-list (index.js) 114.36KB 28.24KB
plugin-map (index.js) 22.05KB 7.14KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 43.55KB 11.99KB
plugin-timeline (index.js) 30.72KB 8.96KB
plugin-tree (index.js) 10.74KB 3.76KB
plugin-view (index.js) 85.86KB 21.38KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 11.66KB 3.50KB
providers (index.js) 0.45KB 0.23KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.62KB 2.34KB
react (LazyPluginLoader.js) 4.47KB 1.63KB
react (SchemaRenderer.js) 114.58KB 37.60KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.63KB 2.18KB
react (schema-input.js) 4.25KB 2.04KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (body-dialect.js) 4.78KB 2.09KB
sdui-parser (codegen.js) 6.58KB 2.74KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 5.78KB 2.56KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (kanban-quick-add.js) 3.89KB 1.87KB
sdui-parser (parse.js) 25.28KB 7.80KB
sdui-parser (provenance.js) 3.66KB 1.82KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 18.27KB 6.20KB
types (ai.js) 4.11KB 2.06KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 2.87KB 1.00KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 2.93KB 1.49KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (data-display.js) 3.75KB 1.85KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.85KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (expression.js) 0.20KB 0.18KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (http-inflight.js) 8.87KB 3.73KB
types (http-retry.js) 4.32KB 2.02KB
types (icon-key-migration.js) 4.26KB 1.63KB
types (index.js) 4.74KB 2.25KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 5.00KB 2.39KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 0.20KB 0.18KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (select-option.js) 0.20KB 0.19KB
types (spec-report.js) 5.05KB 1.93KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (strict-authoring-face.js) 17.15KB 6.32KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.28KB 2.87KB
types (ui-action.js) 8.11KB 3.32KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 65df5b381c57e402e6e406968bbd6a692c331e6e

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; git diff f475557..65df5b3 (merge-base is now f475557: 5 files, +302/-2) and git diff a4835b6..65df5b3 -- packages/types .changeset (1 PR file, +41/-37, plus three changesets that arrived from main); @objectstack/spec@17.4.0 via npm pack, executed under zod@4.4.3 in scratch (census.mjs); objectstack local clone at 5581d30 (2026-09-24T23:00Z) via git show; check-runs on the head polled twice (first: 1 in_progress; last: 43 total, 40 success, 3 skipped, 0 running).

① Derived judgments

Merge commit 8d62d1b — clean, carries nothing new. git show --cc 8d62d1b prints no conflict-resolution hunk. Diff against parent 2 (f475557) is exactly the PR's 5 files and is byte-identical to git diff a707197 a4835b6 (diff of the two outputs: empty). Diff against parent 1 equals main's advance a707197..f475557 (21 files, +734/-47), none of which touch packages/types, pnpm-lock.yaml or the root package.json; spec pin unchanged (^17.4.0, lock 17.4.0). Main has since moved 3 commits to 3335767; git diff --stat f475557 refs/review2/main -- packages/types .changeset/7715-* is empty.

Zod sources and changeset unchanged since a4835b6 — VERIFIED. git diff a4835b6 65df5b3 --stat -- packages/types/src/zod .changeset/7715-mirrors-carry-spec-object-checks.md packages/types/src/__tests__/imported-defaults-8317.test.ts prints 0 lines. The only PR-side change in round 1 is spec-object-refinements-7715.test.ts.

Census — RIGHT, re-measured on the packed 17.4.0. _zod.def.checks.length: NavigationArea 0, App 0, DashboardWidget 0, Dashboard 0, Page 1, ListView 2, GlobalFilter 1. Object.keys(await import('@objectstack/spec/ui')).filter(/^check[A-Z]/) yields exactly 4 names, all typeof function: checkGlobalFilterDateDefaultValue, checkListViewCalendarVisualization, checkListViewPageMount, checkPageSourceCompleteness; api-surface/ui.json in the same tarball lists the same 4 (function) entries. Table in the PR matches.

The two attached checks — RIGHT, unchanged since round 0. Probed on 17.4.0: spec refuses appearance.allowedVisualizations: ['calendar'] with no calendar block at path calendar (code custom) and accepts it with calendar: { startDateField }; the exported function run on objectui's node spelling gives the same verdict, and calendar: {} passes (the pre-existing field-level residual round 0 recorded). checkPageSourceCompleteness: kind: 'html' no source and kind: 'react' whitespace source refused at source; sourced html, {} and kind: 'full' accepted on both faces. objectql.zod.ts and layout.zod.ts chain .superRefine(spec function) as the last call; the spec's own function runs, no copy.

checkListViewPageMount not attached — still DEFENSIBLE. On 17.4.0 the spec accepts { type: 'page', pageName: 'home_page', columns: [] }, and the exported function run on objectui's spelling (type: 'list-view', viewType: 'page', pageName) refuses at pageName with the spec's own message. On objectstack 5581d30: export function checkListViewPageMount count 0 in packages/spec/src/ui; view.zod.ts line 2461 records 'page' removed from the list-view type enum in 17.5.0. The residual (viewType page with no pageName; pageName on a non-page view) stays accepted by objectui at 17.4.0 and disappears at the bump. Disclosed in changeset and PR; not a defect for this card.

Round-1 change: run-time read of check* through dynamic import('@objectstack/spec/ui') — keeps the census's intent. No check* name is bound at compile time in that file (grep: zero static imports of check; mod is typed object, so no member of the namespace type is referenced). Against a spec that adds a check, the it.each row test compares _zod.def.checks.length with attached.length + notAttachable.length per site and fails by site name; against one that drops checkListViewPageMount, the ListView row (2 vs 1), the export-set pin and the page-mount measurement (with an explicit remedy message) go red at run time. Consistent with objectstack 5581d30, where dashboard.zod.ts chains checkDashboardWidgetStageOrder and checkDashboardWidgetMetricMeasureArity on DashboardWidgetSchema (lines 1153, 1157), so the DashboardWidget row will fail 0 vs 2 by name at the next bump. Compilation no longer depends on any check name.

Same-module control — SOUND. Reflect.get(mod, 'ListViewSchema') === SpecListViewSchema measured true under Node ESM in scratch; in vitest both the static and dynamic import of one specifier resolve through one module graph, and the packed exports['./ui'].import is a single index.mjs. An alias (OBJECTSTACK_SPEC_DIST) would move both together.

objectui#3090 rule — intent HONOURED, convention SIDESTEPPED (non-blocking). The rule is eslint.config.js line 247: no-restricted-imports on @objectstack/spec/ui with importNames: ['FormField', 'FormFieldSchema'] (the spec form-VIEW vocabulary whose type erases to any). With importNames set, ESLint also reports the static import * as form, which is why the repository's existing whole-surface census packages/types/src/__tests__/spec-ui-schema-reexports.test.ts line 24 carries // eslint-disable-next-line no-restricted-imports with a "sanctioned importer (#3090 tripwire)" note. The 7715 file's dynamic import() is invisible to that rule. Its read is regex-filtered to check[A-Z] names, never touches FormField or FormFieldSchema, and mod: object discards the namespace type, so nothing 3090 guards against crosses the boundary: the rule's purpose is met. But a static namespace import with the same visible disable would have compiled equally well against any spec (a namespace import fails on no missing name), so the dynamic form buys nothing ③-1 needed and drops the audit trail the repository's precedent leaves. The repository also has dynamic-import precedent for spec entry points in tests (app-shell/src/__tests__/spec-symbol-parity.test.ts line 328, clientValidation.optOuts.test.ts lines 434 to 441), and the Lint check-run is success, so this is a convention note for the seat, not a stop. The other rules are clear: object-ui/no-dynamic-import-in-test-hook (the import sits in an it body, not a hook); AGENTS.md #6 concerns loading components; the AGENTS.md 244 to 247 timeout hazard does not apply because the same specifier is already statically imported at module scope, so the dynamic call resolves from cache.

8317 census entries and the mirrors' own static imports name only checkListViewCalendarVisualization and checkPageSourceCompleteness, both export function at objectstack 5581d30 (view.zod.ts 2585, page.zod.ts 633). That compile-time dependency is inherent to B1 (a mirror must import a check to attach it) and is the shape the gate exists to report on.

② Semver level

minor on @object-ui/types is correct. AGENTS.md line 261 at head: changesets never declare major; objectui's own breaking changes are minor with the breaking semantics in the body; enforced by scripts/check-changeset-no-major.mjs (present at head); @object-ui/types is in the fixed group of .changeset/config.json (now 40 packages). Changeset Declaration, Changeset Bump Policy, Changeset Fixed Group Check all success on the head. The changeset file is byte-identical to a4835b6, and its prose remains true point by point at this head; the last sentence ("a check the spec adds in a later release fails there by site name instead of being dropped") is now true without qualification: round 0's over-claim was that compilation would fail first, and the round-1 file has no compile-time dependency on any check name, so the failure is the row's, by site name, at run time.

③ Boundary flags

  1. Prior ③-1 RESOLVED. Spec Main Shape Gate on head 65df5b3: completed / success, job 107893979130, started 2026-09-25T00:34:44Z, completed 00:47:04Z (on a4835b6 the same context was completed / failure, job 107888507933). Its only annotation is the ubuntu-latest migration notice; the check-run output does not expose the objectstack sha compiled against (the CI-named 7e6ca1787aa9 is newer than my local objectstack clone at 5581d30). The fix is confined to the test file. No gate or test was re-run by this review.
  2. All 43 check-runs concluded: 40 success, 3 skipped (2 coverage shards, dependabot); none in progress at the final poll. Lint, Type Check, Test and all 8 shards, Build & E2E, Build Docs success.
  3. No new risk from the merge: clean three-way merge, PR content byte-identical across it, main's advance touches no PR path, spec pin unchanged.
  4. hotcrm still NOT MEASURED for ruling precondition 3 (declared honestly in the PR body; unreachable from my inputs too). The narrowing only moves a refusal hotcrm already meets at the spec's publish door, so a maintainer flag, not a stop.
  5. Housekeeping for the seat, not stops: PR is still draft: true; needs:contract-review is not on the PR (labels: package: types, tests); the PR body's section heading "Local verification (head 843e125)" is stale although its bullets name 65df5b3; the 3090 convention note in ① (a visible eslint-disable is the repository's sanctioned form for a whole-surface read).

Implemented-by: claude/issue-7715-mirror-carries-spec-refinements
Reviewed-by: session_014mXUNuFomfj24w7s1pZzhN

VERDICT: PASS

@os-elon-musk
os-elon-musk marked this pull request as ready for review September 25, 2026 00:55
@os-elon-musk
os-elon-musk added this pull request to the merge queue Sep 25, 2026
Merged via the queue into main with commit 309c75e Sep 25, 2026
44 checks passed
@os-elon-musk
os-elon-musk deleted the claude/issue-7715-mirror-carries-spec-refinements branch September 25, 2026 02:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

2 participants