Skip to content

fix(plugin-detail): the related-list renderer's props type refuses a misspelled key again (objectui#9963) - #10303

Merged
objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/issue-9963-related-list-props-refusal
Sep 24, 2026
Merged

objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/issue-9963-related-list-props-refusal

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #9963
Clause-②: no

RecordRelatedListRendererProps refuses a misspelled key again, at every read in the
record:related_list renderer, and keeps exactly the looseness objectstack#6953 needs.

⚠️ Type parameters are spelled with SQUARE brackets in this body (Record[string, any],
Pick[...]), because the GitHub body sanitizer eats angle-bracket-shaped fragments, even
inside backticks. The source spells them the normal way.

The defect, reproduced before it was repaired

On origin/main at 6099dd87, the renderer's schema member was the mirror
RecordRelatedListComponentProps (with objectName made optional) intersected with
Record[string, any], and the props interface also carried [k: string]: any. The new
pin file record-related-list.propsRefusal-9963.test.tsx was compiled against that
unchanged renderer (tsc -p tsconfig.test.json, before the fix): 8 errors, all in the
new file
:

  • the misspelled read schema.relationshipValueFeild: @ts-expect-error UNUSED (TS2578),
    i.e. the misspelling type-checked;
  • the exact-key-set leg (keyof Schema equal to the mirror's keys plus dataSource): TS2344,
    because an index signature makes keyof read string | number;
  • Schema['dataSource'] equal to the gate's ElementDataSourceConfig: TS2344 (it was any);
  • the host-props key-set leg: TS2344; the misspelled host prop props.classname: TS2578;
  • the three cast-read keys written into a typed schema literal: TS2578 x3 (admitted).

Every control in the same command stayed green: the SAME misspelling through the mirror
(no index signature) was refused, and the authoring shape, the full binding and a literal
carrying all 12 mirror members all type-checked.

The minimal honest shape — every read enumerated with the checker

A TypeScript program over the renderer walked every property access whose base is
schema (bare or through a cast) or props, and classified each key with
getPropertiesOfType on the mirror (real members only; an index signature does not count):

class keys (reads) how read
(a) declared on the mirror add (4), aria (1), columns (2), filter (1), limit (3), objectName (1), relationshipField (3), relationshipValueField (1), sort (1), title (1) un-cast
(b) the objectstack#6953 authoring shape dataSource read by ElementDataSourceGate (useElementDataSource), never by the body; objectName optional because the gate binds it
(c) other requiredPermissions (2), enforceFieldSecurity (1), redactFields (2) through (schema as any) casts

(c) are real reads, not dead code. objectui#8649 ruled them "declared by no block the
contract maps" and routed them to the producer; detailRendererUndeclaredKeys-8649.test.ts
ledgers them. Their reads are untouched, because each goes through its own cast. What
the narrowing now refuses is a typed call site writing one of them into a schema
literal (TS2353). ⛔ They are NOT re-admitted: no index signature, no new member.

Two mirror members, showViewAll and actions, are read by nothing in this renderer.
That is noted below under Acceptance notes and not acted on.

What the registry forwards: measured, not read off the code. A throwaway probe (not
committed) rendered a real SchemaRenderer over the real registration with the entry
swapped for a spy. It captured the component's props for a node carrying id,
className, relationshipField, dataSource, aria and properties:

aria, className, data-obj-id, data-obj-type, disabled, id, limit, properties,
relationshipField, title, plus schema (whose keys were aria, className, dataSource, id, limit, properties, relationshipField, title, type).

The component reads only schema, className, and the three keys splitDesigner
destructures (style, data-obj-id, data-obj-type). The registry's call does not bind
this declaration: the registration goes through ComponentRenderer[T = any] on a
Registry[any]. So the declared host props are the ones read, and what is forwarded
and never read is not declared.

The shape now: the mirror minus objectName, plus objectName optional, plus
{ dataSource?: ElementDataSourceConfig }. The props are schema, className, style,
data-obj-id and data-obj-type. ElementDataSourceConfig is the gate's own existing
export from @object-ui/core, a peer dependency already. Measured on the built
dist/renderers/record-related-list.d.ts: it imports ElementDataSourceConfig from
@object-ui/core and exports no new type. ⇒ Clause-②: no holds.

After the repair (HEAD 979764a1)

The same pins are green. The @ts-expect-error directives fire for the right reason. A
throwaway copy with the directives stripped reported:

  • TS2551 "Did you mean 'relationshipValueField'?" on the schema read;
  • TS2551 "Did you mean 'className'?" on the host prop;
  • TS2353 on each of the three (c) literals.

Ablation, with on-disk proof, both legs restored

Both legs were run through ablation-replace.mjs (a literal anchor that must hit, blob
hashes, restore by git checkout HEAD), then tsc -p tsconfig.test.json:

  • Leg A: put & Record[string, any] back on schema. The anchor went 1 to 0 and the
    replacement 0 to 1. The blob went b97d2f7c2640 to 38afd1d37cfd, and an on-disk grep
    counted 1. tsc then gave exactly 5 errors: the misspelled read (TS2578), the key-set
    leg (TS2344) and the three (c) legs (TS2578). The authoring-shape, full-binding,
    all-mirror-members, dataSource-type, objectName and host-props legs stayed green.
    The mirror control stayed green too. Restored: blob b97d2f7c2640 equals HEAD, and
    git diff HEAD is empty.
  • Leg B: put [k: string]: any back on the props interface. tsc gave exactly 2
    errors
    : the host key-set leg (TS2344) and the misspelled host prop (TS2578).
    Everything else stayed green. Restored: blob equals HEAD, and git diff HEAD is empty.
    ⚠️ The first leg-B attempt was refused by the tool before anything ran. Its replacement
    contained the anchor, so the anchor count could not fall. Nothing was measured on it;
    the retry used a non-overlapping replacement.

Consumers

  • @object-ui/plugin-detail type-check at HEAD 979764a1 (both tsc --noEmit and
    tsc -p tsconfig.test.json): exit 0, 0 errors. The test project compiles 201 test files
    (--listFiles: 200 tracked plus the new one). That includes every
    RecordRelatedListRenderer JSX site in src/__tests__/ and src/renderers/__tests__/,
    so no existing call site broke. The one in record-blocks.requiredPermissions-gate.test.tsx
    passes its schema as any.
  • Every importer's type-check, at HEAD 979764a1, after building their union
    dependency closure (35 workspace packages). The eight workspace packages that depend on
    @object-ui/plugin-detail are plugin-calendar, plugin-gantt, plugin-grid,
    plugin-kanban, plugin-tree, example-console-starter, app-shell and console.
    Each ran type-check, and each log echoes the script name. All 8 exited 0 with 0
    errors.
    No importer broke, so nothing was widened back.
  • The objectui#9475 ledger leg _MisspellingIsStillAdmittedHere went red, as it was
    written to do on the day this intersection narrowed. Its re-derivation and its mirror
    control moved to the new pin file, and that file's header was updated.

Census the card left NOT MEASURED

An AST walk ran over every non-test source of packages/plugin-detail/src at
6099dd87, 57 files and 44 declarations named *Props. Enumeration and reads both came
from that one commit. 10 declarations carry an any string index, on the interface
and/or folded into schema. record-related-list.tsx is among them, as the lit control;
34 carry none. So 9 other renderers carry the shape:

  • 4 with the exact mirror-intersection form: record-chatter, record-details,
    record-highlights, record-path;
  • 4 with [k: string]: any inside an inline schema literal: record-activity,
    record-history, record-quick-actions, record-reference-rail;
  • 1 with the outer index only: record-alert.

All 9 carry the outer [k: string]: any.

The census reads syntax, so it was followed by a type-level reading at HEAD 979764a1.
A throwaway file, never committed and removed under a trap, put @ts-expect-error on
one misspelled DECLARED schema member per renderer, and on a misspelled className
per renderer:

  • 8 of the 9 admit the schema misspelling. record-alert refuses it, because its
    schema carries no index; that refusal is what makes the other column a reading.
  • All 9 admit the host-prop misspelling.
  • record-related-list refused both. It is the lit control.

⛔ None of the 9 is touched here; they go to the report as findings.

Verification (HEAD 979764a1)

  • pnpm exec vitest run packages/plugin-detail/ (repo root): 200 files passed, 1
    skipped (201); 2005 tests passed, 8 skipped
    .
  • The dependency closure was built before any type reading (pnpm --filter '@object-ui/plugin-detail^...' build), so the pins read rebuilt .d.ts files.
  • Gates, all exit 0: check:control-bytes, check:new-line-citations (0 new
    citations), node scripts/check-changeset-presence.mjs (1 changeset declared),
    check:element-data-source-declaration, check:component-surface-parity,
    check:handler-key-reads, check:comment-mask-corpus, check:test-path-roots,
    check:vi-mock-specifiers, check:vi-mock-inherit, check:vi-mock-override-shape,
    check:changeset-claims, check:pending-changeset-literals, check:phantom-deps,
    check:installed-pin-claims, check:published-tsconfig-exclude.
  • NOT MEASURED: check:sdui-registration-pins (it needs a console bundle, exit 2
    PREREQUISITE) and check:readme-exports (it needs every package built, and its
    population collapsed). Neither reads a file this diff touches: no registration array,
    README or export moved.
  • eslint, narrowed and declared. The population is the three touched .ts/.tsx files,
    and --format json counted 3 files linted, 0 errors. The renderer went from 24 to
    22 warnings, the two any removed. The narrowing is safe: eslint.config.js enables no
    type-aware linting (0 occurrences of projectService or project:, against 9 each of
    rules: and files:), so no rule can read a file this diff did not touch. The
    repository-wide pnpm lint is CI's run.
  • NOT MEASURED: remote CI. This is handed over before CI converges.

Changeset

@object-ui/plugin-detail: minor. A published props type narrows: TypeScript callers
that wrote a key outside the declared set now fail to compile. major is banned by this
repo's version policy, and the changeset body states the breaking semantics. The runtime
is unchanged: SchemaRenderer forwards exactly what it did, and nothing the renderer
reads moved.

Acceptance notes

  • showViewAll and actions are declared and published but read by nothing. Both are
    declared on the spec's RecordRelatedListProps ("Show View All link to see all related
    records", "Action IDs available for related records"), on the mirror, and as registry
    inputs. This renderer reads neither and hands neither to RelatedList. RelatedList
    does draw a View All link, but only when an onViewAll prop is passed, and this block
    never passes one. This card keeps both admitted, because they are mirror members;
    whether they are live is a separate question, handed to the report as a finding.
  • The wrapper still hands the bound schema to the body as schema={bound as any}. Both
    sides already carry the same type, so the cast is inert. It was left alone because it is
    not a read and the change is out of this card's question.
  • No doc names this TypeScript props type. A grep for RecordRelatedListRenderer over
    content/docs and the package README finds 0 hits; the lit control, the same grep for
    record:related_list, finds several. So no doc moves.
  • Open PR objectui#10279 edits the sibling record-blocks.requiredPermissions-gate.test.tsx.
    Its related-list site passes schema as any, so there is no textual or type overlap
    with this narrowing.

Generated by Claude Code

…misspelled key again

`RecordRelatedListRendererProps['schema']` intersected the mirror with
`Record<string, any>` and the interface carried `[k: string]: any`, so a
misspelled declared key type-checked at every read in the renderer, cast
or not.

The looseness objectstack#6953 needs is now two NAMED members instead of
an open index: `objectName` optional (the gate binds it) and `dataSource`
typed with the gate's own `ElementDataSourceConfig`. Host props are the
ones the component reads. The objectui#9475 ledger leg that pinned the
old limit went red as designed; its re-derivation and mirror control
moved to the new propsRefusal pin.

Claude-Session: https://claude.ai/code/session_01BA3nKVUwKQJf8DBxrSVtNC
@github-actions

github-actions Bot commented Sep 24, 2026 •

Copy link
Copy Markdown
Contributor

changeset-claim-re-read

⚠️ 2 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/8649-detail-renderer-undeclared-keys.md

  • names record-related-list.tsx → packages/plugin-detail/src/renderers/record-related-list.tsx — edited by this change

    @object-ui/plugin-detail — the annotation-erasing destructure default is gone from three renderers. record-details.tsx, record-highlights.tsx and record-related-list.tsx each annotated schema correctly and then wrote schema = {} as any. A destructuring default's type joins the annotated property type at the binding, so any erased the annotation for every read site in the file — declared keys and undeclared ones alike read any. No published surface moves: the exported annotations were always correct.

.changeset/9964-related-list-picker-filter-mirror.md

  • names record-related-list.tsx → packages/plugin-detail/src/renderers/record-related-list.tsx — edited by this change

    So one key carried two declarations, with the looser one on the face an author — or an AI writing metadata — reads: unknown offers no shape guidance for a key whose consumer demands a specific shape. Nothing could report the divergence either, because record-related-list.tsx reached the block through four (schema as any).add reads; a cast unwraps the declaration at its own read site, so no compiler ever compared the two.

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

@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 329 chunks) 3039.2 KB 3104.5 KB
Main entry chunk (gzip) 147.8 KB 350 KB
Entry file index-Fl7tgm_u.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) 25.05KB 9.16KB
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.46KB 3.43KB
auth (index.js) 3.19KB 1.44KB
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) 541.66KB 129.51KB
core (index.js) 8.94KB 3.59KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 222.47KB 61.83KB
fields (index.js) 253.41KB 64.01KB
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.22KB 2.26KB
i18n (pickLocalized.js) 9.86KB 3.95KB
i18n (provider.js) 33.36KB 10.88KB
i18n (useDisplayLocale.js) 2.85KB 1.45KB
i18n (useObjectLabel.js) 34.34KB 9.17KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 39.15KB 11.05KB
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) 14.81KB 3.63KB
plugin-calendar (index.js) 51.44KB 14.62KB
plugin-charts (index.js) 71.82KB 20.13KB
plugin-chatbot (index.js) 198.27KB 47.18KB
plugin-dashboard (index.js) 133.00KB 35.19KB
plugin-designer (index.js) 215.98KB 44.34KB
plugin-detail (index.js) 258.85KB 67.53KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 144.77KB 37.17KB
plugin-gantt (index.js) 167.99KB 41.37KB
plugin-grid (index.js) 215.46KB 58.88KB
plugin-kanban (index.js) 48.83KB 15.21KB
plugin-list (index.js) 113.90KB 28.11KB
plugin-map (index.js) 21.74KB 7.07KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 43.55KB 11.99KB
plugin-timeline (index.js) 30.68KB 8.95KB
plugin-tree (index.js) 10.56KB 3.71KB
plugin-view (index.js) 85.18KB 21.05KB
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) 110.46KB 36.33KB
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: 979764a1

