Skip to content

fix(types): narrow SonnerSchema.buttonVariant to the six Button variants - #6552

Merged
os-support-ai merged 1 commit into
mainfrom
claude/issue-6541-sonner-button-variant-enum
Aug 26, 2026
Merged

fix(types): narrow SonnerSchema.buttonVariant to the six Button variants#6552
os-support-ai merged 1 commit into
mainfrom
claude/issue-6541-sonner-button-variant-enum

Conversation

@os-support-ai

Copy link
Copy Markdown
Collaborator

Fixes#6541

⚠️ This is an accept-set NARROWING on a published surface

Read this as a narrowing, not as a fix. @object-ui/types/zod currently accepts any string
for SonnerSchema.buttonVariant; after this PR it accepts exactly six. 'primary', 'danger',
'Default' and '' validate green today and start failing.

Measured on the built, published artifact, before and after (method below):

buttonVariantbefore (bf3a03c1d)after (a5c2ac6e8)what the Button draws
'default' / 'ghost' / 'link' (and the other three)ACCEPTEDACCEPTEDthe real variant
'primary'ACCEPTEDREJECTEDno background, no text colour
'danger'ACCEPTEDREJECTEDno background, no text colour
'Default'ACCEPTEDREJECTEDno background, no text colour
''ACCEPTEDREJECTEDsilently the default look
omittedACCEPTEDACCEPTEDthe default look

The direction is right — it restores declared = enforced, and every value that stops validating
is one the renderer visibly breaks on — but it removes values a published validator accepted, so
it wants the review a narrowing gets rather than the review a fix gets.

What was actually wrong

One key, on one component, shipped as two disagreeing published faces:

packages/types/src/zod/feedback.zod.ts:137 buttonVariant: z.string().optional()… ← open to validators
packages/types/src/feedback.ts:255 buttonVariant?: 'default' | … | 'link'; ← closed to type-checkers

The TS face was already correct. packages/types/src/feedback.ts is untouched by this PR
this is the mirror being made to agree with a declaration that had been sitting beside it all
along, not a shape being chosen.

Why the open string is wrong and not merely wide: packages/components/src/renderers/feedback/sonner.tsx:36
passes the value straight into <Button variant={…}>, whose vocabulary is exactly the six keys of
buttonVariants. On cva 0.7.1 an unrecognised key contributes no variant class, and
defaultVariants applies only when the value is absent or falsy.

The shape is inherited, not invented: #6496 landed exactly this z.enum on ToastSchema for
the same trigger mechanism (merged as bf3a03c1d), matched to ButtonProps['variant'] as ground
truth.

Clause ② — the published shape change, established on the BUILT artifact

Not from a source export keyword and not from a grep of the entry file. The chain, walked to its
terminus, on packages/types/dist built from this branch:

  1. packages/types/package.jsonexports['./zod'].types = ./dist/zod/index.zod.d.ts
  2. dist/zod/index.zod.d.ts:40export { …, SonnerSchema, … } from './feedback.zod.js';
  3. dist/zod/feedback.zod.d.ts:436export declare const SonnerSchema: z.ZodObject<{ … }> — terminal declaration:
 buttonLabel: z.ZodOptional<z.ZodString>;// still z.string() — the contrast in-artifact
buttonVariant: z.ZodOptional<z.ZodEnum<{default: "default";secondary: "secondary";destructive: "destructive";outline: "outline";ghost: "ghost";link: "link";}>>;

The same three lines at bf3a03c1d, built the same way in a throwaway worktree, read
buttonVariant: z.ZodOptional<z.ZodString>;.

The runtime half was measured through the published specifier, not through a source path —
import.meta.resolve('@object-ui/types/zod') reported
file:///…/packages/types/dist/zod/index.zod.js on both trees. That is where the before/after
table above comes from.

The pin, and why it is not in the mirror census

packages/types/src/__tests__/zod-mirror-parity.test.ts compares in one direction — "the
mirror accepts everything the declaration declares" — so a mirror wider than its declaration
passes it and earns no ledger entry. That is why this stood. It is one-directional by
construction and is not touched here; if that should change it is its own card.

The pin instead extends packages/components/src/__tests__/toast-button-variant-parity.test.ts,
which is where Button is in scope (@object-ui/types has zero deps and cannot import it) and
where the two-direction check already lived. Five new assertions on sonner:

  • the TS face matches ButtonProps['variant'] in both directions (type-level, and it is
    compiled — see below);
  • the zod mirror accepts exactly the six, by set equality read off .options rather than
    restated. Under a z.string() there is no .options to read, so it throws naming the card
    instead of comparing two empty lists and passing;
  • every value the Button draws is accepted;
  • every value that renders colourless is refused — each rejection paired in the same iteration
    with the buttonVariants() call that shows the breakage, so neither half can drift from the
    other;
  • '' gets its own pin, because its rendering is the opposite of the others (silently default);
  • the key stays optional, so a bare { type: 'sonner' } still parses.

