From 86ad659121605281ac80b681503cf50f765fa934 Mon Sep 17 00:00:00 2001 From: Jeroen Schweitzer Date: Thu, 23 Apr 2026 08:52:44 +0200 Subject: [PATCH] add chrome token set for frame surfaces MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit New tokens: chromeBackground, chromeForeground, chromeBorder — shared root for hat bar, sidebar, context panel, status bar, spines, and drag handles. All resolve to bgSunken/textDim/border in the palette by default. Themes can override individually to diverge surfaces. Replaces direct sidebarBackground/statusBar references in chrome contexts. Updated theme-ui skill. Co-Authored-By: Claude Opus 4.6 (1M context) --- .claude/skills/theme-ui/SKILL.md | 17 +++++++++-------- lib/app.dart | 18 +++++++++--------- lib/kernel/src/panels/drag_resize.dart | 2 +- lib/kernel/src/theme/resolver.dart | 7 +++++++ lib/kernel/src/theme/tokens.dart | 16 ++++++++++++++++ lib/widgets/src/clide_spine.dart | 2 +- 6 files changed, 43 insertions(+), 19 deletions(-) diff --git a/.claude/skills/theme-ui/SKILL.md b/.claude/skills/theme-ui/SKILL.md index bf1e713c..20fa50ea 100644 --- a/.claude/skills/theme-ui/SKILL.md +++ b/.claude/skills/theme-ui/SKILL.md @@ -18,19 +18,19 @@ Never hardcode colors. Never use Material/Cupertino color constants. Pick tokens based on **where** the widget lives, not what it does. -### Chrome (hat bar, status bar, spines) +### Chrome (hat bar, status bar, sidebar, context panel, spines, drag handles) ``` -background → sidebarBackground -border → dividerColor (1px) -text → globalTextMuted +background → chromeBackground +text → chromeForeground +border → chromeBorder (1px) active text → globalForeground ``` ### Side panels (sidebar, context panel) ``` -background → sidebarBackground (left) / panelBackground (right) +background → chromeBackground (both sides — they're chrome frame) text → sidebarForeground hover → sidebarItemHover selected → sidebarItemSelected @@ -149,9 +149,10 @@ The palette layer has these depth primitives: - `surface` (`#242838`) — elevated: pane headers, active tabs - `surfaceHi` (`#2C3046`) — interactive: hover states, selections -**Pending:** `chromeBackground`/`chromeForeground`/`chromeBorder` tokens -need to be added to `SurfaceTokens` so hat bar, sidebar, and status bar -share a named root instead of cross-referencing each other's tokens. +Chrome tokens (`chromeBackground`/`chromeForeground`/`chromeBorder`) are +the shared root for all frame surfaces. They resolve to `bgSunken` / +`textDim` / `border` in the palette. Themes can override them to diverge +hat from sidebar from status bar if desired. ## Anti-patterns diff --git a/lib/app.dart b/lib/app.dart index 3c1b63ec..5e6204a4 100644 --- a/lib/app.dart +++ b/lib/app.dart @@ -177,7 +177,7 @@ class RootLayout extends StatelessWidget { if (statusVisible) Container( height: statusHeight, - decoration: BoxDecoration(border: Border(top: BorderSide(color: ClideTheme.of(ctx).surface.dividerColor))), + decoration: BoxDecoration(border: Border(top: BorderSide(color: ClideTheme.of(ctx).surface.chromeBorder))), child: Row( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ @@ -222,8 +222,8 @@ class _HatBar extends StatelessWidget { child: Container( height: hatHeight, decoration: BoxDecoration( - color: tokens.sidebarBackground, - border: Border(bottom: BorderSide(color: tokens.dividerColor, width: 1)), + color: tokens.chromeBackground, + border: Border(bottom: BorderSide(color: tokens.chromeBorder, width: 1)), ), padding: const EdgeInsets.symmetric(horizontal: 8), child: Row( @@ -238,7 +238,7 @@ class _HatBar extends StatelessWidget { return ClideText( name != null ? 'clide > $name' : 'clide', fontSize: 12, - color: tokens.globalTextMuted, + color: tokens.chromeForeground, fontFamily: clideMonoFamily, ); }, @@ -343,7 +343,7 @@ class _WinBtnState extends State<_WinBtn> { width: 36, height: hatHeight, color: _hover ? hoverBg : null, alignment: Alignment.center, - child: ClideIcon(widget.icon, size: 14, color: _hover && widget.isClose ? const Color(0xFFFFFFFF) : widget.tokens.globalTextMuted), + child: ClideIcon(widget.icon, size: 14, color: _hover && widget.isClose ? const Color(0xFFFFFFFF) : widget.tokens.chromeForeground), ), ), ); @@ -442,7 +442,7 @@ class _SidebarSlot extends StatelessWidget { Widget build(BuildContext context) { final tokens = ClideTheme.of(context).surface; return Container( - color: tokens.sidebarBackground, + color: tokens.chromeBackground, alignment: Alignment.topLeft, padding: const EdgeInsets.fromLTRB(2, 2, 0, 0), child: active.build(context), @@ -579,10 +579,10 @@ class _BottomRail extends StatelessWidget { listenable: kernel.panels, builder: (ctx, _) { final tabs = kernel.panels.tabsFor(slot); - if (tabs.isEmpty) return Container(color: tokens.statusBarBackground); + if (tabs.isEmpty) return Container(color: tokens.chromeBackground); final activeId = kernel.panels.activeTabIn(slot) ?? tabs.first.id; return Container( - color: tokens.statusBarBackground, + color: tokens.chromeBackground, child: ClideIconRail( items: [ for (final t in tabs) @@ -636,7 +636,7 @@ class StatusbarHost extends StatelessWidget { final left = items.where((i) => i.priority < 100).toList(); final right = items.where((i) => i.priority >= 100).toList(); return Container( - color: tokens.statusBarBackground, + color: tokens.chromeBackground, padding: const EdgeInsets.symmetric(horizontal: 8), alignment: Alignment.center, child: Row( diff --git a/lib/kernel/src/panels/drag_resize.dart b/lib/kernel/src/panels/drag_resize.dart index 26419ee3..91d1600a 100644 --- a/lib/kernel/src/panels/drag_resize.dart +++ b/lib/kernel/src/panels/drag_resize.dart @@ -49,7 +49,7 @@ class _DragResizeHandleState extends State { child: Container( width: widget.axis == Axis.horizontal ? widget.thickness : null, height: widget.axis == Axis.vertical ? widget.thickness : null, - color: widget.slot == Slots.sidebar ? tokens.sidebarBackground : tokens.panelBackground, + color: tokens.chromeBackground, child: Align( alignment: widget.slot == Slots.sidebar ? Alignment.centerRight : Alignment.centerLeft, child: Container( diff --git a/lib/kernel/src/theme/resolver.dart b/lib/kernel/src/theme/resolver.dart index f89bf13a..156d83c8 100644 --- a/lib/kernel/src/theme/resolver.dart +++ b/lib/kernel/src/theme/resolver.dart @@ -50,6 +50,9 @@ class ThemeResolver { globalBorder: surface[TokenKeys.globalBorder]!, globalFocus: surface[TokenKeys.globalFocus]!, globalTextMuted: surface[TokenKeys.globalTextMuted]!, + chromeBackground: surface[TokenKeys.chromeBackground]!, + chromeForeground: surface[TokenKeys.chromeForeground]!, + chromeBorder: surface[TokenKeys.chromeBorder]!, panelBackground: surface[TokenKeys.panelBackground]!, panelBorder: surface[TokenKeys.panelBorder]!, panelActiveBorder: surface[TokenKeys.panelActiveBorder]!, @@ -196,6 +199,10 @@ const Map> _defaultSurfaceMap = { TokenKeys.globalBorder: ['border', 'semantic.surface'], TokenKeys.globalFocus: ['accent', 'semantic.focus'], TokenKeys.globalTextMuted: ['textDim', 'semantic.text_muted'], + // chrome — frame surfaces (hat bar, sidebar, status bar, spines) + TokenKeys.chromeBackground: ['bgSunken', 'semantic.mainchrome'], + TokenKeys.chromeForeground: ['textDim', 'semantic.text_muted'], + TokenKeys.chromeBorder: ['border', 'semantic.surface'], // panel TokenKeys.panelBackground: ['bgSunken', 'semantic.mainchrome'], TokenKeys.panelBorder: ['border', 'semantic.surface'], diff --git a/lib/kernel/src/theme/tokens.dart b/lib/kernel/src/theme/tokens.dart index cd57a998..346beaa4 100644 --- a/lib/kernel/src/theme/tokens.dart +++ b/lib/kernel/src/theme/tokens.dart @@ -17,6 +17,10 @@ class SurfaceTokens { required this.globalBorder, required this.globalFocus, required this.globalTextMuted, + // chrome (hat bar, sidebar, status bar, spines — frame surfaces) + required this.chromeBackground, + required this.chromeForeground, + required this.chromeBorder, // panel required this.panelBackground, required this.panelBorder, @@ -94,6 +98,10 @@ class SurfaceTokens { final Color globalFocus; final Color globalTextMuted; + final Color chromeBackground; + final Color chromeForeground; + final Color chromeBorder; + final Color panelBackground; final Color panelBorder; final Color panelActiveBorder; @@ -179,6 +187,11 @@ abstract class TokenKeys { static const globalFocus = 'global.focus'; static const globalTextMuted = 'global.textMuted'; + // chrome + static const chromeBackground = 'chrome.background'; + static const chromeForeground = 'chrome.foreground'; + static const chromeBorder = 'chrome.border'; + // panel static const panelBackground = 'panel.background'; static const panelBorder = 'panel.border'; @@ -266,6 +279,9 @@ abstract class TokenKeys { globalBorder, globalFocus, globalTextMuted, + chromeBackground, + chromeForeground, + chromeBorder, panelBackground, panelBorder, panelActiveBorder, diff --git a/lib/widgets/src/clide_spine.dart b/lib/widgets/src/clide_spine.dart index 61a9879a..e0756609 100644 --- a/lib/widgets/src/clide_spine.dart +++ b/lib/widgets/src/clide_spine.dart @@ -45,7 +45,7 @@ class _ClideSpineState extends State { child: Container( width: ClideSpine.width, decoration: BoxDecoration( - color: _hovered ? tokens.sidebarItemHover : tokens.sidebarBackground, + color: _hovered ? tokens.sidebarItemHover : tokens.chromeBackground, border: Border( left: widget.side == SpineSide.right ? borderSide : BorderSide.none, right: widget.side == SpineSide.left ? borderSide : BorderSide.none,