① Derived judgments

  • The export surface narrows strictly. RecordRelatedListRendererProps is published as the props of the exported RecordRelatedListRenderer.
    • Before: schema intersected Record<string, any>, and the interface carried [k: string]: any.
    • After: schema = the mirror minus objectName, plus optional objectName, plus dataSource?: ElementDataSourceConfig; the host props are exactly schema, className, style, data-obj-id and data-obj-type.
    • Everything admitted after was admitted before, and nothing new is admitted.
  • Exactness.
    • All 12 mirror members arrive at the mirror's own types.
    • The objectstack#6953 authoring shape compiles.
    • objectName is honestly optional, because the gate writes it from composed.object.
    • The host props are exactly the keys the component reads.
    • The reviewer's own enumeration agrees: 18 un-cast reads over 10 mirror keys; dataSource read only by the gate; 5 cast reads over 3 routed keys.
  • Registry forwarding is harmless. The registration is ComponentRenderer<any>, so only typed JSX callers bind. The 16 in-repo typed callers all pass schema only, and there are 0 typed uses outside plugin-detail.
  • ElementDataSourceConfig is the very type the gate imports and narrows with. Every spec-valid ElementDataSourceSchema value is assignable to it.
  • Cast reads vs objectui#8649: the three routed keys still compile through their casts, and a typed literal writing one is TS2353, exactly as decision(plugin-detail): twelve undeclared reads across four record renderers — and eleven of them are only invisible because schema = {} as any erases a correct annotation (2 of 7, objectui#8327 class (a)) #8649 ruled. No typed caller wrote them un-cast.
  • Pins, independently re-run: 0 errors at head; exactly 8 with main's renderer; leg A gives exactly 5 and leg B exactly 2. The objectui#9475 ledger leg was moved, not deleted.

