tokenise window-control colours + palette shadow
test / unit + widget + golden + a11y (push) Failing after 28s
test / integration_test (xvfb) (push) Has been skipped
test / bundle smoke (xvfb 5s) (push) Has been skipped
test / daemon subprocess + web WASM smoke (push) Has been skipped
test / dart doc (lib API) (push) Failing after 1m2s

Hard-coded Windows close-button red (#E81123), white close glyph, and
the palette's 0x40000000 drop shadow were the three colour sites the
UX consultant flagged as not adapting per theme. Now they're
`windowControl.closeHoverBackground` / `closeHoverForeground` /
`shadow.ambient`. Paper themes override the shadow to a softer ink so
it doesn't read as a CRT halo on cream.

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2026-05-18 09:00:52 +02:00
co-authored by Claude
parent 70c1bd3598
commit 6d4a642773
8 changed files with 62 additions and 7 deletions
+4
View File
@@ -111,6 +111,10 @@ heading, and (b) bumping `pubspec.yaml` `version:` in the same commit.
### Changed
- Window-control close-button red, white close glyph, and palette
ambient shadow are now tokens (`windowControl.closeHover*`,
`shadow.ambient`) instead of hard-coded hex. Light themes get a
softer ink-tinted shadow (T-114).
- Changelog gate is binary — dropped the soft 40-word warning, kept
the 60-word hard cap. Warnings that never blocked just normalised
drift.
+6 -2
View File
@@ -346,7 +346,7 @@ class _WinBtn extends StatelessWidget {
@override
Widget build(BuildContext context) {
final hoverBg = isClose ? const Color(0xFFE81123) : tokens.listItemHoverBackground;
final hoverBg = isClose ? tokens.windowControlCloseHoverBackground : tokens.listItemHoverBackground;
return ClideTappable(
onTap: onTap,
builder: (context, hovered, _) => Container(
@@ -354,7 +354,11 @@ class _WinBtn extends StatelessWidget {
height: hatHeight,
color: hovered ? hoverBg : null,
alignment: Alignment.center,
child: ClideIcon(icon, size: 14, color: hovered && isClose ? const Color(0xFFFFFFFF) : tokens.chromeForeground),
child: ClideIcon(
icon,
size: 14,
color: hovered && isClose ? tokens.windowControlCloseHoverForeground : tokens.chromeForeground,
),
),
);
}
+11
View File
@@ -96,6 +96,9 @@ class ThemeResolver {
modalOverlayBackground: surface[TokenKeys.modalOverlayBackground]!,
modalSurfaceBackground: surface[TokenKeys.modalSurfaceBackground]!,
modalSurfaceBorder: surface[TokenKeys.modalSurfaceBorder]!,
windowControlCloseHoverBackground: surface[TokenKeys.windowControlCloseHoverBackground]!,
windowControlCloseHoverForeground: surface[TokenKeys.windowControlCloseHoverForeground]!,
shadowAmbient: surface[TokenKeys.shadowAmbient]!,
dividerColor: surface[TokenKeys.dividerColor]!,
statusSuccess: surface[TokenKeys.statusSuccess]!,
statusWarning: surface[TokenKeys.statusWarning]!,
@@ -250,6 +253,14 @@ const Map<String, List<String>> _defaultSurfaceMap = {
TokenKeys.modalOverlayBackground: ['#C0000000'],
TokenKeys.modalSurfaceBackground: ['surface', 'semantic.mainchrome'],
TokenKeys.modalSurfaceBorder: ['accent', 'semantic.focus'],
// window controls — the Windows-style red close button is a hard
// platform convention, not a theme accent; the foreground stays
// white so the glyph remains visible across all themes.
TokenKeys.windowControlCloseHoverBackground: ['#FFE81123'],
TokenKeys.windowControlCloseHoverForeground: ['#FFFFFFFF'],
// shadow — translucent black works for dark themes; light themes can
// override this to a translucent ink (e.g. paper uses softer alpha).
TokenKeys.shadowAmbient: ['#40000000'],
// divider
TokenKeys.dividerColor: ['border', 'semantic.surface'],
// status
@@ -28,6 +28,9 @@ palette:
textMute: "#7A7A72"
accentSoft: "#218E2D10"
surface:
shadow.ambient: "#26000000"
syntax:
keyword: "#52215C"
type: "#154168"
+3
View File
@@ -25,6 +25,9 @@ palette:
textMute: "#A8A89E"
accentSoft: "#21C14B2A"
surface:
shadow.ambient: "#26000000"
syntax:
keyword: "#7B3F8C"
type: "#2A6FC1"
+26
View File
@@ -74,6 +74,11 @@ class SurfaceTokens {
required this.modalOverlayBackground,
required this.modalSurfaceBackground,
required this.modalSurfaceBorder,
// window controls
required this.windowControlCloseHoverBackground,
required this.windowControlCloseHoverForeground,
// shadow
required this.shadowAmbient,
// divider
required this.dividerColor,
// status
@@ -155,6 +160,17 @@ class SurfaceTokens {
final Color modalSurfaceBackground;
final Color modalSurfaceBorder;
/// Hover background on the Windows-style close button. Conventionally
/// the destructive-action red even on dark themes that don't otherwise
/// surface red as a CTA.
final Color windowControlCloseHoverBackground;
final Color windowControlCloseHoverForeground;
/// Ambient drop-shadow color for floating surfaces (palette,
/// dropdowns, modals). Typically a translucent black on dark themes
/// and a translucent ink on light themes.
final Color shadowAmbient;
final Color dividerColor;
final Color statusSuccess;
@@ -255,6 +271,13 @@ abstract class TokenKeys {
static const modalSurfaceBackground = 'modal.surfaceBackground';
static const modalSurfaceBorder = 'modal.surfaceBorder';
// window controls
static const windowControlCloseHoverBackground = 'windowControl.closeHoverBackground';
static const windowControlCloseHoverForeground = 'windowControl.closeHoverForeground';
// shadow
static const shadowAmbient = 'shadow.ambient';
// divider
static const dividerColor = 'divider.color';
@@ -325,6 +348,9 @@ abstract class TokenKeys {
modalOverlayBackground,
modalSurfaceBackground,
modalSurfaceBorder,
windowControlCloseHoverBackground,
windowControlCloseHoverForeground,
shadowAmbient,
dividerColor,
statusSuccess,
statusWarning,
+6 -2
View File
@@ -108,7 +108,7 @@ class _WinButton extends StatelessWidget {
@override
Widget build(BuildContext context) {
final hoverBg = isClose ? const Color(0xFFE81123) : tokens.listItemHoverBackground;
final hoverBg = isClose ? tokens.windowControlCloseHoverBackground : tokens.listItemHoverBackground;
return ClideTappable(
onTap: onTap,
builder: (context, hovered, _) => Container(
@@ -116,7 +116,11 @@ class _WinButton extends StatelessWidget {
height: hatHeight,
color: hovered ? hoverBg : null,
alignment: Alignment.center,
child: ClideIcon(icon, size: 14, color: hovered && isClose ? const Color(0xFFFFFFFF) : tokens.globalTextMuted),
child: ClideIcon(
icon,
size: 14,
color: hovered && isClose ? tokens.windowControlCloseHoverForeground : tokens.globalTextMuted,
),
),
);
}
+3 -3
View File
@@ -126,11 +126,11 @@ class _ClidePaletteState extends State<ClidePalette> {
color: tokens.dropdownBackground,
border: Border.all(color: tokens.dropdownBorder),
borderRadius: BorderRadius.circular(6),
boxShadow: const [
boxShadow: [
BoxShadow(
color: Color(0x40000000),
color: tokens.shadowAmbient,
blurRadius: 12,
offset: Offset(0, 4),
offset: const Offset(0, 4),
),
],
),