From 4e3133a8ad27cb1b1b7433b6269dc6809ed25035 Mon Sep 17 00:00:00 2001 From: Trevor Burnham Date: Tue, 16 Dec 2025 10:45:41 -0500 Subject: [PATCH] fix: generate dark mode overrides for tokens referencing other tokens MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The resolveToken function was returning CSS variables for any token that existed in propertiesMap, including regular token-to-token references like colorBackgroundTableHeader → colorBackgroundContainerHeader. This caused both light and dark modes to resolve to the same CSS variable string (e.g., var(--color-background-container-header)), so the difference() function couldn't detect any difference between modes and didn't generate dark mode overrides. The fix restricts CSS variable resolution to only reference tokens (palette tokens like colorPrimary500) by using isReferenceToken() instead of checking token existence. This allows regular token references to continue resolving recursively to their actual values, enabling proper light/dark mode comparison and override generation. --- .../__snapshots__/index.test.ts.snap | 42 ++--- .../__snapshots__/internal.test.ts.snap | 10 +- .../public-secondary.test.ts.snap | 30 ++-- .../__snapshots__/index.test.ts.snap | 157 ++++++++++-------- src/shared/theme/resolve.ts | 3 +- 5 files changed, 132 insertions(+), 110 deletions(-) diff --git a/src/browser/__tests__/__snapshots__/index.test.ts.snap b/src/browser/__tests__/__snapshots__/index.test.ts.snap index c83deb6..b59d551 100644 --- a/src/browser/__tests__/__snapshots__/index.test.ts.snap +++ b/src/browser/__tests__/__snapshots__/index.test.ts.snap @@ -12,7 +12,7 @@ exports[`applyTheme > with baseThemeId > attaches one style node containing over } @media not print {.dark.dark.secondary-theme:not(#\\9){ --shadow-css:orange; - --boxShadow-css:var(--brown-css); + --boxShadow-css:black; }} .secondary-theme.secondary-theme .navigation:not(#\\9){ --shadow-css:pink; @@ -21,11 +21,11 @@ exports[`applyTheme > with baseThemeId > attaches one style node containing over --shadow-css:pink; } @media not print {.dark.dark.secondary-theme .navigation:not(#\\9){ - --shadow-css:var(--grey-css); + --shadow-css:grey; --buttonShadow-css:green; }} @media not print {.dark.dark.navigation.secondary-theme:not(#\\9){ - --shadow-css:var(--grey-css); + --shadow-css:grey; --buttonShadow-css:green; }}" `; @@ -42,18 +42,18 @@ exports[`applyTheme > with secondary theme > attaches one style node containing } @media not print {.dark.dark:not(#\\9){ --shadow-css:orange; - --boxShadow-css:var(--brown-css); + --boxShadow-css:brown; }} .navigation.navigation:not(#\\9){ --shadow-css:pink; --boxShadow-css:purple; } @media not print {.dark.dark .navigation:not(#\\9){ - --shadow-css:var(--brown-css); + --shadow-css:brown; --buttonShadow-css:green; }} @media not print {.dark.dark.navigation:not(#\\9){ - --shadow-css:var(--brown-css); + --shadow-css:brown; --buttonShadow-css:green; }}" `; @@ -70,18 +70,18 @@ exports[`applyTheme > with targetDocument > attaches one style node containing o } @media not print {.dark.dark:not(#\\9){ --shadow-css:orange; - --boxShadow-css:var(--brown-css); + --boxShadow-css:brown; }} .navigation.navigation:not(#\\9){ --shadow-css:pink; --boxShadow-css:purple; } @media not print {.dark.dark .navigation:not(#\\9){ - --shadow-css:var(--brown-css); + --shadow-css:brown; --buttonShadow-css:green; }} @media not print {.dark.dark.navigation:not(#\\9){ - --shadow-css:var(--brown-css); + --shadow-css:brown; --buttonShadow-css:green; }}" `; @@ -98,18 +98,18 @@ exports[`applyTheme > without secondary theme > attaches one style node containi } @media not print {.dark.dark:not(#\\9){ --shadow-css:orange; - --boxShadow-css:var(--brown-css); + --boxShadow-css:brown; }} .navigation.navigation:not(#\\9){ --shadow-css:pink; --boxShadow-css:purple; } @media not print {.dark.dark .navigation:not(#\\9){ - --shadow-css:var(--brown-css); + --shadow-css:brown; --buttonShadow-css:green; }} @media not print {.dark.dark.navigation:not(#\\9){ - --shadow-css:var(--brown-css); + --shadow-css:brown; --buttonShadow-css:green; }}" `; @@ -126,7 +126,7 @@ exports[`generateThemeStylesheet > with baseThemeId > creates override styles 1` } @media not print {.dark.dark.secondary-theme:not(#\\9){ --shadow-css:orange; - --boxShadow-css:var(--brown-css); + --boxShadow-css:black; }} .secondary-theme.secondary-theme .navigation:not(#\\9){ --shadow-css:pink; @@ -135,11 +135,11 @@ exports[`generateThemeStylesheet > with baseThemeId > creates override styles 1` --shadow-css:pink; } @media not print {.dark.dark.secondary-theme .navigation:not(#\\9){ - --shadow-css:var(--grey-css); + --shadow-css:grey; --buttonShadow-css:green; }} @media not print {.dark.dark.navigation.secondary-theme:not(#\\9){ - --shadow-css:var(--grey-css); + --shadow-css:grey; --buttonShadow-css:green; }}" `; @@ -163,18 +163,18 @@ exports[`generateThemeStylesheet > with secondary theme > creates override style } @media not print {.dark.dark:not(#\\9){ --shadow-css:orange; - --boxShadow-css:var(--brown-css); + --boxShadow-css:brown; }} .navigation.navigation:not(#\\9){ --shadow-css:pink; --boxShadow-css:purple; } @media not print {.dark.dark .navigation:not(#\\9){ - --shadow-css:var(--brown-css); + --shadow-css:brown; --buttonShadow-css:green; }} @media not print {.dark.dark.navigation:not(#\\9){ - --shadow-css:var(--brown-css); + --shadow-css:brown; --buttonShadow-css:green; }}" `; @@ -191,18 +191,18 @@ exports[`generateThemeStylesheet > without secondary theme > creates override st } @media not print {.dark.dark:not(#\\9){ --shadow-css:orange; - --boxShadow-css:var(--brown-css); + --boxShadow-css:brown; }} .navigation.navigation:not(#\\9){ --shadow-css:pink; --boxShadow-css:purple; } @media not print {.dark.dark .navigation:not(#\\9){ - --shadow-css:var(--brown-css); + --shadow-css:brown; --buttonShadow-css:green; }} @media not print {.dark.dark.navigation:not(#\\9){ - --shadow-css:var(--brown-css); + --shadow-css:brown; --buttonShadow-css:green; }}" `; diff --git a/src/build/__tests__/__snapshots__/internal.test.ts.snap b/src/build/__tests__/__snapshots__/internal.test.ts.snap index 8789fba..52f4195 100644 --- a/src/build/__tests__/__snapshots__/internal.test.ts.snap +++ b/src/build/__tests__/__snapshots__/internal.test.ts.snap @@ -5,18 +5,18 @@ exports[`builds internal themed components without errors 1`] = ` --font-family-base-91xi75:"Amazon Ember", "Helvetica Neue", Roboto, Arial, sans-serif; --color-orange-500-vz48fa:#ec7211; --color-orange-700-sk82ji:#dd6b10; - --color-background-button-primary-default-nhput1:var(--color-orange-500-vz48fa); - --color-background-button-primary-active-xbf2p5:var(--color-orange-700-sk82ji); + --color-background-button-primary-default-nhput1:#ec7211; + --color-background-button-primary-active-xbf2p5:#dd6b10; --space-xxs-c0i4qj:4px; --space-xs-djjbsd:8px; - --space-scaled-xs-yqelf2:var(--space-xs-djjbsd); + --space-scaled-xs-yqelf2:8px; } .compact-mode:not(#\\9) { - --space-scaled-xs-yqelf2:var(--space-xxs-c0i4qj); + --space-scaled-xs-yqelf2:4px; } .dark-mode:not(#\\9) { - --color-background-button-primary-active-xbf2p5:var(--color-orange-500-vz48fa); + --color-background-button-primary-active-xbf2p5:#ec7211; }" `; diff --git a/src/build/__tests__/__snapshots__/public-secondary.test.ts.snap b/src/build/__tests__/__snapshots__/public-secondary.test.ts.snap index f12accf..fed392b 100644 --- a/src/build/__tests__/__snapshots__/public-secondary.test.ts.snap +++ b/src/build/__tests__/__snapshots__/public-secondary.test.ts.snap @@ -6,31 +6,31 @@ exports[`Build-time theming of main theme with matching baseThemeId generates th --color-orange-500-25sm1g:#ec7211; --color-orange-700-2auf4j:#dd6b10; --color-background-button-primary-default-cqohzr:#aaaaaa; - --color-background-button-primary-active-h8she0:var(--color-orange-700-2auf4j); + --color-background-button-primary-active-h8she0:#dd6b10; --space-xxs-ihlmrd:4px; --space-xs-xhls0c:8px; - --space-scaled-xs-26ut8b:var(--space-xs-xhls0c); + --space-scaled-xs-26ut8b:8px; } .compact-mode:not(#\\9) { - --space-scaled-xs-26ut8b:var(--space-xxs-ihlmrd); + --space-scaled-xs-26ut8b:4px; } .dark-mode:not(#\\9) { --color-background-button-primary-default-cqohzr:#bbbbbb; - --color-background-button-primary-active-h8she0:var(--color-orange-500-25sm1g); + --color-background-button-primary-active-h8she0:#ec7211; } .secondary:not(#\\9) { --color-orange-500-25sm1g:#ec72aa; --color-orange-700-2auf4j:#dd6baa; - --color-background-button-primary-default-cqohzr:var(--color-orange-500-25sm1g); - --color-background-button-primary-active-h8she0:var(--color-orange-700-2auf4j); + --color-background-button-primary-default-cqohzr:#ec72aa; + --color-background-button-primary-active-h8she0:#dd6baa; } .dark-mode.secondary:not(#\\9) { - --color-background-button-primary-default-cqohzr:var(--color-orange-700-2auf4j); - --color-background-button-primary-active-h8she0:var(--color-orange-500-25sm1g); + --color-background-button-primary-default-cqohzr:#dd6baa; + --color-background-button-primary-active-h8she0:#ec72aa; }" `; @@ -39,30 +39,30 @@ exports[`Build-time theming of secondary theme generates the correct css files 1 --font-family-base-rpbu7d:"Amazon Ember", "Helvetica Neue", Roboto, Arial, sans-serif; --color-orange-500-25sm1g:#ec7211; --color-orange-700-2auf4j:#dd6b10; - --color-background-button-primary-default-l567s3:var(--color-orange-500-25sm1g); - --color-background-button-primary-active-h8she0:var(--color-orange-700-2auf4j); + --color-background-button-primary-default-l567s3:#ec7211; + --color-background-button-primary-active-h8she0:#dd6b10; --space-xxs-ihlmrd:4px; --space-xs-xhls0c:8px; - --space-scaled-xs-26ut8b:var(--space-xs-xhls0c); + --space-scaled-xs-26ut8b:8px; } .compact-mode:not(#\\9) { - --space-scaled-xs-26ut8b:var(--space-xxs-ihlmrd); + --space-scaled-xs-26ut8b:4px; } .dark-mode:not(#\\9) { - --color-background-button-primary-active-h8she0:var(--color-orange-500-25sm1g); + --color-background-button-primary-active-h8she0:#ec7211; } .secondary:not(#\\9) { --color-orange-500-25sm1g:#ec72aa; --color-orange-700-2auf4j:#dd6baa; --color-background-button-primary-default-l567s3:#aaaaaa; - --color-background-button-primary-active-h8she0:var(--color-orange-700-2auf4j); + --color-background-button-primary-active-h8she0:#dd6baa; } .dark-mode.secondary:not(#\\9) { --color-background-button-primary-default-l567s3:#bbbbbb; - --color-background-button-primary-active-h8she0:var(--color-orange-500-25sm1g); + --color-background-button-primary-active-h8she0:#ec72aa; }" `; diff --git a/src/shared/declaration/__tests__/__snapshots__/index.test.ts.snap b/src/shared/declaration/__tests__/__snapshots__/index.test.ts.snap index 474c7c9..1f46c81 100644 --- a/src/shared/declaration/__tests__/__snapshots__/index.test.ts.snap +++ b/src/shared/declaration/__tests__/__snapshots__/index.test.ts.snap @@ -9,161 +9,182 @@ exports[`renderDeclarations > does not render unnecessary declarations 1`] = ` exports[`renderDeclarations > includes secondary theme 1`] = ` "body{ --fontFamilyBase-css:"Helvetica Neue", Arial, sans-serif; - --fontFamilyBody-css:var(--fontFamilyBase-css); + --fontFamilyBody-css:"Helvetica Neue", Arial, sans-serif; --black-css:black; --grey-css:grey; --brown-css:brown; - --shadow-css:var(--grey-css); - --buttonShadow-css:var(--shadow-css); - --boxShadow-css:var(--shadow-css); - --lineShadow-css:var(--buttonShadow-css); + --shadow-css:grey; + --buttonShadow-css:grey; + --boxShadow-css:grey; + --lineShadow-css:grey; --small-css:1px; --medium-css:3px; - --scaledSize-css:var(--medium-css); + --scaledSize-css:3px; --appear-css:20ms; --containerShadowBase-css:2px 3px orange, -1px 0 8px olive; - --modalShadowContainer-css:var(--containerShadowBase-css); + --modalShadowContainer-css:2px 3px orange, -1px 0 8px olive; } .compact{ - --scaledSize-css:var(--small-css); + --scaledSize-css:1px; } @media not print {.dark{ - --shadow-css:var(--black-css); - --boxShadow-css:var(--brown-css); - --lineShadow-css:var(--boxShadow-css); + --shadow-css:black; + --buttonShadow-css:black; + --boxShadow-css:brown; + --lineShadow-css:brown; }} .disabled-motion{ --appear-css:0; } .navigation{ - --shadow-css:var(--black-css); + --shadow-css:black; + --buttonShadow-css:black; --boxShadow-css:purple; - --buttonShadow-css:var(--shadow-css); - --lineShadow-css:var(--buttonShadow-css); + --lineShadow-css:black; } @media not print {.dark .navigation{ - --shadow-css:var(--brown-css); - --lineShadow-css:var(--boxShadow-css); + --shadow-css:brown; + --buttonShadow-css:brown; + --lineShadow-css:purple; }} @media not print {.dark.navigation{ - --shadow-css:var(--brown-css); - --lineShadow-css:var(--boxShadow-css); + --shadow-css:brown; + --buttonShadow-css:brown; + --lineShadow-css:purple; }} .secondary-theme{ --black-css:purple; --brown-css:black; } +@media not print {.dark.secondary-theme{ + --shadow-css:purple; + --buttonShadow-css:purple; + --boxShadow-css:black; + --lineShadow-css:black; +}} .secondary-theme .navigation{ - --boxShadow-css:var(--shadow-css); + --shadow-css:purple; + --buttonShadow-css:purple; + --lineShadow-css:purple; } .navigation.secondary-theme{ - --boxShadow-css:var(--shadow-css); + --shadow-css:purple; + --buttonShadow-css:purple; + --lineShadow-css:purple; } @media not print {.dark.secondary-theme .navigation{ - --shadow-css:var(--grey-css); - --boxShadow-css:var(--brown-css); + --shadow-css:grey; + --buttonShadow-css:grey; + --boxShadow-css:black; + --lineShadow-css:black; }} @media not print {.dark.navigation.secondary-theme{ - --shadow-css:var(--grey-css); - --boxShadow-css:var(--brown-css); - --lineShadow-css:var(--boxShadow-css); + --shadow-css:grey; + --buttonShadow-css:grey; + --lineShadow-css:black; }}" `; exports[`renderDeclarations > renders declarations for theme with :root selector and context 1`] = ` ":global body{ --fontFamilyBase-css:"Helvetica Neue", Arial, sans-serif; - --fontFamilyBody-css:var(--fontFamilyBase-css); + --fontFamilyBody-css:"Helvetica Neue", Arial, sans-serif; --black-css:black; --grey-css:grey; --brown-css:brown; - --shadow-css:var(--grey-css); - --buttonShadow-css:var(--shadow-css); - --boxShadow-css:var(--shadow-css); - --lineShadow-css:var(--buttonShadow-css); + --shadow-css:grey; + --buttonShadow-css:grey; + --boxShadow-css:grey; + --lineShadow-css:grey; --small-css:1px; --medium-css:3px; - --scaledSize-css:var(--medium-css); + --scaledSize-css:3px; --appear-css:20ms; --containerShadowBase-css:2px 3px orange, -1px 0 8px olive; - --modalShadowContainer-css:var(--containerShadowBase-css); + --modalShadowContainer-css:2px 3px orange, -1px 0 8px olive; } :global .compact{ - --scaledSize-css:var(--small-css); + --scaledSize-css:1px; } @media not print {:global .dark{ - --shadow-css:var(--black-css); - --boxShadow-css:var(--brown-css); - --lineShadow-css:var(--boxShadow-css); + --shadow-css:black; + --buttonShadow-css:black; + --boxShadow-css:brown; + --lineShadow-css:brown; }} :global .disabled-motion{ --appear-css:0; } :global .navigation{ - --shadow-css:var(--black-css); + --shadow-css:black; + --buttonShadow-css:black; --boxShadow-css:purple; - --buttonShadow-css:var(--shadow-css); - --lineShadow-css:var(--buttonShadow-css); + --lineShadow-css:black; } @media not print {:global .dark .navigation{ - --shadow-css:var(--brown-css); - --lineShadow-css:var(--boxShadow-css); + --shadow-css:brown; + --buttonShadow-css:brown; + --lineShadow-css:purple; }} @media not print {:global .dark.navigation{ - --shadow-css:var(--brown-css); - --lineShadow-css:var(--boxShadow-css); + --shadow-css:brown; + --buttonShadow-css:brown; + --lineShadow-css:purple; }}" `; exports[`renderDeclarations > renders declarations for theme with non :root selector 1`] = ` ".secondary-theme{ --fontFamilyBase-css:"Helvetica Neue", Arial, sans-serif; - --fontFamilyBody-css:var(--fontFamilyBase-css); + --fontFamilyBody-css:"Helvetica Neue", Arial, sans-serif; --black-css:purple; --grey-css:grey; --brown-css:black; - --shadow-css:var(--grey-css); - --buttonShadow-css:var(--shadow-css); - --boxShadow-css:var(--shadow-css); - --lineShadow-css:var(--buttonShadow-css); + --shadow-css:grey; + --buttonShadow-css:grey; + --boxShadow-css:grey; + --lineShadow-css:grey; --small-css:1px; --medium-css:3px; - --scaledSize-css:var(--medium-css); + --scaledSize-css:3px; --appear-css:20ms; --containerShadowBase-css:2px 3px orange, -1px 0 8px olive; - --modalShadowContainer-css:var(--containerShadowBase-css); + --modalShadowContainer-css:2px 3px orange, -1px 0 8px olive; } .compact.secondary-theme{ - --scaledSize-css:var(--small-css); + --scaledSize-css:1px; } @media not print {.dark.secondary-theme{ - --shadow-css:var(--black-css); - --boxShadow-css:var(--brown-css); - --lineShadow-css:var(--boxShadow-css); + --shadow-css:purple; + --buttonShadow-css:purple; + --boxShadow-css:black; + --lineShadow-css:black; }} .disabled-motion.secondary-theme{ --appear-css:0; } .secondary-theme .navigation{ - --shadow-css:var(--black-css); - --buttonShadow-css:var(--shadow-css); - --boxShadow-css:var(--shadow-css); - --lineShadow-css:var(--buttonShadow-css); + --shadow-css:purple; + --buttonShadow-css:purple; + --boxShadow-css:purple; + --lineShadow-css:purple; } .navigation.secondary-theme{ - --shadow-css:var(--black-css); - --buttonShadow-css:var(--shadow-css); - --boxShadow-css:var(--shadow-css); - --lineShadow-css:var(--buttonShadow-css); + --shadow-css:purple; + --buttonShadow-css:purple; + --boxShadow-css:purple; + --lineShadow-css:purple; } @media not print {.dark.secondary-theme .navigation{ - --shadow-css:var(--grey-css); - --boxShadow-css:var(--brown-css); - --lineShadow-css:var(--boxShadow-css); + --shadow-css:grey; + --buttonShadow-css:grey; + --boxShadow-css:black; + --lineShadow-css:black; }} @media not print {.dark.navigation.secondary-theme{ - --shadow-css:var(--grey-css); - --boxShadow-css:var(--brown-css); - --lineShadow-css:var(--boxShadow-css); + --shadow-css:grey; + --buttonShadow-css:grey; + --boxShadow-css:black; + --lineShadow-css:black; }}" `; diff --git a/src/shared/theme/resolve.ts b/src/shared/theme/resolve.ts index d1fa61c..40cb9fe 100644 --- a/src/shared/theme/resolve.ts +++ b/src/shared/theme/resolve.ts @@ -98,7 +98,8 @@ function resolveToken( if (isReference(assignment)) { const ref = getReference(assignment); - if (propertiesMap?.[ref] && (theme.tokens[ref] || baseTheme?.tokens[ref])) { + // Only return CSS variable for reference tokens (palette tokens), not regular token references + if (propertiesMap?.[ref] && isReferenceToken('color', theme, ref)) { return `var(${propertiesMap[ref]})`; } return resolveToken(theme, ref, path, state, baseTheme, propertiesMap);