Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions .changeset/7678-tombstone-discriminator-agreement.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
---
---

Comment-only agreement fix in `@object-ui/types`. The retire-vs-remove discriminator was
stated at three sites in `complex.ts` at two different levels of amendment: the
`KanbanColumn.color` block and the `ChatbotSchema` dark-keys block gave the two prongs
without their precondition, while the `displayMode` tombstone already carried it. Both of
the first two now state the amended rule (objectui#7678) in the wording the third already
uses — a `?: never` tombstone is available only on a surviving carrier, and on one it is
used when either prong holds; a whole exported type name has no carrier and is removed
outright — so a reader landing on any of the three gets the same rule. The
`KanbanColumn.color` site's own parenthetical had named this card as its open correction;
that parenthetical is now discharged rather than repeated.

The `triggerIcon` tombstone's own rationale was the site objectui#7678 was filed about, and
it still argued from `tsc` behaviour alone — a contrast that applied equally to the two
precedent retirements that were removed outright, so it cannot be what separates the routes.
It now carries the amended discriminator and names prong 2 (the 3.3.0 release record
advertised the key, and its published JSDoc promised a default) as the reason this key earns
a tombstone, with prong 1 recorded as not holding. The route itself is unchanged.

`mobile.ts`'s `MobileOverrides` retirement note keeps its prong-1 argument, which is
settled, and gains a pointer to the amended rule and to the reason prong 2 has nothing to
guard there: this module has never had a `zod/` twin, so there is no mirror that could
accept an undeclared key and strip it silently.

`mobile.ts`'s `GestureConfig` retirement note stated the two prongs in full and without the
precondition. It now carries the amended form in the same wording as the other sites, and
records that the precondition settles the route there on its own, a whole exported type name
having no carrier to host a `?: never` member. Every existing clause is kept, including the
per-prong measurement and the note that whether a reclaim line counts as "taught as working"
is recorded on objectui#7519 rather than decided there. The removal is not re-argued.

The `objectui#6152` tripwire comment in `floating-chatbot-trigger-icon-retired.test.ts`
claimed the parse-green assertion it sits on would go red once a `FloatingChatbotConfigSchema`
is minted. Measured by injecting the arm on both twins and restoring under a trap: a
house-style non-strict `z.object` mirror reds the shape pin only (2 failures, one per twin)
and leaves that parse-green line green, while a `z.strictObject` mirror reds both (4
failures). The comment now names the shape pin as the assertion that fires and records both
readings; the shape pin gained a back-pointer.

`Clause-②: no` — no member added, removed, renamed or retyped, no accept set moved, and
nothing newly exported. The prose does reach the emitted `.d.ts` (this repo builds with
`removeComments: false`), but nothing a consumer can execute or type-check against changes,
so this releases nothing.
Original file line number Diff line number Diff line change
Expand Up @@ -152,11 +152,19 @@ describe.each([
// green before the tombstone and is green after it — the retirement changed
// the TypeScript face only, and this pins that it changed no parse outcome.
//
// ⚠️ TRIPWIRE: if objectui#6152 ever mints a `FloatingChatbotConfigSchema`,
// this goes RED. That is the intended signal, not a nuisance — whoever
// lands the mirror must add the `retirementTombstone()` half for
// `triggerIcon` at the same time, and flip this control rather than delete
// it into a vacuum.
// ⚠️ TRIPWIRE: if objectui#6152 ever mints a `FloatingChatbotConfigSchema`
// and wires it onto these twins as the `floatingConfig` arm, the assertion
// that fires is the SHAPE PIN at the foot of this block — the one reading
// `shape.floatingConfig` — and NOT this line. Measured on objectui#7678's
// base, arm injected on both twins and restored under a trap: a
// house-style non-strict `z.object` mirror reds the shape pin ONLY (2
// failures, one per twin) and leaves this parse-green line GREEN, because
// a non-strict object accepts `triggerIcon` and strips it, so `success`
// stays `true`; a `z.strictObject` mirror reds both (4 failures). Either
// shape trips the file — that is the intended signal, not a nuisance:
// whoever lands the mirror must add the `retirementTombstone()` half for
// `triggerIcon` at the same time, and flip these controls rather than
// delete them into a vacuum.
const result = twin.safeParse({
...node,
floatingConfig: { title: 'Chat', triggerIcon: 'Sparkles' },
Expand All @@ -175,6 +183,10 @@ describe.each([
it('the mirror really has no `floatingConfig` key at all', () => {
// The load-bearing fact behind everything above, asserted rather than
// assumed: a key the mirror declares would appear in its shape.
//
// ⚠️ This is also the assertion the objectui#6152 TRIPWIRE fires through:
// under a house-style non-strict mirror it reds HERE and nowhere else in
// this file. The measurement is recorded at that comment, above.
const shape = (twin as unknown as { shape: Record<string, unknown> }).shape;
expect(shape.floatingConfig).toBeUndefined();
// Lit control: a key the mirror DOES declare is present, so the reading
Expand Down
36 changes: 29 additions & 7 deletions packages/types/src/complex.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,10 +156,13 @@ export interface KanbanColumn {
* `KanbanEnhanced`, `ObjectKanban`).
*
* A tombstone rather than a plain removal on BOTH prongs of the
* discriminator the precedent changesets state (objectui#5941, #7526; the
* one-line form is under correction as objectui#7678) — a tombstone exists
* (1) to steer authors to a named live replacement KEY, or (2) to keep loud a
* key the docs taught as working:
* discriminator the precedent changesets state (objectui#5941, #7526), in
* the form objectui#7678 amended it to: a `?: never` tombstone is available
* only on a SURVIVING CARRIER — `KanbanColumn` survives this retirement,
* while a whole exported type name has no carrier and is removed outright —
* and on such a carrier it is used when either prong holds: (1) it steers
* authors to a named live replacement KEY, or (2) it keeps loud a key the
* docs taught as working. Both hold here:
*
* - prong 1: `className` is that live replacement — style a lane through
* it;
Expand Down Expand Up @@ -1054,9 +1057,12 @@ export interface ChatToolInvocation {
* UNDECLARED key is not refused, it is KEPT. That is the hazard the two-prong
* discriminator leaves to the carrier — where there is no mirror there is "no
* silent-strip hazard for prong 2 to guard" (`mobile.ts`, objectui#5941 /
* #7526 / #7678: a tombstone exists to steer authors to a named live
* replacement KEY, or to keep loud a key the docs taught as working). Here
* there IS a mirror to host the refusal, and prong 1 holds by the letter for
* #7526 / #7678: a `?: never` tombstone is available only on a SURVIVING
* CARRIER — `ChatbotSchema` survives, while a whole exported type name has no
* carrier and is removed outright — and on such a carrier it is used when
* either prong holds: it steers authors to a named live replacement KEY, or it
* keeps loud a key the docs taught as working). Here there IS a mirror to host
* the refusal, and prong 1 holds by the letter for
* four of the six — `userAvatar` → `userAvatarUrl`, `assistantAvatar` →
* `assistantAvatarUrl`, `height` → `maxHeight`, `markdown` →
* `enableMarkdown` on a `chatbot-enhanced` node. Each member's own comment
Expand Down Expand Up @@ -1864,6 +1870,22 @@ export interface FloatingChatbotConfig {
* - TOMBSTONED, both paths are refused: the declared `never` makes the
* assignment itself ill-typed, so freshness stops mattering.
*
* ⚠️ That `tsc` contrast is the MECHANISM, not the discriminator. It applied
* equally to objectui#4919 and #5942, which were removed outright, so it
* cannot be what separates the routes. What decides the route is this
* package's retire-vs-remove discriminator in its amended form
* (objectui#7678, the same wording the `chatbot` tombstones above carry): a
* `?: never` tombstone is available only on a SURVIVING CARRIER —
* `FloatingChatbotConfig` survives this retirement, while a whole exported
* type name has no carrier and is removed outright — and on such a carrier
* it is used when either prong holds. PRONG 2 is the one that holds here:
* the key was advertised in the 3.3.0 release record (2026-04-17, the "New
* ChatbotSchema floating fields" entry, which names `triggerIcon` among
* `FloatingChatbotConfig`'s options) and its published JSDoc promised
* `@default 'MessageCircle'` — the docs taught it as working. Prong 1 does
* NOT hold: there is no replacement key, which is why the guidance below
* names the trigger's own markup instead.
*
* Pinned in `__tests__/floating-chatbot-trigger-icon-retired.test.ts`,
* including the deletion contrast, so nobody can "simplify" this back into a
* deletion without that file going red.
Expand Down
26 changes: 22 additions & 4 deletions packages/types/src/mobile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,19 @@ export type ResponsiveValue<T> = T | Partial<Record<BreakpointName, T>>;
// the retired `AccordionItem.icon` / `ToggleGroupItem.icon`, which were
// likewise removed outright rather than tombstoned.
//
// That one line states prong 1 alone. The discriminator's full, amended form
// (objectui#7678) is stated in `complex.ts` beside the `chatbot` tombstones: a
// `?: never` tombstone is available only on a SURVIVING CARRIER, and on one it
// is used when EITHER prong holds — a named live replacement KEY, or keeping
// loud a key the docs taught as working. Neither branch reaches this
// retirement: an exported type name that goes whole has no carrier to host a
// `?: never` member at all, and where a member did have one, prong 1 fails
// (above) and prong 2 has nothing to guard — this module has never had a
// `zod/` twin, so there is no mirror that could accept an undeclared key and
// strip it silently, which is the hazard prong 2 guards where a mirror does
// exist. ⛔ The route itself is settled (objectui#4919); this note states the
// rule it followed and does not reopen it.
//
// Reopen condition, recorded on objectui#4919: real mobile-override renderer
// work re-enters as designed product surface on its own card, with the
// renderer landing in the same change as the declaration. Re-adding the
Expand Down Expand Up @@ -289,10 +302,15 @@ export type GestureType ='tap' | 'double-tap' | 'long-press' | 'swipe-left' | 's
// A binding written against it could not reach a handler by any path —
// `action` was a string nothing dispatched.
//
// Removed outright rather than tombstoned, measured against the two-prong
// discriminator the precedent changesets state (objectui#5941, #7526): a
// tombstone exists (1) to steer authors to a named live replacement KEY, or
// (2) to keep loud a key the docs taught as working. Prong 1: none — no
// Removed outright rather than tombstoned, measured against the discriminator
// the precedent changesets state (objectui#5941, #7526), in the form
// objectui#7678 amended it to: a `?: never` tombstone is available only on a
// SURVIVING CARRIER, and on such a carrier it is used when either prong holds
// — (1) it steers authors to a named live replacement KEY, or (2) it keeps
// loud a key the docs taught as working. `GestureConfig` is a whole exported
// type name, so it has no carrier to host a `?: never` member at all and the
// precondition settles the route on its own; the per-prong measurement that
// follows was taken anyway and is kept as the record. Prong 1: none — no
// dispatcher reads a gesture `action` (zero hits), and the only `gestures` key
// on any type is `TouchInteraction.gestures: SpecGestureConfig[]`, a different
// contract with no reader of its own. Prong 2: the CHANGELOG lines naming it
Expand Down
Loading