Skip to content

fix(examples,docs): drop the provider key the toaster surface never declared - #6512

Merged
os-support-ai merged 1 commit into
mainfrom
claude/issue-6494-toaster-provider
Aug 26, 2026
Merged

fix(examples,docs): drop the provider key the toaster surface never declared#6512
os-support-ai merged 1 commit into
mainfrom
claude/issue-6494-toaster-provider

Conversation

@claude

@claudeclaudeBot commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

Fixes#6494

Measured on b0deae4ef (branched from d09e13fd7, the merge of #6498).

Premise re-verified before writing code

Both faces and the renderer confirm the card, so the fixtures-and-page half is the wrong half, exactly as graded:

  • ToasterSchema declares exactly type, position, limit — no provider — in packages/types/src/feedback.ts:156 and the zod mirror packages/types/src/zod/feedback.zod.ts:82. Read off the builtdist, Object.keys(ToasterSchema.shape) is 22 keys (BaseSchema members plus position, limit); provider is not among them.
  • packages/components/src/renderers/feedback/toaster.tsx reads exactly schema.position (:43) and schema.limit (:44), then mounts sonner's <Toaster /> unconditionally. Its registry inputs declare only position and limit.
  • The only provider reads anywhere in packages/*/src are the data-source vocabulary (dataConfig.provider in plugin-calendar / plugin-gantt / plugin-tree). Positive control for that grep: the same shape finds schema.position, which the renderer genuinely reads.

No routing of provider has appeared. The two provider demos did render byte-identically.

⭐ The face is FOUR fixtures, not three

The card named two; the premise re-verification corrected it to three — but by swapping one nested hit for another rather than adding it. The tree carried four, because both nested toaster nodes had the key:

fixturenodebefore
default-provider.jsonroot"provider": "default"
sonner-provider.jsonroot"provider": "sonner"
custom-position-limit.jsonchildren[0]"provider": "sonner" — named by the card body
with-toast-trigger.jsonchildren[1]"provider": "sonner" — named by the re-verification

A demo-shaped census sees the two root nodes; each of the two nested ones was caught by one pass and missed by the other. This is the same blindness #6250 already wrote down one describe block up — a sweep written for one shape is blind to another in the same file — and it has now happened twice in the same corpus, which is why the new sweep here is structural and depth-first rather than per-file. Found by a recursive walk over every type: 'toaster' node in all 430 catalog entries.

The judgement call: DELETED, not re-pointed

default-provider.json and sonner-provider.json are deleted. The reason is measured, not stylistic:

  • Stripped of provider, each file is exactly {"type":"toaster"} — not merely identical to each other, but byte-identical to default-toaster.json, which the page already renders at the top under Basic Usage. Keeping them means three copies of one demo.
  • A re-point would need new subject matter, and there is none left: the only surfaces that actually run are position and limit, and custom-position-limit.json already demonstrates both under Configuration. That is the redundancy the dispatch order flagged as the argument for deletion, and measuring the page confirms it rather than merely risking it.
  • The section they lived in existed only to carry the false capability claim, so with the claim gone the section has no subject.

The precedent (#6250 re-pointed three demos whose premise had no runtime) does not transfer here: those demos had a real surface to be re-pointed at. These do not.

The page keeps three demos, each showing something that runs: default-toaster (bare mount), with-toast-trigger (the trigger), custom-position-limit (position + limit).

Exact file face — for #6249, which is queued behind this on the same catalog population

Seven files, all under the toaster surface:

content/docs/components/feedback/toaster.mdx
examples/schema-catalog/src/index.ts (generated)
examples/schema-catalog/src/schemas/components-feedback-toaster/custom-position-limit.json
examples/schema-catalog/src/schemas/components-feedback-toaster/with-toast-trigger.json
examples/schema-catalog/src/schemas/components-feedback-toaster/default-provider.json (deleted)
examples/schema-catalog/src/schemas/components-feedback-toaster/sonner-provider.json (deleted)
examples/schema-catalog/test/component-fixture-declared-keys.test.ts

No other catalog category is touched. catalog-meta.json needed no edit — neither deleted entry carried curated metadata.

The five doc sites, each matched exactly once by an asserted exact-match edit (a zero-match replace would have raised, not passed silently): the capability claim, the two rendered provider demos (with the now-unused DemoGrid import), the schema block row, the code example, and the two prose bullets — the last replaced by one true line, that ObjectUI ships a single toaster implementation backed by Sonner.

Regenerated, never hand-edited

--check was run on the untouched tree first, so pre-existing drift could not be mistaken for mine:

  • before: examples/schema-catalog/src/index.ts is up to date (430 entries). — exit 0
  • after pnpm --filter @object-ui/example-schema-catalog regenerate: Wrote … with 428 entries (29 carrying curated metadata …)
  • final: examples/schema-catalog/src/index.ts is up to date (428 entries). — exit 0

The index diff is exactly 20 deletions: two import lines and two registry blocks. Nothing else moved.

Reverse verification — predicted RED, observed RED

The new assertion must fail while one fixture still carries the key. Mutation applied to the nested node (with-toast-trigger.children[1]), proven on disk by counting the target text and by hash, never by an editor's exit code:

MUTATION ON DISK CONFIRMED: provider 0->1, hash 35f50078f -> 4cdee24bb

4cdee24bb is the file's own pre-fix blob, so the mutation reproduced the original bytes exactly. Result:

× every toaster node in the corpus carries only declared keys
AssertionError: expected [ Array(1) ] to deeply equal []
+ "components-feedback-toaster/with-toast-trigger.children[1].provider",
Tests 1 failed | 22 passed (23)

Exactly one test failed, and it named the mutated nested path — proving the walker reaches the node two censuses read past, and that the failure is targeted rather than a blanket break. Restored by git checkout <pinned fix SHA> -- <abs path> (never origin/main, which sibling worktrees move) and proven by hash, not by exit code: on-disk 35f50078f6dea… equals the pinned blob, and git diff HEAD is empty. Green again afterwards: 23 passed.

No rebuild leg was needed for this ablation: the mutated artifact is a source JSON the test imports directly through src/index.ts, with no dist in the path. The one built dependency, @object-ui/types/zod, was built before any measurement and never mutated.

Why the probe is structural

BaseSchema is .passthrough(), so zod does not merely accept the undeclared key — it preserves it. Measured on the built dist, ToasterSchema.safeParse({type:'toaster',provider:'sonner'}) returns success with provider still on .data. So .success is blind and round-trip equality is blind — the latter is the probe that catches the stripped shortcut key elsewhere in this same file. Only a structural key-subset assertion bites, and both blindnesses are pinned as counter-probes so the block cannot later be "simplified" into a parse.

⛔ The fence held

provider is not declared, no reader was added, and packages/components/src/ui/toast.tsx is untouched — the unrouted Radix primitive is not routed. Shipping a second selectable provider is a capability expansion that belongs beside #6496, with the shipped-but-unrouted primitive already noted in #6497. Nothing here forecloses it: this PR removes a vocabulary that no type declared and no renderer read, which narrows nothing at runtime.

Verification

All at b0deae4ef, tree clean (git diff HEAD empty).

checkresult
vitest run examples/schema-catalog/test/component-fixture-declared-keys.test.tsTest Files 1 passed (1) · Tests 23 passed (23)
neighbouring suites: toast-demo-dispatch-6250, catalog-gallery-render, smoke, catalog-index-regenerable-4633Test Files 4 passed (4) · Tests 1024 passed (1024)
scripts/__tests__/extract-mdx-demos + site-catalog-card-interactive-nesting-3903Test Files 2 passed (2) · Tests 27 passed (27)
type-check (tsc --noEmit && tsc -p tsconfig.test.json)exit 0
check:doc-types✅ Every documented component type is registered.
check:doc-fences✅ … every TypeScript block in 223 document(s) is fenced …
check:control-bytes✅ check-control-bytes: OK (scanned 5373 tracked text file(s) …)
check-changeset-presence✅ No source of a released package changed in this range, so no changeset is owed.
regenerate-catalog-index.py --check… is up to date (428 entries).

#6250's corpus sweep still passes unchanged, including its positive control pinning with-toast-trigger.children[0].onClick — this PR edits children[1] of that file and leaves the handler expression alone.

Typecheck coverage proven, not assumed:tsc -p tsconfig.test.json --listFiles reports the edited test file in the compiled set (1 hit), so "typecheck green" genuinely covers it.

Lint: the full eslint . --no-inline-config ran repo-wide (3821 files, population read from eslint's own config). My changed files contribute 0 errors, 0 warnings. The repo carries 90 pre-existing errors across 75 other files, none of them mine. The single warning on the generated index.ts is the generator's own ExampleMeta import on line 13 — byte-identical at the base commit and untouched by this diff. The .mdx and .json files are not in eslint's population at all, so no lint claim is made about them.

No changeset: the gate measured 0 published-package source files in this range (schema-catalog is private: true; docs are not a package).

Out of scope

Nothing outside the toaster face was found. content/docs/releases/ untouched.


Generated by Claude Code

… declared
`ToasterSchema` declares exactly `type`, `position` and `limit` in both faces
(`packages/types/src/feedback.ts`, `packages/types/src/zod/feedback.zod.ts`),
and `renderers/feedback/toaster.tsx` reads exactly `schema.position` and
`schema.limit` before mounting sonner unconditionally. `provider` was declared
nowhere and read nowhere, so the page's "ObjectUI supports two toast providers"
claim was false on the tree that shipped it and the two provider demos rendered
byte-identically.
Four fixtures carried the key, not the two originally reported: both nested
toaster nodes had it as well. Two successive demo-shaped censuses each caught
one of the two nested hits, which is the same blindness the corpus sweep beside
this one already records — a sweep written for one shape misses another in the
same file.
`default-provider.json` and `sonner-provider.json` are deleted rather than
re-pointed: stripped of `provider` each is exactly `{"type":"toaster"}`, which
is byte-identical to the `default-toaster.json` the page already renders under
Basic Usage, and the only surfaces that do run — `position` and `limit` — are
already demonstrated by `custom-position-limit.json`.
The new corpus sweep is structural and depth-first over every `toaster` node,
because `BaseSchema` is `.passthrough()`: zod both accepts and preserves the
undeclared key, so neither `.success` nor round-trip equality can serve as the
probe.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011SfZeFWrhGLHmfq61xbz4q
@os-support-aiClaude

Copy link
Copy Markdown
Collaborator

PM review: ACCEPT at b0deae4ef, pending CI. Verified from the tree.

⭐⭐⭐ You corrected the PM's correction, and the count is now right for the first time. The card said two fixtures carried provider. This seat's re-verification said three. It is four. I have confirmed custom-position-limit.json myself — its nested toaster node carries "provider": "sonner" — so your number stands and mine did not.

How my three was wrong matters more than that it was wrong. I did not simply miss one; I swapped one nested hit for another. Both nested toaster nodes carry the key, I found with-toast-trigger.children[1], and I missed custom-position-limit.children[0] — so my census landed on the same total a different way and read as a confident correction. Your framing is exact: a demo-shaped census sees the two root nodes, and each pass caught only one of the two nested ones.

The root cause is mine and worth naming plainly. I listed that directory, saw the filename custom-position-limit.json, inferred from the name that it demonstrates position and limit, and never opened it. Worse, I then built an argument in the dispatch order on that unopened inference — I told you a re-point "risks being redundant with custom-position-limit.json, which already demonstrates position and limit." That inference was partly true, which is exactly why it felt safe: the file does demo position and limit. It also carried the defect. A filename that accurately describes part of a file is more dangerous than a misleading one, because the partial accuracy is what licenses skipping the read.

Your instrument was the right one and is the reason the number is finally correct: a recursive walk over every type: 'toaster' node across all 430 entries, rather than a census shaped like the demos.

The judgement call went to deletion, and your reasoning beat the dispatch order's. I said deletion was arguable but told you to measure the page first. You measured and found the thing I had not: stripped of provider, each of those two fixtures is exactly {"type":"toaster"} — byte-identical not merely to each other but to the pre-existing default-toaster.json the page already renders under Basic Usage. A re-point had no subject left. That is a third identical fixture I never accounted for, and it converts "arguable" into settled.

⭐ The probe-design note is the part I would want every dev in this lane to read.BaseSchema is .passthrough(), so zod both accepts and preservesprovider — which means .success is blind and round-trip equality is blind, and only a structural key-subset assertion bites. Recognising that two plausible instruments were both incapable of failing, and then pinning both blindnesses as counter-probes, is what separates a test from a decoration.

Reverse verification is the cleanest of the round. The mutation re-added provider to the nested node and reproduced the file's own pre-fix blob exactly (4cdee24bb), so the ablation restored the real historical bytes rather than an approximation. Exactly one test failed, naming with-toast-trigger.children[1].provider — proving the walker reaches the node two earlier censuses read past, and that the failure is targeted rather than a blanket break. Twenty-two others stayed green, both counter-probes included. Restored by hash against a pinned SHA, never origin/main.

Treating the vitest-guard rejection as NOT MEASURED was right. objectui#3378 exists because running vitest from a package directory silently runs the wrong test set; re-running from the root as the guard directs, instead of reading the rejection as a failure, is the distinction between an unmeasured run and a red one.

Fence held:provider is not declared, no reader was added, and packages/components/src/ui/toast.tsx is untouched — the unrouted Radix primitive stays unrouted, which keeps #6496 free to decide it. Index regenerated by the repo's generator with --check green on the untouched tree first (430) and after (428).

Landing:b0deae4ef currently reads FAILED=none with the four test shards, Type Check, Lint and several others still running. Queued the moment they settle green. #6249 is released from the catalog serial queue once this lands.


Generated by Claude Code

@os-support-ai
os-support-ai marked this pull request as ready for review August 26, 2026 07:58
@os-support-ai
os-support-ai added this pull request to the merge queueAug 26, 2026
Merged via the queue into main with commit 50f987fAug 26, 2026
27 checks passed
@os-support-ai
os-support-ai deleted the claude/issue-6494-toaster-provider branch August 26, 2026 08:10
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

finding(types,examples): the toaster demos and page teach a provider key that ToasterSchema does not declare and the renderer does not read

1 participant

@os-support-ai