feat(types)!: retire six dark ChatbotSchema keys as ADR-0049 tombstones (objectui#7703) - #8154
Conversation
loading, showAvatars, userAvatar, assistantAvatar, markdown and height were declared on ChatbotSchema, mirrored on its Zod twin, and read by no plugin-chatbot registration: a schema.KEY census per ComponentRegistry.register body returns 0/0/0 for all six, with placeholder 1/1/1, messages 1/1/1, userAvatarUrl 1/1/1, maxHeight 1/1/0, floatingConfig 0/0/1 and processVisibility 0/1/0 lit on the same instrument. Each becomes `?: never` on complex.ts plus retirementTombstone() on complex.zod.ts — both halves, the convention #6972 / #6355 / #7779 already carry. Deleting them was the wrong route: all six have a Zod arm, and BaseSchema is .passthrough() with a [key: string]: any index signature, so an undeclared key is KEPT, not refused. Enforce was refused per key: <Chatbot>, the component this registration renders, declares none of the six, so enforcing means growing a component prop or publishing a second spelling of a key that already works. showAvatars is the one key the FENCE turned dark rather than a key nothing ever read: <ChatbotEnhanced> has such a prop and chatbot-floating's raw props spread delivered an authored value to it until #7708 ruled fence (PR #8077). The distinction is recorded in the tombstone comment, the changeset and the pin. processVisibility is NOT folded in — chatbot-enhanced reads it (0/1/0) — and is pinned live as the scope control. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YBWFb5YgMU5dw8p2VKj16S
✅ 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 review — PASS — PR #8154 (objectui#7703) Reviewed-by:
|
|
Landing — PR #8154 (objectui#7703), head Pre-landing three: ① Contract review PASS (comment 5562189869) at ② CI converged, enumerated 32 against Read explicitly, because each mattered for a different reason on this diff:
The single failure is
⇒ that is still a sufficient discriminator, and here is why: the window contains no Playwright output and no test failure — the job died at backend readiness, before any objectui code was exercised. A failure this PR caused would have to appear after that point and would look nothing like this. Carded objectstack#16186; the red is by design per objectui#7689, whose triage ⛔ forbids reverting the pin. ⛔ No re-run spent. ③ Ready flipped, auto-merge SQUASH enabled ( ⭐ Landing control established BEFORE the queue takes it, on both faces:
⇒ both zeros are readings on a live file, and both faces are checked because a tombstone that landed on only one of them is exactly the half-state this card exists to prevent.
Generated by Claude Code |
Fixes #7703
Retires the six
ChatbotSchemamembers noplugin-chatbotregistration reads —loading,showAvatars,userAvatar,assistantAvatar,markdown,height— as ADR-0049 retirement tombstones on both published faces:?: neveronpackages/types/src/complex.tsplusretirementTombstone()onpackages/types/src/zod/complex.zod.ts.chatbotdocument authoring one of the six parses green today and is refused by name afterwards.needs:contract-reviewis attached and this PR stays DRAFT; it does not enqueue while that label is on it.1. Per-registration census — rebuilt on this branch's base, with its own lit control
The card's instrument, rebuilt here rather than inherited:
packages/plugin-chatbot/src/renderer.tsxsplit at its threeComponentRegistry.register(...)calls (bodies at lines 60-240 / 241-378 / 379-531), oneschema.KEYcount per body. Base21d7989fb— i.e. after #7708's fence landed as #8077.chatbotchatbot-enhancedchatbot-floatingloadingshowAvatarsuserAvatarassistantAvatarmarkdownheightplaceholder(lit control)messages(lit control)userAvatarUrl(lit control)maxHeight(lit control)floatingConfig(lit control)processVisibility(lit control)Six controls light on the same pass, three of them asymmetric across the three bodies — so the zeros are readings, not a blind grep, and the instrument distinguishes registrations rather than reporting a package-wide number. Whole-package word-boundary counts (tests excluded) agree:
userAvatar0,assistantAvatar0,showAvatars8 (allChatbotEnhancedcomponent-prop sites),markdown5,loading3,height13 (the last two unrelated identifiers), againstplaceholder28.2.
showAvatarsis the key the FENCE turned dark — not a key nothing ever readStated deliberately, because the two provenances are different facts and the card flagged this as the thing that differs.
ChatbotEnhancedreally does declareshowAvatars?: boolean(ChatbotEnhanced.tsx:489, read at 1368 / 3097-3114 / 3295), and until #7708 thechatbot-floatingregistration ended itsFloatingChatbotelement with a raw trailing{...props}spread that handed an authored value straight to it. That card was ruled fence and landed as #8077;renderer.tsx:449now reads{...toDomProps(props)}at the head of the element. So the key is dark on all three registrations by ruling.The other five were live on no channel at any time: they are not
ChatbotEnhancedPropsmembers either —markdownexists there only asenableMarkdown— so the spread had nothing to land them on. The changeset says exactly this, and the pin asserts the sentence.3. Enforce-or-remove, one decision per key
Chatbot— the component thechatbotregistration renders (plugin-chatbot/src/index.tsx:20-31) — declaresmessages,placeholder,onSendMessage,disabled,showTimestamp,userAvatarUrl,userAvatarFallback,assistantAvatarUrl,assistantAvatarFallback,maxHeight. Not one of the six.Chatbot?loadingisLoadingfromuseObjectChatand spends it ondisabled={hostDisabled || isLoading}. A static authored boolean would fight the runtime, not configure itshowAvatarsChatbotEnhancedwould re-open by declaration the channel #7708 closed by fence, one card earlierchatbotnode already renders an avatar beside every message (unconditionally, no gate)userAvataruserAvatarUrlalready carries (AGENTS.md #0.1)userAvatarUrl(+userAvatarFallback)assistantAvatarassistantAvatarUrl(+assistantAvatarFallback)markdownChatbotprints content as text and has no markdown path; on the two nodes that do render markdown,enableMarkdownis the live keytype: 'chatbot-enhanced'withenableMarkdownheightChatbothas noheightprop, and the livemaxHeightit forwards is astring, not this key'sstring | numbermaxHeight, orfloatingConfig.panelHeighton a floating node⛔
processVisibilityis not folded in — it reads 0 / 1 / 0 and #7655 left the member as it was. It is pinned live as the scope control (and still validated:processVisibility: 'loud'still fails).4. Why tombstones and not deletions
All six have a Zod arm, which is what decides the route here.
BaseSchemais.passthrough()on the Zod side and carries[key: string]: anyon the TS side, so an UNDECLARED key is not refused — it is kept. Deleting the members would hand the authored spelling exactly the silent no-op this card exists to close, on both faces at once. The "deleted" row is pinned live as a control in the new test, so the contrast cannot rot into prose.5. Evidence
Tests —
packages/types/src/__tests__/chatbot-dark-keys-retired-7703.test.ts, 40 cases:Ablation (reverse verification), run from the committed state, expected direction RED — observed RED on both legs. Reverted
showAvatarsalone on both faces:Exactly the four
showAvatarscases went red and the other thirty-six stayed green, so the pin is per key, not a blanket. The@ts-expect-errorlegs are real enforcement — this package type-checks its tests throughtsconfig.test.json(confirmed bytsc --listFiles, the new file is in the compile set), and vitest cannot see them.Gates (verdict lines, exit codes captured before any pipe):
pnpm lint(whole repo)Tasks: 47 successful, 47 total·0 errors(2905 pre-existing warnings)pnpm exec turbo run type-check(whole repo)Tasks: 81 successful, 81 totalpnpm --filter '...@object-ui/types' build— the downstream consumer direction, 43 packagescheck:control-bytesOK (scanned 6509 tracked text file(s))check:doc-typesEvery documented component type is registered.check:doc-snippets572 of 572 block(s) judged, 0 failed(after its declared build precondition)check:doc-fencescheck:handler-key-readsOK 106 arm(s) … every judged read is a declared member of its armcheck:doc-example-readerscheck:readme-exportsunbuilt-packageon the untouchedplugin-ai, a precondition, not a finding)check:unreferenced-sourceschangeset:check+check-changeset-presence4 source file(s) … declares 1 changeset(s)·No changeset declares a major bumpcheck-governed-queue-guard --testNOT GOVERNED — none of 6 paths matchedzod-mirror-parity.test.tsneeds no new ledger row: a?: nevermember is still a declared key and aretirementTombstone()arm is still a mirrored key, so the pair stays in lockstep — the same routeviewTabBar(#7779) andhideEmpty(#7129) took. Green in the run above without an edit.6. Files, and one declared in-place fix beyond the claim's list
The claim named
complex.ts,zod/complex.zod.ts, a new pin and a parity-ledger row if the suite demanded one (it did not). Two files beyond that list, both declared here:content/docs/plugins/plugin-chatbot.mdx— the published page still taught the fenced channel in the present tense: "chatbot-floatingis different today … (processVisibility,surface,showAvatars) do reach the panel", plus the same claim inside thesurfaceandprocessVisibilitytable rows. fix(plugin-chatbot): fence chatbot-floating's raw props spread #8077 landed the fence and touched no doc (git show --stat d3499b315: one changeset, two test files, one renderer). Shipping a tombstone forshowAvatarswhile the docs say it reaches the panel would publish a self-contradiction about the exact key whose provenance sentence this card is about, so the three claims are corrected to what fix(plugin-chatbot): fence chatbot-floating's raw props spread #8077 left behind. The page also gains the restatement of the six removed keys with their replacements (finding(views): the gantt and timeline branches still fabricate date-axis field names on all three faces — and app-shell's own #3129 note certifies them as already fixed #7070: a control is restated, never deleted into a vacuum) — none of the six had a row in the property table to remove.packages/types/src/__tests__/chatbot-registration-authoring-faces-7655.test.ts— comment only, same staleness: its header assertedshowAvatars"are LIVE on achatbot-floatingnode today". No assertion changed; that file'sassertionChatbotKeepsItsWholeFacealready anticipated this retirement ("a?: nevermember is still a declared key, so the census does not move when a key is tombstoned") and stays green untouched.⛔
packages/types/src/registry.tsis untouched — the region fence with the concurrent #7704 holds. ⛔BaseSchema's index signature (#5155) is untouched.7. Blast radius
ChatbotSchemaused to compile and now does not — including through a widened, non-fresh value, the half a deletion would have missed on aBaseSchemacarrier.chatbotdocument authoring one of the six went from green toinvalid_typeat the key's own path, with the guidance as the message. This is the narrowing under review.chatbot-enhanced/chatbot-floating: unchanged in both directions. Those faces never declared the six (finding(types):ChatbotSchemapinstypeto'chatbot', sochatbot-enhancedandchatbot-floatingnodes have no authoring-face type #7655 censused them out), their twins have no arm to refuse one, and.passthrough()keeps the value. Pinned in both directions.Generated by Claude Code