feat(types,plugin-chatbot): name the chatbot node's authoring-face type - #6688
Merged
Merged
Conversation
Lift the 11-key anonymous inline intersection at packages/plugin-chatbot/src/renderer.tsx:62 (the `chatbot` registration) into ChatbotSchema, per the maintainer's 2026-08-25 #6172 family ruling: every component node has exactly one named, importable authoring-face type. Each key was read-site-censused first; all ten net-new keys have live readers (renderer.tsx and/or useObjectChat.ts), so none took the ADR-0049 retirement route. `disabled` is not redeclared -- it is already BaseSchema.disabled (boolean | string), inherited, read generically. The Zod mirror (complex.zod.ts) gains the same ten keys in lockstep, so these move straight from unmirrored-and-passthrough to mirrored, never through an interim drift window that would fail the parity ratchet. Part of #6169. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Contributor
✅ 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
|
os-sales
marked this pull request as ready for review
August 28, 2026 15:01
This was referenced Aug 28, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #6169
Docs half already merged — untouched here
PR #6193 (
ead74abe4) already fixed thebody/requestBodycollision on this page. This PR does not re-touch that row's wording; it adds one sentence above the table naming the type these keys now come from (see "Docs" below).The re-derived key set, and where the card's list didn't reconcile
The maintainer ruling scoped this to the anonymous inline intersection at
packages/plugin-chatbot/src/renderer.tsx:62— thechatbotregistration specifically (notchatbot-enhanced/chatbot-floating, which have their own, different, still-anonymous intersections; out of scope, noted below). Counting that literal block:11 keys inside the intersection — matches the card's "11". Two things in the dispatch's warning did not reconcile, and both turned out to be measurement errors in the card, not in the count:
surface— named by the card as one of the 11. It is not in this block, not in either of the other two registrations' blocks, and not forwarded anywhere inrenderer.tsx(grep -n surface packages/plugin-chatbot/src/renderer.tsx→ zero matches). It's a real prop ofChatbotEnhanced(surface?: ChatbotSurface,ChatbotEnhanced.tsx:805), but no registration ever readsschema.surfaceto forward it. The docs table'ssurfacerow (also un-backed) is a separate, pre-existing defect — filed as finding(plugin-chatbot): the docssurfacerow names a key nochatbot*registration reads — zero read points across all three #6687, not touched here (see "Out of scope" below).disabled— the card said the intersection "also carriesdisabled, which the registration destructures separately." It does:disabled?: booleanappears twice — once inside the intersection (an authored schema key) and once at the sibling level of the destructure type (; className?: string; disabled?: boolean; ...}, the host-EVALUATED verdict SchemaRenderer forwards ashostDisabled). These are two different carriers for two different questions that happen to share a spelling — not a miscount.renderer.tsx's own comment (right abovedisabled={hostDisabled || isLoading}) already documents whyhostDisabledis read there and notschema.disabled.Read-site census (the clause that makes this more than a rename)
showTimestamprenderer.tsx(bothuseObjectChat()call and<Chatbot>render prop);useObjectChat.ts:750,768(sets/omits message timestamp);index.tsx:203(conditional render)userAvatarUrlrenderer.tsxrender prop;index.tsx:104,174userAvatarFallbackrenderer.tsxrender prop;index.tsx:105,177assistantAvatarUrlrenderer.tsxrender prop;index.tsx:106,174assistantAvatarFallbackrenderer.tsxrender prop;index.tsx:107,178maxHeightrenderer.tsxrender prop;index.tsx:47,88(style={{ maxHeight }})autoResponserenderer.tsx→useObjectChat();useObjectChat.ts:427,761(gates the auto-response timer); live consumerpackages/app-shell/src/console/ai/AiChatPage.tsx:1662autoResponseTextuseObjectChat.ts:428,767;AiChatPage.tsx:1663autoResponseDelayuseObjectChat.ts:429,772(setTimeoutdelay);AiChatPage.tsx:1664onSenduseObjectChat.ts:429,718,720,755(invoked with content + messages)disabled(the authored, in-intersection one)chatbotregistration body — but it's inherited fromBaseSchema.disabled(boolean | string), read generically for every node type atpackages/react/src/SchemaRenderer.tsx:1085(newSchema.disabled)⭐ Zero keys took the ADR-0049 route. Every key in the 11-key block has a real, measured reader — ten of them directly inside the very file that declared them anonymously, which is exactly the "authorable surface with no declaration anyone can import" the ruling described.
disabledneeded no action at all: it was never actually anonymous — it'sBaseSchema.disabled, already named, already inherited byChatbotSchema, and the anonymous block's local re-declaration of it (asbooleanonly) would have narrowed away the inheritedstring-expression case had I copied it forward. I did not.Positive control, same query shape: the query that reports
disabledas "not read in this file's body" is the same plain-textschema\.<key>grep run against all eleven keys — it correctly reports real hits (with line numbers) for the other ten. A zero from a query that also produces real positives on siblings is a measurement, not a blind spot.Structural choice: extend
ChatbotSchemain place, not a new named typeThe ruling allowed either. I extended
ChatbotSchema(packages/types/src/complex.ts) directly, for three reasons:<X>Schema extends BaseSchema— there is no existing "<X>Schema+<X>ExtraSchema" pattern to follow, and inventing one here would leave thechatbotnode with two types where the ruling asks for one.ChatbotSchemais already exported frompackages/types/src/index.ts(line 303) — I did not touch that file. (Noting this explicitly per the fence:packages/types/src/index.tsis the one real collision point with the concurrent finding(components):data-tablereads two column keysTableColumndoes not declare —headerIconandfitContent#6424 dispatch, and I left it alone.)BaseSchemacarries[key: string]: any(objectui#5155). Any object typed againstChatbotSchema— new sibling type or not — already swallows any unlisted key asany. The fix that actually matters is declaring the field with its real type, so a wrong value is refusable; which interface holds the declaration is secondary. See the ablation below for the empirical version of this point.disabledis deliberately not redeclared in the new group — see the census above.onSend's message-array parameter is typedChatMessage[](the existing, already-exported authoring typeChatbotSchema.messagesuses), notplugin-chatbot's internalObjectChatMessage.renderer.tsx's own doc comment already established that a callback declaringChatMessage[]type-checks againstObjectChatMessage[](a structural superset perchatMessageAdapter.ts's documented seam), so this is a no-behavior-change, dependency-direction-respecting choice:packages/typescannot import apackages/plugin-chatbot-local type without a wrong-direction dependency.Zod mirror — kept in lockstep (the "validatable" third of the ruling)
The ruling asks for "referenceable, validatable, and documentable." The Zod mirror (
packages/types/src/zod/complex.zod.ts) gained the same ten keys, matched type-for-type. Two things that made this necessary, not optional:BaseSchema's Zod mirror is.passthrough()(confirmed empirically —.extend()preserves it without restating). Before this PR, a wrongly-typed value at any of these ten keys rode through unvalidated:ChatbotSchema.safeParse({ ..., autoResponseDelay: 'not-a-number' })returnedsuccess: true.packages/types/src/__tests__/zod-mirror-parity.test.ts(objectui#5684/finding: the #5684 zod-mirror-parity guard is BLIND to a key declared on the TS side but absent from the mirror — measured on ObjectGanttSchema, and it is the guard #5927 just used as authoritative #6058's drift ratchet) already carries aUnmirroredDeclaredledger entry forChatbotSchema('displayMode' | 'floatingConfig' | 'requestBody', pre-existing, untouched here). That ratchet cannot absorb a second key on an already-ledgered pair — adding these 10 keys to the TS declaration without mirroring them would have failedpackages/types' owntype-check(confirmed in the ablation below), not silently drifted.Tests
New file:
packages/types/src/__tests__/chatbot-authoring-face-keys.test.ts— pins both the TS and Zod halves: all ten keys accept their declared type onChatbotSchemaand through the Zod mirror; a wrong-typed value onautoResponseDelayis refused by both (a@ts-expect-erroron the TS side,safeParse().success === falseon the Zod side);disabledstays wide (boolean | string) rather than being shadowed narrower; the Zod shape lists all ten keys by name.Ablation — each leg predicted before it ran, mutated on disk, restored, re-verified
Both legs mutated
packages/typessource with atrap ... EXIT INT TERMrestoring viagit checkout HEAD -- <path>(never barecheckout --), field counts asserted before AND after the mutation (never an editor's exit code), andgit diff HEADconfirmed empty after restore, both times.Leg 1 — remove the ten Zod fields only, keep the TS declaration.
Predicted:
packages/types type-checkgoes red — the drift ratchet cannot absorb new unmirrored keys on the already-ledgeredChatbotSchemapair.First attempt, and a real correction: I first ran only
vitest run zod-mirror-parity.test.tsand it stayed green — which contradicted the prediction. The reason is itself worth recording: that file's actual drift ratchet (ReconcileAgainstLedger/assertionRatchetRejectsGrowth) is a type-levelexport type assertion... = Expect<Equal<...>>construct, invisible to a vitest run (transpile-only, types erased) and checked only bytsc. The file's fiveit()blocks test population completeness, not per-key drift. Re-ran against the actual instrument:Observed:
pnpm --filter @object-ui/types type-check→ exit 2 —src/__tests__/zod-mirror-parity.test.ts(1203,14): error TS2322: Type '"complex.zod.ts#ChatbotSchema"' is not assignable to type 'never'.Exactly the ratchet firing, exactly on theChatbotSchemapair. Restored; re-ran → exit 0.Leg 2 — remove the ten TS declaration fields only, keep the Zod mirror.
Predicted:
packages/types type-checkfails on the new test file's@ts-expect-errorbecoming an unused directive (TS2578) — without the declared type, the wrong-typed value no longer errors (swallowed byBaseSchema's index signature asany), so the pin has nothing to catch. Also predicted:packages/plugin-chatbot type-checkstays green, becauserenderer.tsx's reads ofschema.showTimestampetc. fall through the same inherited index signature toanyeither way — the only place this regression is caught is the dedicated pin test, not the renderer itself.Observed:
packages/types type-check→ exit 2,chatbot-authoring-face-keys.test.ts(93,7): error TS2578: Unused '@ts-expect-error' directive.(plus a cascading TS7006 on the now-untypedonSendcallback params).packages/plugin-chatbot type-check→ exit 0, confirming the prediction: this ablation leg has no independent guard other than the new test file. Restored; re-ran → exit 0.(One process note, not a code issue: my first pass at leg 2's
type-checkrun accidentally executed from the shared primary checkout instead of this worktree — caught immediately via the repo-root guard added to the second script, confirmed the shared checkout was untouched/clean afterward, and re-ran correctly from the worktree. No shared state was affected.)Docs
One sentence added above the
## Propertiestable, namingChatbotSchemaas the type these keys now come from — the direct answer to the #6086 gap this card's parent card recorded. No table rows edited or deleted, per the fence.Clause-② — published type surface, at contract-review tier
Yes, this changes published type surface, per the ruling. What becomes published:
ChatbotSchema(@object-ui/types) gains ten optional keys with real types (previously unreachable outsiderenderer.tsx);@object-ui/types/zod'sChatbotSchemagains the same ten as validated Zod fields (previously silently passed through). An external consumer can nowimport type { ChatbotSchema } from '@object-ui/types'and getautoResponseDelay: number, etc. with real type-checking, and canChatbotSchema.safeParse(node)from@object-ui/types/zodand have a wrong value at any of these ten keys actually refused. Noneeds:contract-reviewlabel applied (phantom label, zero readers, per this lane's standing order).Out of scope, deliberately
chatbot-enhanced/chatbot-floating's own anonymous intersections — different key sets (enableMarkdown,enableFileUpload,onClear, …), a decision for a separate card in the same family, per the fence.surfacerow, which names a key nochatbot*registration reads anywhere (a different, pre-existing defect, structurally thebody/requestBodysibling but with zero read points rather than a naming collision) — filed as finding(plugin-chatbot): the docssurfacerow names a key nochatbot*registration reads — zero read points across all three #6687, not touched here.ChatbotSchema's existinguserAvatar/assistantAvatar/showAvatars/markdown/height/onSendMessage/loadingfields, which I measured have zero read points anywhere inpackages/plugin-chatbot(an older, unrelated naming generation, already declared before this PR). Not this card's subject — the ruling named the anonymous intersection, not an audit ofChatbotSchema's pre-existing declared surface — but flagging it here since a future ADR-0049 pass over this exact interface will want it.Gate table (final commit
036da7c06)pnpm --filter '@object-ui/plugin-chatbot^...' build,--filter @object-ui/types build,--filter @object-ui/plugin-chatbot buildpnpm --filter @object-ui/types type-check(chainstsconfig.json+tsconfig.examples.json+tsconfig.test.json)pnpm --filter @object-ui/plugin-chatbot type-checkpnpm exec vitest run packages/types/ packages/plugin-chatbot/pnpm exec vitest run .../zod-mirror-parity.test.tscheck:control-bytesnode scripts/check-control-bytes.mjscheck:doc-fencesnode scripts/check-doc-fence-languages.mjscheck:doc-typesnode scripts/check-doc-component-types.mjscheck:doc-snippets--build-filterbuild (32 tasks) thennode scripts/check-doc-snippet-types.mjs;dist/complex.d.tsre-verified on disk to carry the new fields before trusting the greendocs:check-linksnode scripts/check-doc-links.mjschangeset:check(fixed-group + no-major)check-changeset-fixed.mjs,check-changeset-no-major.mjsnode scripts/check-changeset-presence.mjs.tsfiles,--format json@typescript-eslint/no-explicit-any— counts diffed againstorigin/main's versions of the same files line-for-line (3/3 renderer.tsx, 8/8 complex.ts, 2/2 complex.zod.ts): zero newLint scope note:
eslint.config.jsusestseslint.configs.recommended(notrecommendedTypeChecked/strictTypeChecked, noparserOptions.project) — non-type-aware, so this diff cannot move any untouched file's verdict; scoping to the touched files is a complete measurement, not a sample.Generated by Claude Code
Generated by Claude Code