feat(types)!: retire FloatingChatbotConfig.triggerIcon as an ADR-0049 tombstone (objectui#7654) - #7669
Conversation
…ombstone (objectui#7654) `triggerIcon` was declared `?: string` with `@default 'MessageCircle'` and read by nothing. `FloatingChatbot` destructures six of the interface's seven keys and never this one; `FloatingChatbotTrigger` takes no icon prop, so the advertised default never rendered. It is absent from the `chatbot-floating` registration's `inputs` and `defaultProps`, so no designer control offered it and no designer-created node carries it -- TypeScript was the only way to reach it. `triggerIcon?: string` becomes `triggerIcon?: never`. The refusal is TYPE-LEVEL ONLY: `FloatingChatbotConfig` has no Zod mirror, `floatingConfig` sits in the UnmirroredDeclared ledger, and `BaseSchema` is `.passthrough()`, so runtime parse behaviour is unchanged. Minting a mirror to host a `retirementTombstone()` would be the declared-but-unmirrored axis (objectui#6152), a different defect. Deletion was measured and refused: excess-property checking only reaches a fresh literal, so a deleted key still rides a widened value silently. The declared `never` refuses both paths. Both rows are pinned, the "deleted" row as a live control. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KbJQ1y1J12nZxYzFWhP8Q3
✅ 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
|
契约复核 — 裁决(逐字采纳)归属: 档位与核验:走隔离 fable 子代理(⛔ 契约复核不适用额度耗尽豁免降档)。transcript 中 harness 逐消息盖章的 独立性:只喂卡片 #7654、既有裁定与 PR 本身;⛔ 未喂派发简报、⛔ 未喂本席结论;简报按对抗性写法。 席位对三条非阻断建议的处置:③ 标题 Contract review — objectui PR #7669 (card #7654,
|
Part of #7654
Retires
FloatingChatbotConfig.triggerIconunder ADR-0049 enforce-or-remove. This is thetriggerIconhalf only of the card, per thedomain:uiPM split ruling incomment 5542748081.
Part of, notFixes, on purpose: the card's other key,displayMode, is a maintainerdecision and the card carries
needs-user-decisionfor it. A closing keyword here wouldsilently close a card that still has an open decision on it when this merges. #7654 remains
open.
Premise re-measured on this branch's base (
f7cf7e8a), with a lit controlThe card body's assertions were verified rather than trusted. All three hold:
triggerIcongit grepover tracked files, build output excluded: 2 hits — the declaration and one historical CHANGELOG lineinputsinputscarriesfloatingConfig.{position,defaultOpen,panelWidth,panelHeight,title,triggerSize}, not this keydefaultPropsfloatingConfigseed carries the same six keysLit control: the same query shape over
triggerSize, a key that is read, returns 10hits across 4 files. The zeros above are readings, not a dark instrument. Line numbers
drifted from the card (declaration is at
complex.ts:928, not:907); the facts did not.FloatingChatbotdestructures six of the interface's seven keys and never this one, andFloatingChatbotTriggertakes no icon prop at all — so the advertised'MessageCircle'default never rendered either.
The finding that changed the shape of the fix: there is no Zod mirror
The brief asked me to check whether
FloatingChatbotConfig's mirror is strict ornon-strict. It is neither — there is no mirror at all.
floatingConfigsits in theUnmirroredDeclaredledger (zod-mirror-parity.test.ts,complex.zod.ts#ChatbotSchema),and
BaseSchemaends.passthrough(), so the whole object rides through unvalidated.Consequences, stated plainly:
retirementTombstone()half in this PR, and that is not an omission.There is no twin to carry one.
carrying
floatingConfig.triggerIconparsed green before this change and parses greenafter it.
(121 declared-but-unmirrored keys across 16 schema pairs — the lane #6058's new UnmirroredDeclared ledger made visible #6152), a different defect from this card's declared-but-unread axis. This PR does
not widen into it.
A tripwire is pinned instead: the new test asserts that such a node still parses green,
so if #6152 ever mints a
FloatingChatbotConfigSchemathis goes red — the signal thatwhoever lands the mirror must add the
retirementTombstone()half at the same time and flipthe control rather than delete it into a vacuum.
Why a tombstone and not a deletion, when the usual argument is unavailable
This repo refuses deletion because an undeclared key is silently stripped by a
non-strict
z.object. That argument needs a mirror. With none here, the route was measuredon the
tscchannel alone — predicted in writing first, then observed:TS2353excess-property errorTS2322TS2322Excess-property checking only reaches a fresh literal, so deletion would have left
const raw = { triggerIcon: 'Sparkles' }; const cfg: FloatingChatbotConfig = raw;silentlyaccepting a key nothing reads. The declared
nevermakes the assignment itself ill-typed,so freshness stops mattering. The tombstone strictly dominates deletion on the only
channel available.
Both rows are pinned in the new test — the "deleted" row as a live control on a genuinely
undeclared key, not as prose, so the contrast cannot rot.
Evidence
Instrument named:
tsc -p packages/types/tsconfig.test.json --noEmit. Type-level assertionsare erased at runtime, so a green vitest run is not evidence about them. Program membership
proved with
--listFiles:complex.ts,complex.zod.tsand the new test file are allprogram inputs.
triggerIcon: 'Sparkles'andtriggerSize: 56both compiledclean; a bogus key fired
TS2353. That third reading is the discrimination control: itproves "no error" meant accepted, not not in the program.
b0d9db27to14fdcb53), then restored under anEXIT INT TERMtrap using absolute paths, verified byempty
git diff HEADand blob-hash equality back tob0d9db27.plugin-chatbotresolves@object-ui/typesthrough
dist/*.d.ts, so this proves consumers read the rebuilt declaration. Injecting atriggerIconwrite there turned a greentype-checkred with exactly one error at theinjected site; restored and re-verified by blob hash.
assignable to type
'never'". It reads'undefined'—?: neverunderexactOptionalPropertyTypes: falseisnever | undefined, which collapses toundefined.The direction was right, the printed type name was wrong. No assertion in this PR depends
on the word "never".
PRECONDITION NOT METand is reported asNOT MEASURED, never as a pass or a red: the baseline was already red (
Cannot find module '@object-ui/components') because the dependency closure had not been built. Closure built,leg re-run from a green baseline.
Union re-run after the final commit, at
a1170f28:pnpm --filter @object-ui/types type-check— exit 0 (all three tsc projects; script nameechoed, so not a zero-match)
vitest run packages/types/src/__tests__— 104 files / 1738 tests passed, thezod-mirror-parityledger includedpnpm --filter @object-ui/plugin-chatbot type-check— exit 0pnpm lint(whole repo, 47/47 tasks) — exit 0, 0 errorscheck:control-bytes,check:published-tsconfig-exclude,check:published-dist,check:shell-escape-residue,check:doc-fences— all exit 0Exit codes were captured by redirect before any pipe.
Clause-② determination: yes —
needs:contract-reviewMy own determination, and it survives the mirror finding.
FloatingChatbotConfigisexported from
@object-ui/types, so this is a published authoring surface: a write thatcompiled for an external consumer now fails their build. That is a contract change and a
breaking one for anyone authoring the key today, even though the in-repo census found no
such site.
What sharpens rather than softens it: the refusal lands in exactly one channel. A
reviewer should not read "tombstone" here and assume the usual two-channel shape — the
runtime accept set is untouched. Labelled on both carriers (#7654 and this PR). Opened as a
draft; not flipped ready, not enqueued.
Scope
packages/types/src/complex.tsand one new test, plus the changeset.displayModeisuntouched — not tombstoned, not removed, not made live.
content/docs/plugins/plugin-chatbot.mdxis not modified:
triggerIconwas never documented there (the Properties section #7656landed documents the six live
floatingConfigkeys and correctly omits this one), so thetombstone required no doc change. No other fenced file was touched.
Generated by Claude Code