Skip to content

dogfooding fixes: theme build custom-variant type augmentations#3469

Open
josephfarina wants to merge 1 commit into
mainfrom
navi/fix/theme-build-variant-augmentation
Open

dogfooding fixes: theme build custom-variant type augmentations#3469
josephfarina wants to merge 1 commit into
mainfrom
navi/fix/theme-build-variant-augmentation

Conversation

@josephfarina

Copy link
Copy Markdown
Contributor

What this fixes

astryx theme build supports declaring custom component variants in a theme (e.g. button['variant:accentOutline']) and generates a .variants.d.ts with a TypeScript module augmentation so the custom value type-checks. In practice the augmentation was dead code, for three reasons. Fixes #3371.

The three bugs

1. Wrong interface name. The codegen emitted the augmentation against XDS${Component}${Prop}Map:

declare module '@astryxdesign/core/Button' {
  interface XDSButtonVariantMap {   // <- does not exist in core
    'accentOutline': true;
  }
}

XDSButtonVariantMap doesn't exist anywhere in @astryxdesign/core — the real, augmentable interface is ButtonVariantMap (no prefix). So the augmentation created a new, unused interface and never widened ButtonVariant, and variant="accentOutline" failed with TS2322.

2. Props with no augmentation point still got augmented. Some themeable props are backed by closed literal-union types, not an augmentable *Map interface — e.g. Button size (keyof typeof sizeStyles) and Heading type/level ('display-1' | ..., 1 | 2 | ...). The codegen assumed every prop had a ${Component}${Prop}Map interface and emitted declare module blocks against interfaces that don't exist (ButtonSizeMap, HeadingTypeMap), which are doubly dead.

3. The generated file was never referenced. Even a correct augmentation never loaded: <name>.variants.d.ts was emitted but nothing referenced it, so importing the theme's types didn't pull in the augmentation.

The fix

  • Correct interface name: emit against ${Component}${Prop}Map (e.g. ButtonVariantMap, BannerStatusMap).
  • Existence check: only emit an augmentation when @astryxdesign/core/<Component> actually exports a matching interface. This is derived by reading core's shipped dist/<Component>/index.d.ts (falling back to src/<Component>/index.ts in the monorepo), so props with no augmentation point are skipped instead of producing dead declarations. It also self-corrects if a component's interface name ever diverges from the convention.
  • Reference the generated file: the main <name>.d.ts now emits a /// <reference path="./<name>.variants.d.ts" /> directive so importing the theme also loads the augmentation.

Verification

Ran the real CLI against a theme declaring button['variant:accentOutline'], button['size:jumbo'], and heading['type:hero']:

  • .variants.d.ts targets interface ButtonVariantMap under declare module '@astryxdesign/core/Button' — no XDS-prefixed interface.
  • No augmentation is emitted for Button size or Heading type (no ButtonSizeMap / HeadingTypeMap, no declare module '@astryxdesign/core/Heading').
  • The main <name>.d.ts contains the triple-slash reference to the variants file.
  • A type-level check confirms that, with the augmentation loaded, <Button variant="accentOutline"> and the built-in <Button variant="primary"> both type-check while the module augmentation merges with core's original interface.

Tests

New build-theme.variants.test.mjs (4 cases, run against the real CLI binary like the existing build-theme.*.test.mjs):

  1. custom variant targets the real, un-prefixed ButtonVariantMap;
  2. props with no augmentation point (Button size, Heading type) are skipped;
  3. no .variants.d.ts is written when every custom value is non-augmentable;
  4. the main .d.ts references the variants file.

Existing build-theme tests (prose, import-path, path-safety) still pass; ESLint clean.

Generate the augmentation against the component's real interface
(e.g. ButtonVariantMap) instead of a non-existent XDS-prefixed name, so
custom variants declared in a theme type-check. Only emit an augmentation
when the target interface actually exists in core: props backed by closed
literal-union types (Button size, Heading type/level) have no augmentation
point, so they are skipped instead of producing dead declarations. The
main <name>.d.ts now references the generated <name>.variants.d.ts via a
triple-slash directive so importing the theme loads the augmentation.

Fixes #3371
@vercel

vercel Bot commented Jul 2, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
astryx Ready Ready Preview, Comment Jul 2, 2026 9:04pm

Request Review

@github-actions

github-actions Bot commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

PR Analysis Report

📚 Storybook Preview

View Storybook for this PR
GitHub Pages may take up to a minute to hydrate after deploy.

🧪 Sandbox Preview

View Sandbox for this PR
GitHub Pages may take up to a minute to hydrate after deploy.

No new or modified components detected.

Bundle Size Summary

Package Size (ESM) Size (CJS) Gzipped
@astryxdesign/core N/A 4.6KB 0B

Accessibility Audit

Status: No accessibility violations detected.


Generated by PR Enrichment workflow | Storybook | Sandbox | View full report

github-actions Bot added a commit that referenced this pull request Jul 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Meta Open Source bot.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

astryx theme build generates custom-variant augmentations against non-existent interface names (XDSButtonVariantMap ≠ ButtonVariantMap)

1 participant