Skip to content

refactor(types): derive the published JoinedReportBlock from the installed spec (objectui#10940) - #11288

Merged
objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/issue-10940-joined-report-block-from-spec
Oct 1, 2026
Merged

objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/issue-10940-joined-report-block-from-spec

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #10940

Clause-②: yes
Why yes: the published @object-ui/types JoinedReportBlock changes shape in both directions. The local-only objectName / groupingsDown / groupingsAcross / filter / chart and the index signature leave (a narrowing). The spec-only dataset / rows / values / runtimeFilter / order arrive (a widening).

What changed

  • packages/types/src/spec-report.ts. The hand-written interface JoinedReportBlock is gone. JoinedReportBlock is now the spec's own JoinedReportBlock from @objectstack/spec/ui, which is the INPUT shape of JoinedReportBlockSchema. It is imported under an alias and re-exported as a type alias, the same idiom the neighbouring SpecReport / SpecReportInput use. JoinedSpecReport.blocks is now NonNullable OF SpecReport['blocks'], the spec's own Report.blocks, made required.
  • report-chart-query-spec-parity.test.ts, the JoinedReportBlock section.
    • The OnTypedSpec licence is deleted. It was the last erased arm: on an erased spec it answered true for every probe.
    • The local-vs-spec divergence probes are replaced. The new pins cover the derivation (the published block IS the spec block, declared key sets included, and JoinedSpecReport.blocks IS the spec's parsed Report.blocks) and the member-by-member change, asserted on the published type.
    • Every member lookup is guarded with [BlockMember OF …] extends [never] ? false : ….
    • The file docblock and the test-time tripwire's comment record the burn-down.
  • scripts/check-spec-symbol-derivation.mjs. The @object-ui/types:JoinedReportBlock ALLOW row is deleted. The case-2b comment that pointed at that row now records the burn-down.
  • spec-derived-unions.test.ts. The batch-8 ledger note that said "STILL erased" now records the burn-down.
  • .changeset/10940-joined-report-block-from-spec.md. @object-ui/types minor, with the member-by-member change.
  • .changeset/3162-types-ledger-batch8-verdicts.md (pending). A dated, append-only note: in this same release, its "still erased" verdict for this symbol no longer holds. The frontmatter is byte-identical.

Member by member (installed spec 17.5.0)

member before (hand-written) after (the spec's type) direction
name required string required string unchanged
type optional 'tabular' | 'summary' | 'matrix' the same; the schema defaults it to tabular unchanged
label, description string, or { default, translations? } string, or an inline locale map (every value a string) narrowed
columns REQUIRED array of column objects { field, label?, aggregate?, … } optional string[]: dimension names across a matrix changed concept
objectName, groupingsDown, groupingsAcross, filter, chart declared absent; the spec's closed schema refuses all five (chart by name, objectstack#20161) removed
index signature [k: string]: unknown present absent removed
dataset, rows, values, runtimeFilter, order absent declared added

Two tier choices, and why

  • JoinedReportBlock is the INPUT tier. The spec exports this name with that meaning. A symbol with the spec's name but a different tier is the planted premise that check:spec-symbols exists to stop, and the rc.6 note in spec-report.ts records what a silent tier swap costs. The hand-written interface was input-shaped too (type optional), so type stays optional.
  • JoinedSpecReport.blocks is on the PARSED tier. JoinedSpecReport is built on SpecReport, which is ReportParsed, and the spec's Report.blocks already carries the typed block on that tier. So blocks derives from Report.blocks by reference. Writing blocks: JoinedReportBlock[] would have intersected the input block with the parsed one that SpecReport already carries. Each parsed element can be assigned to JoinedReportBlock, but not the reverse; a pin asserts both directions.
  • Context. On main today, with 17.5.0 installed, JoinedSpecReport['blocks'][number] is the old interface intersected with the spec's parsed block. For example, its columns is { field, … }[] & string[]. This change leaves one shape.

Premises, re-measured on this worktree (base e420df31)

  • H1 holds. The instrument is the tripwire's own. SpecTyping OF SpecJoinedReportBlock satisfies 'typed' compiles on the unmodified tree: the types type-check exits 0. The TypeScript-API reading that installedSpecTyping takes gives: installed @objectstack/spec 17.5.0, JoinedReportBlock: typed, and the lit control ReportSort: typed.
  • H2 holds, with one amendment. At e420df31 the interface, the ALLOW row and the "Burnable now, on its own card" docblock were all present. But the bump (PR chore(deps): resolve @objectstack/* 17.5.0, and the zod 4.6.5 it requires, in pnpm-lock.yaml (objectui#11073) #11086, objectui#11073) had already cut the state pin down to typed alone. What remained of the erased arm was the OnTypedSpec licence, and this PR deletes it.
  • H3 holds. A git grep for JoinedReportBlock, JoinedSpecReport and isJoinedSpecReport over packages apps content examples scripts finds one hit outside packages/types: a comment in plugin-report/src/DatasetReportRenderer.tsx. That renderer types its blocks with its own local DatasetReportLike. Positive control: the grep hits inside packages/types. No dependant's program names these symbols, so the downstream tsc sweep is a declared narrowing left to CI's turbo run type-check. The reverse check below compiles through the published entry.
  • H4 holds. The member table was re-derived against the installed 17.5.0 through the TypeScript API. It matches the card's table exactly.

The two items the contract review owed (the review comment on objectui#10940)

  1. Each probe goes red by itself. Every member lookup is guarded [BlockMember OF …] extends [never] ? false : …, and label and description are now separate probes. Ablation B below shows a dropped member turning its own probe red.
  2. The erased arm is deleted, not flipped. OnTypedSpec is gone, so no probe is licensed by an erased spec. The state pin admits typed alone; that half landed at the bump.

Verification, at 4806a1c16

gate result
@object-ui/types type-check (tsc --noEmit, examples, tests) exit 0, 0 TS errors
vitest run packages/types/ 298 files, 7465 tests passed
the gate script's own suite (the 12 scripts/__tests__ files that name check-spec-symbol-derivation) 12 files, 343 tests passed
type-check:scripts exit 0
pnpm --filter @object-ui/types build exit 0; dist completeness verified (138 files)
eslint over the four touched lintable files (objectui's invocation, JSON) 4 files, 0 errors, 0 warnings
check:spec-symbols exit 0
check:installed-pin-claims, check:new-line-citations (0 new), check:control-bytes exit 0
check:changeset-claims, check:pending-changeset-literals exit 0
check:test-path-roots, type-check:coverage exit 0
check-changeset-presence, check-changeset-no-major exit 0
check-governed-queue-guard --test over the six paths NOT GOVERNED
  • The eslint narrowing is a measurement, not a skip.
    • The four files are inside the root eslint.config.js population: all four came back linted.
    • The count comes from the JSON output.
    • The config has no type-aware setting (zero hits for projectService, parserOptions and typeChecked), and no rule under eslint-rules/ reads the disk. So this diff cannot move any other file's verdict.
    • With --no-inline-config, one error appears, at an untouched and sanctioned eslint-disable block in spec-derived-unions.test.ts. objectui CI never passes that flag (zero hits in workflows and package scripts; the control is lit).

Ablations. Each ran once, through objectstack's scripts/ablation-replace.mjs in WRAP mode, which verifies the mutation on disk and proves the restore against the HEAD blob. None is kept as a test.

  • Control, at HEAD: tsc -p tsconfig.test.json exits 0, and check:spec-symbols exits 0.
  • A: the hand-written interface restored.
    • tsc exits 2, with 8 probes red: _publishedBlockIsTheSpecBlock, _joinedReportBlocksAreParsedTier, _legacyBlockKeysAreGone, _publishedBlockHasNoIndexSignature, _datasetBoundBlockKeysArrived, _blockColumnsAreDimensionNames, _blockLabelRefusesTheLegacyObjectArm and _blockDescriptionRefusesTheLegacyObjectArm.
    • check:spec-symbols exits 1: "a spec-named symbol is hand-written, not derived: interface JoinedReportBlock".
  • B: label dropped from the published block (an Omit of the spec type).
    • tsc exits 2, with exactly two probes red. One is _blockLabelRefusesTheLegacyObjectArm, on its own line. The other is _publishedBlockIsTheSpecBlock, and only through the declared-key-set comparison that 4806a1c16 added: mutual assignability alone stays green when an optional key is dropped.
    • The description probe stays green.
    • check:spec-symbols exits 0, because an Omit of the spec type counts as derived.
  • C: the retired ALLOW row re-added over the derived type.
    • check:spec-symbols exits 1: "@object-ui/types:JoinedReportBlock is in ALLOW but no longer collides with a spec export name". So the row's removal is enforced by the gate, not by prose.
    • ⚠️ The first attempt at C was a no-op, and its reading was discarded. Its replacement contained its own anchor, so the tool refused (anchor count 1 before and 1 after) and the child command never ran. It was re-run with an anchor the replacement does not contain.
  • Every leg restored. The blob equals the HEAD blob, git diff HEAD is empty, and porcelain is 0.

Reverse check through the built declarations. This ran after the types build. A probe in packages/plugin-report/src imports JoinedReportBlock from @object-ui/types; that package's tsconfig resolves @object-ui/types through dist.

  • A spec-shaped block compiles (exit 0). --listFiles shows it read packages/types/dist/spec-report.d.ts, not the source.
  • The same literal with a pasted objectName fails with TS2353.
  • The first run was void: tsc refused file arguments beside a tsconfig (TS5112) before reading anything. It was re-run with --ignoreConfig. The probe file is removed.

NOT MEASURED

  • check:spec-floors and check:readme-exports. Both need every package's dist, and this fresh worktree built only @object-ui/types. The floor gate reported 20 packages no-artifact, and readme-exports reported "the population COLLAPSED -- this run proves nothing". Left to CI.
  • The downstream type-check of @object-ui/types' dependants. This is a declared narrowing; see H3.

Acceptance notes

  • File surface. The claim's surface holds. Two edits sit inside files it names, and both keep text true: the case-2b comment beside the retired ALLOW row, and the tripwire's docblock. One edit sits outside the named paths: the dated note on the pending 3162 changeset. The dispatch's pending-changeset clause sanctions it.
  • Docs. No README or guide names this type (checked with git grep). content/docs/plugins/plugin-report.mdx already documents joined blocks in the spec's dataset-bound shape.
  • Runtime. Nothing changes at runtime. isJoinedSpecReport and every renderer are untouched.

Dispatched by the domain:spec @ objectui seat. Session: https://claude.ai/code/session_01VhxTqosz7wn54ahqyxgERT.


Generated by Claude Code

…alled spec (objectui#10940)

`@objectstack/spec` 17.5.0 types `JoinedReportBlockSchema`, so the
hand-written interface that typed the legacy inline-query block is
replaced by the spec's own `JoinedReportBlock` (the input shape), and
`JoinedSpecReport.blocks` by the spec's own `Report.blocks`, made
required.

The parity test's JoinedReportBlock section drops the `OnTypedSpec`
licence (the residual `erased` arm) and the local-vs-spec divergence
probes, and pins the derivation plus the member-by-member change on the
published type, each member lookup guarded against `never`. The
`check:spec-symbols` ALLOW row for the symbol retires; the pending
objectui#3162 changeset gets a dated note.

Claude-Session: https://claude.ai/code/session_01VhxTqosz7wn54ahqyxgERT
Co-authored-by: Claude <noreply@anthropic.com>
…ation pin

Mutual assignability cannot see an optional key dropped or added, and
every block member but `name` is optional, so the derivation pin also
compares the published and spec blocks' declared key sets.

Claude-Session: https://claude.ai/code/session_01VhxTqosz7wn54ahqyxgERT
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

changeset-claim-re-read

⚠️ 5 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/10867-app-wizard-saves-spec-app.md

  • names spec-derived-unions.test.ts → packages/types/src/__tests__/spec-derived-unions.test.ts — edited by this change

    • A separator carries only type, id and order. @objectstack/spec's separator branch declares exactly those keys, and its AppSchema refuses anything else. NavigationItem required a label on every item, so the wizard's "Add separator" wrote { id, type: 'separator', label: '' }, and the console's create-app and edit-app saves were refused with 422 INVALID_METADATA (unrecognized_keys ['label'] at navigation.N). NavigationItem is now a union of two arms, discriminated by type. NavigationEntryItem holds every other nav type and keeps its required label. NavigationSeparatorItem admits type, id and order, and every other entry key is ?: never on it. Both arms are exported. Reading an entry-only key off an unnarrowed item still compiles, but its type now includes undefined (a label is string | undefined), so passing it where a string is required does not. Narrow on item.type === 'separator' before relying on label. menuItemToNavigationItem maps a legacy separator to { id, type: 'separator' } and drops its label. spec-derived-unions.test.ts no longer pins the separator label as a blocker. It asserts, at both spec tiers, that the separator arm admits the spec separator's keys and no others. - @object-ui/plugin-designer: the wizard and NavigationDesigner write a separator as { id, type }. NavigationDesigner no longer writes a label onto a new separator, and its label, icon and visibility patchers skip one. - @object-ui/layout narrows on the separator arm; nothing it renders changes. resolveNavItemLabel answers '' for every separator. A stored separator carrying a non-empty label, which menuItemToNavigationItem produced before this change, used to resolve to that label. No renderer asks it for a separator's label. The mobile bottom nav's leaf list, which already skipped separators, is now typed as entries. - @object-ui/app-shell narrows the same way; nothing it renders changes. useNavPins registers a favorite only for an entry and leaves a separator as it is, and the Studio sidebar's navigation walk passes a separator through unchanged. - @object-ui/plugin-designer: EditAppPage keeps the stored branding. The wizard maintains the logo, primary colour and favicon, and its branding replaced the stored block, so a stored accentColor was dropped on every edit. The console reads that key. The save now keeps every stored branding key the spec's AppBrandingSchema declares, read from that schema. The wizard's values win for the keys it maintains. A stored key the spec does not declare is still left out. - The wizard's Layout control is removed, with AppWizardDraft.layout. The spec declares no app layout, no console surface reads one, and since objectui#10842 the save wrote none. The control persisted nothing. EditAppPage no longer reads a stored layout into the draft. The Basic Info step's description now reads "Name, title, and icon". - @object-ui/i18n: the four appDesigner layout keys (layout, layoutSidebar, layoutHeader and layoutEmpty) are removed from all ten packs, and appDesigner.stepBasicDesc no longer names a layout.

.changeset/6286-explain-batch-cap-from-spec.md

  • names scripts/check-spec-symbol-derivation.mjs → scripts/check-spec-symbol-derivation.mjs — edited by this change

    No value changes and no behaviour changes. The spec exports 200, which is what the local copy said, verified by resolving the installed package and reading the export — both statically (dist/security/index.d.mts) and at runtime through the same specifier the source now uses. What changes is reference identity: if the server relaxes or tightens the cap and the spec follows, the client follows too, instead of paginating at the old boundary with no signal anywhere. The cap's whole point is that an over-cap request is refused with 400 VALIDATION_FAILED rather than truncated, so a client that silently disagrees with it is exactly the drift scripts/check-spec-symbol-derivation.mjs argues about — and could not catch here: when this change landed, both of its scanners skipped non-exported declarations, and this const was module-local (objectui#5899). objectui#6291 has since removed that filter from both scanners.

.changeset/8841-tree-view-config-derive.md

  • names scripts/check-spec-symbol-derivation.mjs → scripts/check-spec-symbol-derivation.mjs — edited by this change

    What was wrong. @object-ui/types published TreeViewConfig as a hand-written interface — a copy of the protocol's ListView.tree block under a second name — and the copy declared a fifth key, titleField. @objectstack/spec@17.4.0 refuses that key there by name: TreeConfigSchema is a strictObject since spec objectstack#15469 closed the .passthrough() window 17.3.0 left open. So this package's published face accepted what the contract rejects, and an author who followed @object-ui/types was refused at publish with Unrecognized key(s) on this tree configuration: 'titleField'. The copy was invisible to scripts/check-spec-symbol-derivation.mjs, which matches spec symbols BY NAME — a hand copy renamed away from the spec's symbol has nothing for its rule 1 to match (objectui#4592's recorded blind spot).

.changeset/console-form-container-specs-one-declaration-5596.md

  • names scripts/check-spec-symbol-derivation.mjs → scripts/check-spec-symbol-derivation.mjs — edited by this change

    Both types are therefore derived from the spec's own FormSection / FormView with named narrowings — the repo's sanctioned form for a spec-shaped local type (scripts/check-spec-symbol-derivation.mjs) — rather than restated. Every key the two layers agree on comes from the spec and cannot fall behind it; the four positions where this layer is deliberately narrower are each named in an Omit list and restated once next to its reason: fields keeps the converged 26-key leaf (deriving it would silently re-open [finding] A THIRD inline copy of the form-field authoring contract lives in apps/console FormPage.tsx — objectui#5040 converged only the app-shell two #5542), and label / description / visibleWhen / visibleOn keep the shapes this repo's renderers and evaluators actually consume rather than the spec's I18nLabel and ExpressionInput. apps/console's submitBehavior union — previously hand-written under the comment "Mirrors the spec FormView.submitBehavior union" — is now read back off the shared type, making the mirror structural. @object-ui/app-shell re-exports both names from its package root (type-only, erased at build — nothing is added to the bundle), because a type that cannot be imported is a type that gets retyped.

.changeset/listview-comment-pair-4559.md

  • names scripts/check-spec-symbol-derivation.mjs → scripts/check-spec-symbol-derivation.mjs — edited by this change

    objectui#4966 — formatActionLabel's docblock now sits above formatActionLabel. It had drifted two declarations up, so the exported parseSortConfig carried two stacked leading comments and the helper carried none. This one was not cosmetic: because parseSortConfig is exported, vite-plugin-dts copied the misattributed block into dist/ListView.d.ts, so every consumer's editor hover and TypeDoc introduced the sort parser with a sentence about action labels. Moving the block removes it from the .d.ts; formatActionLabel is module-private, so its now-correct docblock does not appear there. It also matters to scripts/check-spec-symbol-derivation.mjs, whose rule 2 reads the comment block attached to a declaration — a misattributed docblock is the mechanism by which a claim gets scored against the wrong symbol. This block carries no spec-alignment phrase, so nothing fired today.

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 1b30c0fe0 (merge-base with origin/main): 4 file(s) changed outside .changeset/, read against 1855 pending declaration(s) that publish a body (2466 pending in total). · run

@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 330 chunks) 3584.5 KB 3607.4 KB
Main entry chunk (gzip) 150.0 KB 350 KB
Entry file index-sY3hKuzY.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.88KB 6.25KB
app-shell (runtime-config.js) 20.68KB 7.36KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.11KB 3.87KB
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.22KB 10.61KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.17KB 5.40KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.72KB 2.24KB
auth (SocialSignInButtons.js) 9.70KB 3.93KB
auth (UserMenu.js) 3.39KB 1.21KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 40.70KB 10.94KB
auth (createAuthenticatedFetch.js) 8.54KB 3.46KB
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) 27.13KB 7.95KB
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) 570.18KB 136.44KB
core (index.js) 10.00KB 3.96KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 228.86KB 63.53KB
fields (index.js) 261.11KB 66.26KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 2.59KB 1.22KB
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 (translateFn.js) 0.20KB 0.18KB
i18n (useDisplayLocale.js) 3.52KB 1.76KB
i18n (useObjectLabel.js) 34.35KB 9.18KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 41.23KB 11.53KB
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.86KB 5.00KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.52KB 2.26KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 8.33KB 3.07KB
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) 52.17KB 15.06KB
plugin-charts (index.js) 84.09KB 22.93KB
plugin-chatbot (index.js) 198.22KB 46.97KB
plugin-dashboard (index.js) 139.27KB 37.26KB
plugin-designer (index.js) 216.32KB 44.56KB
plugin-detail (index.js) 244.72KB 64.45KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 173.61KB 44.59KB
plugin-gantt (index.js) 173.03KB 43.07KB
plugin-grid (index.js) 231.61KB 63.61KB
plugin-kanban (index.js) 48.62KB 15.21KB
plugin-list (index.js) 116.63KB 28.97KB
plugin-map (index.js) 23.50KB 7.82KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 44.04KB 12.21KB
plugin-timeline (index.js) 33.05KB 9.67KB
plugin-tree (index.js) 11.20KB 3.89KB
plugin-view (index.js) 90.32KB 22.76KB
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.81KB 3.58KB
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) 119.55KB 39.23KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.50KB 2.06KB
react (schema-input.js) 4.25KB 2.04KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (body-dialect.js) 4.50KB 1.99KB
sdui-parser (codegen.js) 9.45KB 3.76KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 6.06KB 2.68KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (parse.js) 25.28KB 7.80KB
sdui-parser (provenance.js) 3.84KB 1.90KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 21.42KB 7.05KB
types (ai.js) 4.39KB 2.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 4.12KB 1.61KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 3.19KB 1.62KB
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.26KB
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) 2.52KB 1.31KB
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) 21.59KB 7.71KB
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: 4806a1c16c7186ea198d02f8247236ec579d21a3
Local-runs: none

Rendered 2026-09-30T23:51Z on PR objectui#11288 (card objectui#10940), from the PR body and file list, the net diff against main, the card body and its six comments, and the head's check-runs. Read-only: the installed spec's source was read at the 17.5.0 tag commit (objectstack@0f6dcac5e9: packages/spec/src/ui/report.zod.ts, i18n.zod.ts, analytics-carrier-filter.ts), files at the head and at origin/main with git show / git grep; nothing checked out, built or run.

① Derived judgments

Ten judgments, each named right or wrong. None is wrong.

  1. @object-ui/types JoinedReportBlock (a package-index export): hand-written interface → type alias of the spec's JoinedReportBlock, the z.input of JoinedReportBlockSchema, a strictObject. Accept-set against the tag source, member by member:
    • removed objectName / groupingsDown / groupingsAcross / filter / chart and the [k: string]: unknown index signature — the schema declares none of them; objectName and filter are aliases entries (refused with a rename hint), chart a guidance entry (refused by name, objectstack#20161). Narrowing. Right.
    • columns: required column-object array → z.array(z.string()).optional(). Changed concept. Right.
    • label / description: I18nLabelSchema.optional(), a union of z.string() and InlineLocaleMapSchema; the legacy { default, translations } arm is not admitted, and the two new probes pin exactly that. Narrowing. Right.
    • added dataset? (SnakeCaseIdentifierSchema, a refined string), rows? / values? (string[]), runtimeFilter? (analyticsCarrierFilter() is FilterConditionSchema.optional().superRefine(…), so an optional FilterCondition), order? (ReportSortSchema[], whose input is { by, direction? } because direction carries .default('asc')). Widening. Right.
    • unchanged name: string and type?: 'tabular' | 'summary' | 'matrix' (.default('tabular'), so optional on the input tier). Right. name's runtime accept-set is snake_case now; the TYPE is unchanged and never claimed otherwise.
    • tier: INPUT, the tier the spec exports under that name. Right — the parsed tier under the spec's name would be the silent swap the rc.6 note in spec-report.ts records.
  2. JoinedSpecReport.blocks: was the hand-written JoinedReportBlock[] intersected with the blocks?: JoinedReportBlockParsed[] that SpecReport (= ReportParsed) has carried since 17.5.0 — on main an element's columns is a column-object array AND string[], which nothing satisfies. Now NonNullable OF SpecReport['blocks'], i.e. JoinedReportBlockParsed[]: required, parsed tier (type and order[].direction defaulted), pinned in both directions (_joinedReportBlocksAreTheSpecReportBlocks, _joinedReportBlocksAreParsedTier). Right. Reading: the module now publishes an input-tier block name beside a parsed-tier blocks, so a hand-built JoinedSpecReport literal must give every block a type — stated in both docblocks and in the changeset; a documented asymmetry, not a defect.
  3. isJoinedSpecReport / isSpecReport / specReportToPresentation: bytes unchanged; the guard's narrowing target moves with item 2. No runtime change. Right.
  4. Dependants: git grep of the head over packages apps content examples scripts finds no program reference to JoinedReportBlock / JoinedSpecReport / isJoinedSpecReport outside packages/types — one comment in plugin-report/src/DatasetReportRenderer.tsx; the index.ts exports are the lit control. The declared narrowing (no local downstream tsc) has no in-repo dependant to break; the Type Check run owns that sweep (gates, below). Right as declared.
  5. Declaration exposure: the alias resolves through @objectstack/spec/ui; packages/types declares @objectstack/spec ^17.5.0 under dependencies and the head's lockfile resolves 17.5.0 — the route SpecReport already takes. No new external surface. Right.
  6. scripts/check-spec-symbol-derivation.mjs: the @object-ui/types:JoinedReportBlock ALLOW row is gone; the only remaining mention is the case-2b history comment, restated to stay true. A type alias bound to a spec import is the gate's "derived" form, and the retirement is enforced by its "in ALLOW but no longer collides" rule (the dev's ablation C). Right; not a published surface.
  7. report-chart-query-spec-parity.test.ts: the OnTypedSpec licence and the seven local-vs-spec divergence probes are deleted with the interface they compared; the state pin admits typed alone; twelve pins replace them. Every BlockMember lookup sits behind […] extends [never] ? false : …, label and description are separate probes, and _publishedBlockIsTheSpecBlock compares declared key sets so an optional key dropped or added — invisible to mutual assignability — goes red. JoinedReportBlockParsed is exported from @objectstack/spec/ui at the tag; whether spec main still exports it is the Spec Main Shape Gate's question (gates). Right. _blockNameIsUnchanged / _blockTypeIsUnchanged pin the PUBLISHED type to the old interface's literal shape for those two members — intended: if the spec ever moves them, the failure is the instruction.
  8. Docs and READMEs: no page under content/docs and no package README describes a joined block by its legacy keys (the objectName hits are page-block and CRUD-guide contexts). Right.
  9. Pending .changeset/3162-types-ledger-batch8-verdicts.md: six appended lines at the end; the empty frontmatter declaration is byte-identical. Right.
  10. Net diff: the merge-base with main is e420df31; origin/main (1b30c0fe0, four commits on) touches none of the six files, so the PR diff IS the net diff against main at this head. Right.

② Semver level

Clause-②: yes

  • .changeset/10940-joined-report-block-from-spec.md declares '@object-ui/types': minor. The diff publishes a breaking shape change on an exported type (members removed, a required member made optional with a different element type, an index signature removed). Under objectui AGENTS.md's version-alignment rule — the fixed group follows @objectstack's major, objectui's own breaking changes are minor with the breaking semantics stated in the body, and check-changeset-no-major refuses major — minor is the correct and the only admissible level. Right.
  • The body states the breaking semantics and the member-by-member change in both directions, and its "no renderer typed its blocks with either type" claim holds (① item 4). The dispatch shape (5868075939) asked for exactly this. Right.
  • One published package's src/ changed; scripts/ and tests are unpublished; one changeset is the right count. Changeset Declaration, Changeset Bump Policy, Changeset Claim Re-read, Changeset Fixed Group Check and Changeset Overwrite Report are all success on the head.
  • The PR body's Clause-②: yes matches the claim (5921087159) and the card body's own declaration; the change is a narrowing AND a widening, as the body says. This record is the at-tier record that declaration owed.

③ Boundary flags

Dev flags (os-dev-report 5921642795), each answered:

  1. Dated note on the pending 3162 changeset, outside the claim's named paths — sanctioned by the dispatch's pending-changeset clause; append-only, frontmatter intact (① item 9). Answered: accepted.
  2. Pending 10916-joined-report-block-pin-two-specs.md left unchanged — it records that PR's own act, which this diff does not falsify; the one verdict that no longer holds is corrected in the batch-8 note. Answered: accepted.
  3. Ablation C's first attempt a no-op, and 4. the reverse check's first run void (TS5112) — both re-run and reported; no diff consequence. Answered: accepted.
  4. Commit trailers use the model-free pair — verified on both branch commits (2f70920f1, 4806a1c16): Claude-Session: plus Co-authored-by: Claude, as AGENTS.md's commit-message rule requires; the repo rule governs over the harness reminder. Answered: accepted. (The ACCEPT 5921663763 counts "3 commits"; the head carries two — a slip in the seat's checklist, immaterial.)
  5. Worktree removed after the PR opened — process. Answered: accepted.
  • open_questions is empty — nothing to answer.
  • out_of_scope_findings: TypeScript 6.0.3 refuses tsc with file arguments beside a tsconfig.json (TS5112), which reads as an expected red in a probe. Seat disposition: acceptance note, not filed. Concur — nothing in this diff depends on it; not escalated.
  • NOT MEASURED locally, answered by the head's check-runs: check:readme-exports → README Export Check success; the downstream type-check of @object-ui/types' dependants → Type Check success; check:spec-floors → no PR-time check-run exists by design (spec-range-floors.yml forbids a pull_request trigger and runs Spec Range Floor Scan on schedule and on its own two paths), so it stays a declared gap covered by the next scheduled run — and this diff moves no dependency range, which is what that gate reads.
  • Card items: review 5866645487 item 1 (the [never] guards) — in the diff (① item 7). Item 2 (the erased arm deleted, not flipped) — the state pin's arm was cut at the bump (objectui#11086, the dev's H2 amendment); its last residue, the OnTypedSpec licence, is deleted here, and no literal is flipped. The ALLOW row retires (① item 6). All three answered.
  • Escalated, non-blocking: the card's "also owed" names the closed objectui#3162 BODY, which "still records the 17.2.0 'still erased' verdict". This diff amends the in-tree carriers (the pending 3162 changeset, the ledger comment in spec-derived-unions.test.ts); the closed issue's body is untouched, and the dev's three API writes name no such edit. If the card meant the body, that is a seat-side dated note, not a diff matter. For the seat.

Gates — the head's check-runs. Read at 2026-09-30T23:48Z, once the seven runs still in progress at a first read had completed (Type Check, Spec Main Shape Gate, Test (shard 1/8, 2/8, 4/8, 7/8, 8/8)): 43 check-runs on this head, 40 success, 3 skipped (dependabot, Test (coverage) and its matrix stub), 0 failure, 0 in progress. Named: Type Check success — it runs the dependants' turbo run type-check sweep and check:spec-symbols, so the declared narrowing (① item 4) and the ALLOW-row retirement (① item 6) are answered by the gate, not by prose; Spec Main Shape Gate success — the parity test, its JoinedReportBlockParsed import included, compiles against spec main; Test (shard 1/8 … 8/8) and Test (dist pins) success — the tripwire and the gate script's own suite; README Export Check, Lint, Line Citation Gate, Control Byte Scan, the five Changeset … checks, Build & E2E, Build Docs and Governed Surface Queue Guard (the pull_request leg; the dev's --test read NOT GOVERNED) success.

Implemented-by: claude/issue-10940-joined-report-block-from-spec
Reviewed-by: session_01VhxTqosz7wn54ahqyxgERT

VERDICT: PASS

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review September 30, 2026 23:52
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Sep 30, 2026
Merged via the queue into main with commit 92970c4 Oct 1, 2026
45 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-10940-joined-report-block-from-spec branch October 1, 2026 00:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

2 participants