Repository navigation
fix(plugin-detail): the related-list renderer's props type refuses a misspelled key again (objectui#9963) - #10303
Conversation
…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
|
changeset-claim-re-read
|
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
Contract reviewServed-tier: ① Derived judgments
② Semver level
③ Boundary flags
Implemented-by: VERDICT: PASS |
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
Fixes #9963
Clause-②: no
RecordRelatedListRendererPropsrefuses a misspelled key again, at every read in therecord:related_listrenderer, and keeps exactly the looseness objectstack#6953 needs.Record[string, any],Pick[...]), because the GitHub body sanitizer eats angle-bracket-shaped fragments, eveninside backticks. The source spells them the normal way.
The defect, reproduced before it was repaired
On
origin/mainat6099dd87, the renderer'sschemamember was the mirrorRecordRelatedListComponentProps(withobjectNamemade optional) intersected withRecord[string, any], and the props interface also carried[k: string]: any. The newpin file
record-related-list.propsRefusal-9963.test.tsxwas compiled against thatunchanged renderer (
tsc -p tsconfig.test.json, before the fix): 8 errors, all in thenew file:
schema.relationshipValueFeild:@ts-expect-errorUNUSED (TS2578),i.e. the misspelling type-checked;
keyof Schemaequal to the mirror's keys plusdataSource): TS2344,because an index signature makes
keyofreadstring | number;Schema['dataSource']equal to the gate'sElementDataSourceConfig: TS2344 (it wasany);props.classname: TS2578;schemaliteral: 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) orprops, and classified each key withgetPropertiesOfTypeon the mirror (real members only; an index signature does not count):add(4),aria(1),columns(2),filter(1),limit(3),objectName(1),relationshipField(3),relationshipValueField(1),sort(1),title(1)dataSourceElementDataSourceGate(useElementDataSource), never by the body;objectNameoptional because the gate binds itrequiredPermissions(2),enforceFieldSecurity(1),redactFields(2)(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.tsledgers 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
schemaliteral (TS2353). ⛔ They are NOT re-admitted: no index signature, no new member.
Two mirror members,
showViewAllandactions, 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
SchemaRendererover the real registration with the entryswapped for a spy. It captured the component's props for a node carrying
id,className,relationshipField,dataSource,ariaandproperties:aria,className,data-obj-id,data-obj-type,disabled,id,limit,properties,relationshipField,title, plusschema(whose keys werearia, className, dataSource, id, limit, properties, relationshipField, title, type).The component reads only
schema,className, and the three keyssplitDesignerdestructures (
style,data-obj-id,data-obj-type). The registry's call does not bindthis declaration: the registration goes through
ComponentRenderer[T = any]on aRegistry[any]. So the declared host props are the ones read, and what is forwardedand never read is not declared.
The shape now: the mirror minus
objectName, plusobjectNameoptional, plus{ dataSource?: ElementDataSourceConfig }. The props areschema,className,style,data-obj-idanddata-obj-type.ElementDataSourceConfigis the gate's own existingexport from
@object-ui/core, a peer dependency already. Measured on the builtdist/renderers/record-related-list.d.ts: it importsElementDataSourceConfigfrom@object-ui/coreand exports no new type. ⇒Clause-②: noholds.After the repair (HEAD
979764a1)The same pins are green. The
@ts-expect-errordirectives fire for the right reason. Athrowaway copy with the directives stripped reported:
Ablation, with on-disk proof, both legs restored
Both legs were run through
ablation-replace.mjs(a literal anchor that must hit, blobhashes, restore by
git checkout HEAD), thentsc -p tsconfig.test.json:& Record[string, any]back onschema. The anchor went 1 to 0 and thereplacement 0 to 1. The blob went
b97d2f7c2640to38afd1d37cfd, and an on-disk grepcounted 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,objectNameand host-props legs stayed green.The mirror control stayed green too. Restored: blob
b97d2f7c2640equals HEAD, andgit diff HEADis empty.[k: string]: anyback on the props interface. tsc gave exactly 2errors: the host key-set leg (TS2344) and the misspelled host prop (TS2578).
Everything else stayed green. Restored: blob equals HEAD, and
git diff HEADis empty.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-detailtype-checkat HEAD979764a1(bothtsc --noEmitandtsc -p tsconfig.test.json): exit 0, 0 errors. The test project compiles 201 test files(
--listFiles: 200 tracked plus the new one). That includes everyRecordRelatedListRendererJSX site insrc/__tests__/andsrc/renderers/__tests__/,so no existing call site broke. The one in
record-blocks.requiredPermissions-gate.test.tsxpasses its schema
as any.type-check, at HEAD979764a1, after building their uniondependency closure (35 workspace packages). The eight workspace packages that depend on
@object-ui/plugin-detailareplugin-calendar,plugin-gantt,plugin-grid,plugin-kanban,plugin-tree,example-console-starter,app-shellandconsole.Each ran
type-check, and each log echoes the script name. All 8 exited 0 with 0errors. No importer broke, so nothing was widened back.
_MisspellingIsStillAdmittedHerewent red, as it waswritten 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/srcat6099dd87, 57 files and 44 declarations named*Props. Enumeration and reads both camefrom that one commit. 10 declarations carry an
anystring index, on the interfaceand/or folded into
schema.record-related-list.tsxis among them, as the lit control;34 carry none. So 9 other renderers carry the shape:
record-chatter,record-details,record-highlights,record-path;[k: string]: anyinside an inlineschemaliteral:record-activity,record-history,record-quick-actions,record-reference-rail;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-erroronone misspelled DECLARED
schemamember per renderer, and on a misspelledclassNameper renderer:
record-alertrefuses it, because itsschemacarries no index; that refusal is what makes the other column a reading.record-related-listrefused 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, 1skipped (201); 2005 tests passed, 8 skipped.
pnpm --filter '@object-ui/plugin-detail^...' build), so the pins read rebuilt.d.tsfiles.check:control-bytes,check:new-line-citations(0 newcitations),
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.check:sdui-registration-pins(it needs a console bundle, exit 2PREREQUISITE) and
check:readme-exports(it needs every package built, and itspopulation collapsed). Neither reads a file this diff touches: no registration array,
README or export moved.
.ts/.tsxfiles,and
--format jsoncounted 3 files linted, 0 errors. The renderer went from 24 to22 warnings, the two
anyremoved. The narrowing is safe:eslint.config.jsenables notype-aware linting (0 occurrences of
projectServiceorproject:, against 9 each ofrules:andfiles:), so no rule can read a file this diff did not touch. Therepository-wide
pnpm lintis CI's run.Changeset
@object-ui/plugin-detail: minor. A published props type narrows: TypeScript callersthat wrote a key outside the declared set now fail to compile.
majoris banned by thisrepo's version policy, and the changeset body states the breaking semantics. The runtime
is unchanged:
SchemaRendererforwards exactly what it did, and nothing the rendererreads moved.
Acceptance notes
showViewAllandactionsare declared and published but read by nothing. Both aredeclared on the spec's
RecordRelatedListProps("Show View All link to see all relatedrecords", "Action IDs available for related records"), on the mirror, and as registry
inputs. This renderer reads neither and hands neither to
RelatedList.RelatedListdoes draw a View All link, but only when an
onViewAllprop is passed, and this blocknever 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.
schema={bound as any}. Bothsides 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.
RecordRelatedListRendererovercontent/docsand the package README finds 0 hits; the lit control, the same grep forrecord:related_list, finds several. So no doc moves.record-blocks.requiredPermissions-gate.test.tsx.Its related-list site passes
schema as any, so there is no textual or type overlapwith this narrowing.
Generated by Claude Code