② Semver level

  • minor is RIGHT (objectui 版本号策略: breaking changes ship as minor).
  • One over-general clause, non-blocking: 「a typed call site that wrote a key outside that set … now fails to compile」 is true for a fresh object literal. A non-fresh value has no excess-property check. The PR body's own wording (「written into a schema literal」) is the precise one.

③ Boundary flags

  • Acceptance notes: the 9-sibling census is consistent with a spot-check. The showViewAll / actions finding is verified static (0 reads; RelatedList draws View All only under onViewAll, which is never passed) and is correctly labelled static.
  • Fixes #9963 is right, and Clause-②: no is TRUE.
  • Fences held. RelatedList.tsx is untouched, and there is no overlap with objectui#10255 or fix(plugin-detail): record:details stops reading requiredPermissions #10279.
  • Check-runs on 979764a1: 43 terminal, and all required contexts are green.
    • The single failure is Spec Main Shape Gate: NavigationDesigner.tsx TS2741 (doc missing) plus a downstream TS7016. That is objectui#10287's repo-wide red, not this PR's.
  • Mergeable.

Implemented-by: claude/issue-9963-related-list-props-refusal
Reviewed-by: session_01BA3nKVUwKQJf8DBxrSVtNC

VERDICT: PASS

@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 329 chunks) 3039.2 KB 3104.5 KB
Main entry chunk (gzip) 147.8 KB 350 KB
Entry file index-DOH1fdA5.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) 25.05KB 9.16KB
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.46KB 3.43KB
auth (index.js) 3.19KB 1.44KB
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) 541.66KB 129.51KB
core (index.js) 8.94KB 3.59KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 222.47KB 61.83KB
fields (index.js) 253.41KB 64.01KB
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.22KB 2.26KB
i18n (pickLocalized.js) 9.86KB 3.95KB
i18n (provider.js) 33.36KB 10.88KB
i18n (useDisplayLocale.js) 2.85KB 1.45KB
i18n (useObjectLabel.js) 34.34KB 9.17KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 39.15KB 11.05KB
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) 14.81KB 3.63KB
plugin-calendar (index.js) 51.44KB 14.62KB
plugin-charts (index.js) 71.82KB 20.13KB
plugin-chatbot (index.js) 198.27KB 47.18KB
plugin-dashboard (index.js) 133.00KB 35.19KB
plugin-designer (index.js) 216.12KB 44.37KB
plugin-detail (index.js) 258.85KB 67.53KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 144.77KB 37.17KB
plugin-gantt (index.js) 167.99KB 41.37KB
plugin-grid (index.js) 215.46KB 58.88KB
plugin-kanban (index.js) 48.83KB 15.21KB
plugin-list (index.js) 113.90KB 28.11KB
plugin-map (index.js) 21.74KB 7.07KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 43.55KB 11.99KB
plugin-timeline (index.js) 30.68KB 8.95KB
plugin-tree (index.js) 10.56KB 3.71KB
plugin-view (index.js) 85.18KB 21.05KB
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) 110.46KB 36.33KB
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
objectstack-fleet Bot marked this pull request as ready for review September 24, 2026 18:22
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Sep 24, 2026
Merged via the queue into main with commit 905913c Sep 24, 2026
45 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-9963-related-list-props-refusal branch September 24, 2026 18:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

1 participant