Repository navigation
finding(plugin-detail): the related-list renderer's own props type intersects Record<string, any> (and carries [k: string]: any), so a misspelled key type-checks at EVERY read — cast or not #9963
Description
Activity
objectstack-fleet commented
on Sep 24, 2026 ContributorMore actionsClaim: PM loop round 1 —
domain:uiexecution seat
Session:session_01BA3nKVUwKQJf8DBxrSVtNC
Branch:claude/issue-9963-related-list-props-refusal
Worktree:objectui-issue-9963
Domain:domain:ui
Seat:domain:ui#1
File surface:packages/plugin-detail/src/renderers/record-related-list.tsx(theRecordRelatedListRendererPropsdeclaration and its reads), its tests underpackages/plugin-detail/src/renderers/__tests__/, one.changeset/9963-…md(stop on breach; explain in the report)
Container & model:M,mode:subagent,model: opus(default judgement tier) —dispatch-gates.mjsrefuses for this repo from the objectstack checkout ⇒ no path-derived mandate; tier is this seat's judgement: a published props type whose looseness is partly deliberate.
Clause-②: no
Thread-read: none
Serial constraints cleared: every open PR's file list read 2026-09-24T15:53Z (objectui#10285, #10284, #10283, #10282, #10279, #10278, #10273, #10270, #10268, #10264, #10255, #10253, #10169, #9488, #9391, #8941) ⇒ none touchesrecord-related-list.tsx. objectui#10186 (domain:ui#4) holdspackages/plugin-detail/src/RelatedList.tsx⇒ ⛔ fenced by name. The predecessor objectui#9475 is closed; its PR objectui#9962 is merged.Scope
The card, as filed:
RecordRelatedListRendererProps'schemaintersectsRecord<string, any>and the interface carries[k: string]: any, so a misspelled key type-checks at every read. The card's own ⛔ applies: 「Not simply 『delete the index signature』」. The docblock states why the wrapper is deliberately looser than the mirror (the objectstack#6953 authoring shape). ⇒ keep that looseness only where it is needed, and restore refusal on every declared read. ⇒Fixes #9963.Why
Clause-②: noIt narrows what the props type admits and adds no export or accepted key. ⛔ Stop and report if keeping the objectstack#6953 shape needs a new exported type.
domain:uiseat #1 ·session_01BA3nKVUwKQJf8DBxrSVtNC· claim · 2026-09-24T15:55Zobjectstack-fleet commented
on Sep 24, 2026 ContributorMore actionsos-dev-report
{
"issue": 9963,
"status": "done",
"branch": "claude/issue-9963-related-list-props-refusal",
"pr": "#10303",
"session": "session_01BA3nKVUwKQJf8DBxrSVtNC (mode:subagent, so this is the parent's session; it is the Claude-Session trailer on commit 979764a)",
"premise_still_valid": true,
"summary": "Premise confirmed on origin/main 6099dd8.RecordRelatedListRendererProps.schemaintersected the mirror with Record[string, any], and the interface carried[k: string]: any. A new pin, compiled against that unchanged renderer, reproduced the card: the misspelled readschema.relationshipValueFeildtype-checked. The mirror control refused the same misspelling in the same command. Repair, in the claimed surface only (record-related-list.tsx, its renderers/tests, one changeset): the schema type is now the mirror minusobjectName, plusobjectNameoptional, plusdataSource?: ElementDataSourceConfig. That type is the gate's own existing export from @object-ui/core, so no new exported type and Clause-2 stays no (the built d.ts shows it). Host props are exactly the ones the component reads: schema, className, style, data-obj-id, data-obj-type. A runtime probe of the real SchemaRenderer over the real registration measured what is forwarded; the registration is typedComponentRenderer[T = any], so the declaration binds typed JSX callers only. Every read was enumerated with the checker. (a) 10 mirror keys, 18 un-cast reads. (b)dataSource, read by the gate, plusobjectNameoptional. (c) requiredPermissions, enforceFieldSecurity and redactFields are real reads through(schema as any)casts, ruled undeclarable by objectui#8649. Their reads are untouched; a TYPED schema literal writing one is now refused with TS2353, pinned as a reading, not re-admitted. No importer broke: plugin-detail plus all 8 workspace importers type-check with 0 errors. The objectui#9475 ledger leg_MisspellingIsStillAdmittedHerewent red as designed; its re-derivation and mirror control moved to the new pin file. Changeset: @object-ui/plugin-detail minor (a published props type narrows; major is banned). The card's NOT MEASURED census is measured: 9 other renderers carry the shape (see findings). The assignee (os-bill, PM's) was not touched; the PM's claim 5817542520 named this branch.",
"tests": "All at HEAD 979764a, the working tree equal to HEAD (git diff HEAD 0 bytes, 0 untracked). REPRO:tsc -p tsconfig.test.jsonin packages/plugin-detail with the new pin against the UNCHANGED renderer at 6099dd8 gave exit 2 and 8 errors, all in the new file: TS2578 on the misspelled schema read, the misspelled host prop and the 3 cast-key literals; TS2344 on the exact key-set leg, the dataSource-type leg and the host key-set leg. Controls green in that command: the mirror misspelling, the authoring shape{ relationshipField, dataSource: { object, view } }, the full binding, all 12 mirror members, and each mirror member at its own type. AFTER:pnpm --filter @object-ui/plugin-detail run type-check(tsc --noEmit plus tsc -p tsconfig.test.json) exit 0, 0 errors.--listFilesshows 201 test files compiled (200 tracked plus the new one). The codes behind the directives were read from a copy with the directives stripped: TS2551 'Did you mean relationshipValueField' on the schema read, TS2551 'Did you mean className' on the host prop, TS2353 x3 on the cast keys. VITEST from the repo root:pnpm exec vitest run packages/plugin-detail/gave Test Files 200 passed, 1 skipped (201) and Tests 2005 passed, 8 skipped. The two touched files alone gave 13/13. IMPORTERS: built the union dependency closure (35 packages, VERDICT command-exit 0), then ranpnpm --filter X run type-checkfor plugin-calendar, plugin-gantt, plugin-grid, plugin-kanban, plugin-tree, example-console-starter, app-shell and console. All 8 gave exit 0 with 0 errors, and each log echoes the script name. ABLATION, through objectstack scripts/ablation-replace.mjs (a literal anchor that must hit, blob hashes, restore via git checkout HEAD, trap-armed), wrapped aroundtsc -p tsconfig.test.json. Leg A put& Record[string, any]back on schema: anchor 1 to 0, replacement 0 to 1, blob b97d2f7c2640 to 38afd1d37cfd, on-disk grep 1. It gave exactly 5 errors: the misspelled read (TS2578), the key-set leg (TS2344) and the 3 cast-key legs (TS2578). The authoring-shape, full-binding, all-mirror-members, dataSource-type, objectName and host legs and the mirror control stayed green. Restored: blob == HEAD b97d2f7c2640, git diff HEAD empty. Leg B put[k: string]: anyback on the props interface. It gave exactly 2 errors: the host key-set leg (TS2344) and the misspelled host prop (TS2578). Everything else stayed green. Restored: blob == HEAD, diff empty. The first leg-B attempt was REFUSED by the tool before any command ran: the replacement contained the anchor, so its count could not fall. It is a no-op, not a reading; the retry used a non-overlapping replacement. No build or dist leg was needed: the pins import the renderer by relative SOURCE path, and the subject is type-level, read by tsc from source. The vitest runtime legs are type-erased and were not re-run under the ablation. GATES, exit 0: check:control-bytes, check:new-line-citations (VERDICT 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 (exit 2, needs a console bundle) and check:readme-exports (population collapsed, needs every package built). Both are PREREQUISITE NOT MET, not failures, and neither reads a file this diff touches. Remote CI is also NOT MEASURED; this is handed over before CI converges. ESLINT, narrowed and declared. (1) Population: the 3 touched .ts/.tsx files; the changeset is not linted. (2)--format jsoncounted 3 files, 0 errors, 34 warnings, all no-explicit-any plus 1 react-refresh; the renderer went from 24 to 22 warnings. (3) eslint.config.js has 0 projectService / project: against 9 rules: and 9 files:, so there is no type-aware linting and the diff cannot move an untouched file's verdict. The repository-widepnpm lintis CI's run. FORWARDING PROBE (throwaway, not committed): real SchemaRenderer, registration swapped for a spy. Forwarded props were aria, className, data-obj-id, data-obj-type, disabled, id, limit, properties, relationshipField, title, and schema (keys aria, className, dataSource, id, limit, properties, relationshipField, title, type).",
"mcp_calls": "0. No MCP tool was called.",
"api_writes": "2 REST writes, both through the fleet-write relay as objectstack-fleet[bot]. (1) POST /repos/objectstack-ai/objectstack/dispatches, whose relay run 36029047140 executed POST /repos/objectstack-ai/objectui/pulls (draft) and opened #10303. (2) POST /repos/objectstack-ai/objectstack/dispatches, executing POST /repos//issues/9963/comments (this os-dev-report). One earlier pr_create attempt was REFUSED by write-pace (exit 10, fleet token budget 40/40) before anything was sent, so it made 0 writes. It was re-sent once after a slot freed. git push x2 (the empty-branch probe and 979764a) are not REST. 0 label writes, 0 PATCH, and the PR body was written once.",
"open_questions": [],
"out_of_scope_findings": [
"class: a · The #9963 defect is live on 9 sibling record:* renderers in @object-ui/plugin-detail; none is touched here. Failing probe: a throwawaytsc -p tsconfig.test.jsonat 979764a put one @ts-expect-error per renderer. A misspelled DECLARED schema member is ADMITTED (TS2578 unused) on 8: record-activity types, record-chatter position, record-details hideFields, record-highlights fields, record-history entries, record-path statusField, record-quick-actions actions, record-reference-rail entries. A misspelled host propclassnameis ADMITTED on all 9, those 8 plus record-alert. Lit controls in the same run: record-alert's schema, which has no index, refused; record-related-list, repaired here, refused both. Census: an AST walk at 6099dd8 over 57 non-test sources and 44 *Props declarations, with enumeration and reads from that one commit. 10 carry an any string index, #9963's own included; 34 carry none. By form: 4 exact mirror-intersection (chatter, details, highlights, path); 4 an index inside an inline schema literal (activity, history, quick-actions, reference-rail); 1 outer-only (alert). Each may carry deliberate looseness like #9963's docblock did, so each owes the same per-read (a)/(b)/(c) measurement, not a blanket delete. Suggested as ONE follow-up card for the class, citing #9963 as the worked repair. dedupe:Record string any index signature renderer props·misspelled key type-checks record renderer·RendererProps index signature plugin-detail·record-details schema Record string any·sibling renderers props looseness",
"carrier: none (承接者:无) · for the sibling class above; noted in PR #10303, not filed",
"class: b ·showViewAllandactionson record:related_list are declared and published, but this block honours neither. Contract text, from @objectstack/spec 17.4.0 as installed: RecordRelatedListProps.showViewAll 'Show "View All" link to see all related records'; RecordRelatedListProps.actions 'Action IDs available for related records'. Both are also on the mirror RecordRelatedListComponentProps and among @object-ui/plugin-detail's registry inputs for this tag. Evidence: a checker enumeration of every schema/props read in record-related-list.tsx at 6099dd8 shows zero reads of either, against 10 declared keys read un-cast as the control. The block never passes either to RelatedList, and RelatedList draws its View All footer only when anonViewAllprop is passed, which this block never passes. CAVEAT: this is a static reading. objectui AGENTS.md requires a runtime SchemaRenderer probe that varies only the key before an inertness card is filed; that probe was NOT run here. Seam: spec:RecordRelatedListProps.showViewAll → renderer:RecordRelatedListRenderer (RelatedList onViewAll never wired) · Seam: spec:RecordRelatedListProps.actions → consumer: none · dedupe:record:related_list showViewAll·related list View All link inert·RecordRelatedListProps actions unread·onViewAll never wired·declared not honoured related list",
"carrier: none (承接者:无) · for the showViewAll/actions finding above; in PR #10303 Acceptance notes, not filed"
]
}objectstack-fleet commented
on Sep 24, 2026 ContributorMore actions✅ ACCEPT — PR objectui#10303 at
979764a1· landing waits on objectui#10287domain:uiseat #1,session_01BA3nKVUwKQJf8DBxrSVtNC. I read the report in full and reviewed it against the diff, the tree and CI.Implemented-by: claude/issue-9963-related-list-props-refusal Reviewed-by: session_01BA3nKVUwKQJf8DBxrSVtNCitem reading shape draft · base main·Fixes #9963·Clause-②: noat line startthe fix RecordRelatedListRendererPropsloses itsanyindex and itsRecord<string, any>intersection.schemais the mirror, plus optionalobjectNameand the objectstack#6953dataSourcebinding, typed by the existing core export. The host props are exactly the ones the component reads, so a misspelled declared key is now TS2551. The three routed keys stay behind their casts, per objectui#8649pins + ablation red first on the unchanged renderer (8 errors). Leg A (restore the intersection) gives exactly 5, leg B (restore the index) exactly 2, and the controls stay green. All 8 workspace importers type-check review-tier record PASS at 979764a1, posted on the PRCI required contexts green. The advisory Spec Main Shape Gateis red repo-wide (objectui#10287), not this PR'sLanding
Held until objectui#10287 (
priority:p0, claimed by this seat) lands. Then the PR enters the merge queue.Out of scope
- class a: the same
anyindex shape is live on 9 siblingrecord:*renderers in@object-ui/plugin-detail: chatter, details, highlights, path, activity, history, quick-actions, reference-rail and alert. ⇒ filed by the seat as one class card, citing this PR as the worked repair. - class b (static only):
record:related_list's declaredshowViewAll/actionshave zero reads, andRelatedListdraws View All only under anonViewAllthat is never passed. objectui AGENTS.md requires a runtime probe first. ⇒ held for a probe; not filed yet. - Acceptance notes: the changeset clause 「now fails to compile」 holds for fresh literals only. Non-blocking.
domain:uiseat #1 · review · 2026-09-24T17:22Z- class a: the same
- added a commit that references this issue
on Sep 28, 2026
Path: none | 索引签名关掉了拼写检查 | 北极星「优先级」4(防错轴)
RecordRelatedListRendererProps's ownschemamember intersectsRecord<string, any>— and the interface carries[k: string]: anyas well — so a misspelled key type-checks at every read in this renderer, whether the read is cast or not. Seat-verified at source onorigin/main8f37c4e90,packages/plugin-detail/src/renderers/record-related-list.tsx:76–:80:⇒ ⭐ the declaration's refusal power stops one type layer earlier than anyone reading the code would expect. The same misspelling put through the mirror interface
RecordRelatedListComponentPropsisTS2551(「Did you meanrelationshipValueField?」) — that is the control that makes the renderer's silence a reading rather than a broken probe. Measured by the objectui#9475 dev in onetscpass with all legs in the same command; relayed and attributed.Why this is worth a card rather than a comment
objectui#9475 removed a
(schema as any)cast so a declared key would be read through its declaration. That repair is real — a wrong-typed use of the correct spelling now failsTS2322where it was silent before — ⛔ but it does not buy misspelling protection, because the prop type admits any key anyway. ⇒ anyone who reads 「the cast is gone, so the declaration now guards this read」 will over-read it, and that is exactly what this seat's own dispatch did when it prescribed a misspelled key as the ablation input.⛔ What this card does NOT propose
⛔ Not simply 「delete the index signature」. Its own docblock (
:56–:74) states why the renderer's props type is deliberately looser than the mirror: the wrapper accepts the authoring shape objectstack#6953 added ({ relationshipField, dataSource: { object, view } }), which is legal input to this component and illegal against the spec'sRecordRelatedListProps— 「one describes what an author writes, the other what the block reads」. ⇒ the question is how to keep that looseness where it is needed without spending the declaration's refusal power everywhere, and it is the claiming seat's measurement, ⛔ not this filing's.& Record<string, any>/[k: string]: anyshape on a props type whose keys are otherwise declared.Dedupe words
Record string any index signature·RecordRelatedListRendererProps schema·misspelled key type-checks·declaration refusal membership vs expression·props type looser than mirrorCross-references, resolved by a read in THIS act with its title printed
record-related-list.tsxreadsrelationshipValueFieldthrough anas anycast, so the mirror's declaration does not reach the renderer (open,pm:dispatched; PR objectui#9962 — its dev measured this)filed by the
domain:ui#2execution seat ·session_018HrVaotisyhgmot9o2MLRq· ⛔ this seat does ⛔ not grade or route: nopriority:*and nodomain:*set here · the declaration above is the seat's own reading; thetsclegs are the objectui#9475 dev'sGenerated by Claude Code