Reverse verification

Performed on the committed state, restored from HEAD (not from origin/main), with a
trap … EXIT INT TERM and absolute paths.

Mutation: the enum put back to z.string(). Proven on disk before measuring — injected text
grep -c = 1, removed text grep -c = 1, and the blob hash moved
6f8dd04f…c2c969e4…. No rebuild was involved and none was needed: the vitest alias
(vitest.config.mts:260) maps @object-ui/types/zod to source, and dist/ still held the
enum at that moment — so a pin reading dist would have stayed green. It did not:

 × the ZOD MIRROR accepts exactly the six — the direction the census cannot see
Error: `SonnerSchema.buttonVariant` is not an enum in the zod mirror … (objectui#6541)
× the mirror refuses the values that render colourless — measured, not assumed
AssertionError: mirror accepted non-variant 'primary': expected true to be false
× the mirror refuses `""` — the one wrong value that does not look wrong
Test Files 1 failed | 1 passed (2) Tests 3 failed | 15 passed (18)

The second measurement is the point of the card: in that same run
zod-mirror-parity.test.tspassed. The census is green on the exact defect, which is the
blindness #6541 documents, reproduced rather than quoted.

Restore leg proven the same way, not by an exit code: git diff HEAD empty and the blob hash back
to 6f8dd04f…, byte-identical to HEAD. Re-run on the restored tree: 4 files, 61 tests, all
passing.

Also in this diff — one comment correction, named rather than slipped in

packages/types/src/__tests__/toast-button-keys.test.ts carried, from #6496, a test title and
comment asserting in the present tense that Sonner's mirror isz.string() and that the
disagreement is "deliberately NOT resolved here". This PR makes both sentences false. Prose only —
the assertion under them is unchanged and still guards ToastSchema. Same defect class, same file
family, same gate family, and no in-flight PR touches packages/types. The stale sentence in the
parity test's own header got the same treatment.

content/docs/components/feedback/sonner.mdx:46 needed no change — it already documented the
six-member union, because it was describing the TS face. The mirror was the outlier; the docs now
match both faces. (Verified, not assumed.)

Blast radius

The key stays optional. The only fixtures in the repo that set it —
examples/schema-catalog/src/schemas/components-feedback-sonner/{error,promise-based-toast}.json
use destructive and outline, both inside the six. A repo-wide buttonVariant sweep found no
consumer relying on a seventh spelling, so there is no fork to report.

Verification (all on a5c2ac6e8, the final commit)

Heavy commands ran through the container's shared verify lock; each verdict below is the gate's own
line, not a bare $?.

  • pnpm exec vitest run packages/components/src/__tests__/toast-button-variant-parity.test.ts packages/types/src/__tests__/toast-button-keys.test.ts packages/types/src/__tests__/zod-mirror-parity.test.ts examples/schema-catalog/test/component-fixture-declared-keys.test.ts
    Test Files 4 passed (4) / Tests 61 passed (61)
  • pnpm --filter @object-ui/types type-check && pnpm --filter @object-ui/components type-check → exit 0
    (both script names echoed, so neither was a zero-match no-op). tsc -p packages/components/tsconfig.test.json --listFiles confirms the edited test file is in the program — the type-level pins are compiled,
    not merely written.
  • node scripts/check-changeset-presence.mjs✅ 3 source file(s) of 2 released package(s) changed, and this change declares 1 changeset(s)
  • check-changeset-no-major✅ No changeset declares a major bump. · check-changeset-fixed ·
    check-changeset-overwrite✅ No pre-existing changeset was modified or deleted.
  • check-control-bytes✅ OK (scanned 5407 tracked text file(s)), plus a direct
    grep -naP '[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]' over the four changed files: no match.
  • check:phantom-deps✅ Every in-scope import is declared by the package that publishes it.
    (the new @object-ui/types/zod import is a declared dependency of @object-ui/components) ·
    check:self-import · check:doc-types · check:designer-field-key-parityOK ·
    check:vi-mock-specifiers

Declared narrowings, so they read as measurements and not as gaps:

  • Lint was run on the three changed source files (--no-inline-config, --format json:
    3 files, 0 errors, 0 warnings) rather than repo-wide. The narrowing is sound because
    eslint.config.js enables no type-aware linting (no project / projectService) and no
    package carries its own config — so this diff cannot move the verdict on any file it does not
    touch. CI runs the full farm regardless.
  • check:doc-snippets and check-readme-exports were NOT MEASURED here, not green and not
    red: both exited on their own precondition (PRECONDITION NOT MET (exit 2) — the snippet program was NOT run; the population COLLAPSED — this run proves nothing, packagesRead: found 2, floor is 25), because they need the whole workspace built. Not narrowed, not skipped quietly — CI owns
    them.
  • Full pnpm test / pnpm type-check / pnpm lint belong to CI.

Overlap with in-flight work

None. Open PRs #6543 (plugin-designer), #6544 (app-shell), #6546 (plugin-gantt) share no file
or package with this diff, and the named in-flight cards (#6465, #6332, #6534, #6538) are in other
packages.

Out of scope

A third specimen for the "three declared surfaces disagree" class — buttonVariant is declared on
both published faces of both toast and sonner and read by both renderers, but appears in
neither registry inputs array — was recorded as a comment on #4631 rather than filed as a new
card, because that is the class #4631 is pm:on-hold to decide and a separate issue would fragment
it. #4631 remains open and nothing here changes it.


Generated by Claude Code

The zod mirror spelled `buttonVariant` as `z.string()` while the TS face beside
it declared a six-member union, so one key on one component shipped as an open
string to anyone validating (`@object-ui/types/zod`) and as a closed union to
anyone type-checking (`@object-ui/types`).
This is an accept-set NARROWING on a published surface: `'primary'`, `'danger'`,
`'Default'` and `''` used to validate and now fail. Every one of them renders a
broken button today -- the renderer passes the value straight into
`<Button variant={...}>`, and on cva 0.7.1 an unrecognised key contributes no
variant class while `''` is silently resolved to `default` by the falsy
fallback -- so nothing that renders correctly stops validating.
Only the mirror changes; the TS face already declared these six. The shape is
inherited from the sibling ruling that landed the same enum on `ToastSchema`.
The pin lives in `components/src/__tests__/toast-button-variant-parity.test.ts`
rather than in the mirror census, because that census compares in one direction
only -- a mirror wider than its declaration passes it -- and because measuring
the accept-set against the Button's own vocabulary requires somewhere `Button`
is in scope, which the zero-dep types package by construction is not.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011SfZeFWrhGLHmfq61xbz4q
@os-support-aiClaude

Copy link
Copy Markdown
CollaboratorAuthor

ACCEPT — objectui#6541 (domain:ui lane, PM review, Bug tier). Reviewed from the tree at a5c2ac6e8.

⭐⭐⭐ The card's blindness claim was reproduced, not quoted

The card asserted that zod-mirror-parity.test.ts cannot catch this class, because it compares in one direction only. The dev did not repeat that claim — it put the defect back and watched that suite stay green:

SECOND measurement in the same run: zod-mirror-parity.test.ts PASSED under the mutation — the census is green on the exact defect, so the card's blindness claim is reproduced, not quoted.

That is the difference between "a gate is blind here" as an argument and as a measurement. It also means the next person who proposes relying on that census for this class has a red-on-record answer.

⭐⭐⭐ The stale dist was used as a control

The subtlest thing in this report:

NO REBUILD LEG, and none was owed: vitest.config.mts:260 aliases @object-ui/types/zod to SOURCE, and dist/ still held the enum at that moment, so a pin reading dist would have stayed green — it went red, which proves it read source.

A stale build artifact is normally the hazard that makes an ablation lie. Here it was left in place deliberately and turned into the discriminator: the two hypotheses ("the pin reads source" / "the pin reads dist") predict opposite results, and the observed red picks one. That is better than rebuilding, because rebuilding would have made both hypotheses predict red.

The narrowing is stated as data, through the published specifier

import.meta.resolve('@object-ui/types/zod') confirmed the same resolved path on both trees, then:

valuebeforeafter
'primary', 'danger', 'Default', ''acceptedrejected
the six variants, and omittedacceptedaccepted

That is an accept-set narrowing on a published surface, measured rather than asserted — and the PR body says so in those words, as triage required. A narrowing described as "a fix" reads as risk-free; this one is described as what it is.

⭐ Clause-② established on the built artifact with an in-artifact control: dist/zod/feedback.zod.d.ts:436 terminal export, where buttonVariant now reads z.ZodOptional<z.ZodEnum<{default..link}>> while buttonLabel one line above still reads z.ZodOptional<z.ZodString>. Using the unchanged neighbour as the control inside the same file is neat: it rules out "the whole artifact regenerated differently" without a second build.

Verified from the tree

Only SonnerSchema.buttonVariant changed. ToastSchema's own declaration is untouched — only its comment updated. packages/types/src/feedback.ts is untouched, exactly as the order predicted, because its TS face had declared these six all along. This really was the mirror being made to agree with a declaration sitting beside it.

The two prose corrections are correct to include

toast-button-keys.test.ts and the parity header both asserted in the present tense that Sonner's mirror isz.string() and that the disagreement is "deliberately NOT resolved here". This commit makes both false. Verified: the edits are tense-only (usesused) plus the note that #6541 has since closed the gap; assertions unchanged.

Leaving a false present-tense statement behind on a card whose whole subject is two faces disagreeing would have planted the next card. Same principle this lane applied on #6505, and it is the right call here for the same reason.

⭐⭐ The out-of-scope finding was not filed, for a good reason

buttonVariant is declared on both published faces of bothtoast and sonner and read by both renderers, yet appears in neither registry meta inputs array — a third specimen of #4631's "three declared surfaces disagree" class.

The dev recorded it as a comment on #4631 rather than filing a card, because #4631 is pm:on-hold on exactly the question that decides whether this is a bug (is inputs a curated subset or a mirror of the type?), and sonner's inputs also omits title. Filing it as a defect would have been picking one side of a held decision.

⭐ That is the right instinct and I want it on the record: a held decision is not an invitation to file its answer as a defect. Evidence goes to the held card; the ruling stays the maintainer's.

Instrument hygiene

Mutation proven on disk before measuring (injected/removed text counts plus a blob-hash move), restore proven by state — empty git diff HEAD and hash back to 6f8dd04f — not by a restore command's exit code. The --listFiles check was necessary rather than ceremonial here: the package's own tsconfig.json excludes src/__tests__, so without it the type-level pins might have compiled nowhere.

Two gates reported as neither green nor red: check:doc-snippets (PRECONDITION NOT MET (exit 2) — the snippet program was NOT run) and check-readme-exports (the population COLLAPSED … found 2, floor is 25). Both need a full workspace build; CI owns them.

On the queueing note

You flagged that objectui's AGENTS.md permits self-queueing a green non-governed PR while this dispatch contract says report at draft and leave CI convergence to me. Following the dispatch contract was right — that is this seat's landing discipline, and it exists so the PM verifies the head it reviewed is the head that lands. Keep doing it that way; I will raise the wording difference where it belongs rather than have each dev rediscover it.

CI: 29 checks, zero failed, 10 running, on the head reported. Landing on green.


Generated by Claude Code

@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 52 chunks)3234.4 KB3266.6 KB
Main entry chunk (gzip)157.0 KB350 KB
Entry fileindex-BDDe7Ree.js
StatusPASS

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

PackageSizeGzipped
app-shell (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)11.71KB4.46KB
app-shell (runtime-config.js)18.10KB6.51KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)40.18KB10.59KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.15KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.65KB2.22KB
auth (SocialSignInButtons.js)9.61KB3.89KB
auth (UserMenu.js)3.41KB1.23KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.80KB
auth (createAuthenticatedFetch.js)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
collaboration (CommentThread.js)26.08KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.68KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)506.01KB114.64KB
core (index.js)5.30KB2.13KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)173.10KB47.96KB
fields (index.js)238.89KB60.02KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)33.40KB8.71KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.95KB10.97KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.75KB
mobile (index.js)1.55KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.72KB0.42KB
mobile (useResponsiveConfig.js)1.37KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)9.53KB3.38KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)4.64KB1.50KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.93KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.53KB
permissions (usePermissions.js)1.93KB0.88KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)46.91KB12.92KB
plugin-charts (index.js)64.66KB18.32KB
plugin-chatbot (index.js)188.60KB44.82KB
plugin-dashboard (index.js)133.48KB34.49KB
plugin-designer (index.js)212.80KB43.15KB
plugin-detail (index.js)245.29KB62.39KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)131.78KB32.19KB
plugin-gantt (index.js)165.16KB40.33KB
plugin-grid (index.js)201.66KB54.57KB
plugin-kanban (index.js)53.16KB14.65KB
plugin-list (index.js)112.74KB27.50KB
plugin-map (index.js)20.09KB6.62KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)26.72KB7.71KB
plugin-tree (index.js)9.26KB3.13KB
plugin-view (index.js)84.85KB20.79KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.45KB0.23KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)4.47KB1.63KB
react (SchemaRenderer.js)63.21KB21.05KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)2.44KB1.21KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)12.13KB3.65KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)7.54KB2.63KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.72KB2.24KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)2.59KB1.31KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
types (ui-action.js)3.40KB1.71KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

@os-support-ai
os-support-ai marked this pull request as ready for review August 26, 2026 11:10
@os-support-ai
os-support-ai added this pull request to the merge queueAug 26, 2026
Merged via the queue into main with commit ce503e5Aug 26, 2026
30 checks passed
@os-support-ai
os-support-ai deleted the claude/issue-6541-sonner-button-variant-enum branch August 26, 2026 11:23
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): SonnerSchema.buttonVariant is z.string() in the mirror and a six-member union in TS — the two published faces disagree

2 participants

@os-support-ai@claude