add chrome token set for frame surfaces

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) <noreply@anthropic.com>
This commit is contained in:
2026-04-23 08:52:44 +02:00
co-authored by Claude Opus 4.6
parent 1494d6a414
commit 86ad659121
6 changed files with 43 additions and 19 deletions
+9 -8
View File
@@ -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. 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 background → chromeBackground
border → dividerColor (1px) text → chromeForeground
text → globalTextMuted border → chromeBorder (1px)
active text → globalForeground active text → globalForeground
``` ```
### Side panels (sidebar, context panel) ### Side panels (sidebar, context panel)
``` ```
background → sidebarBackground (left) / panelBackground (right) background → chromeBackground (both sides — they're chrome frame)
text → sidebarForeground text → sidebarForeground
hover → sidebarItemHover hover → sidebarItemHover
selected → sidebarItemSelected selected → sidebarItemSelected
@@ -149,9 +149,10 @@ The palette layer has these depth primitives:
- `surface` (`#242838`) — elevated: pane headers, active tabs - `surface` (`#242838`) — elevated: pane headers, active tabs
- `surfaceHi` (`#2C3046`) — interactive: hover states, selections - `surfaceHi` (`#2C3046`) — interactive: hover states, selections
**Pending:** `chromeBackground`/`chromeForeground`/`chromeBorder` tokens Chrome tokens (`chromeBackground`/`chromeForeground`/`chromeBorder`) are
need to be added to `SurfaceTokens` so hat bar, sidebar, and status bar the shared root for all frame surfaces. They resolve to `bgSunken` /
share a named root instead of cross-referencing each other's tokens. `textDim` / `border` in the palette. Themes can override them to diverge
hat from sidebar from status bar if desired.
## Anti-patterns ## Anti-patterns
+9 -9
View File
@@ -177,7 +177,7 @@ class RootLayout extends StatelessWidget {
if (statusVisible) if (statusVisible)
Container( Container(
height: statusHeight, 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( child: Row(
crossAxisAlignment: CrossAxisAlignment.stretch, crossAxisAlignment: CrossAxisAlignment.stretch,
children: [ children: [
@@ -222,8 +222,8 @@ class _HatBar extends StatelessWidget {
child: Container( child: Container(
height: hatHeight, height: hatHeight,
decoration: BoxDecoration( decoration: BoxDecoration(
color: tokens.sidebarBackground, color: tokens.chromeBackground,
border: Border(bottom: BorderSide(color: tokens.dividerColor, width: 1)), border: Border(bottom: BorderSide(color: tokens.chromeBorder, width: 1)),
), ),
padding: const EdgeInsets.symmetric(horizontal: 8), padding: const EdgeInsets.symmetric(horizontal: 8),
child: Row( child: Row(
@@ -238,7 +238,7 @@ class _HatBar extends StatelessWidget {
return ClideText( return ClideText(
name != null ? 'clide > $name' : 'clide', name != null ? 'clide > $name' : 'clide',
fontSize: 12, fontSize: 12,
color: tokens.globalTextMuted, color: tokens.chromeForeground,
fontFamily: clideMonoFamily, fontFamily: clideMonoFamily,
); );
}, },
@@ -343,7 +343,7 @@ class _WinBtnState extends State<_WinBtn> {
width: 36, height: hatHeight, width: 36, height: hatHeight,
color: _hover ? hoverBg : null, color: _hover ? hoverBg : null,
alignment: Alignment.center, 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) { Widget build(BuildContext context) {
final tokens = ClideTheme.of(context).surface; final tokens = ClideTheme.of(context).surface;
return Container( return Container(
color: tokens.sidebarBackground, color: tokens.chromeBackground,
alignment: Alignment.topLeft, alignment: Alignment.topLeft,
padding: const EdgeInsets.fromLTRB(2, 2, 0, 0), padding: const EdgeInsets.fromLTRB(2, 2, 0, 0),
child: active.build(context), child: active.build(context),
@@ -579,10 +579,10 @@ class _BottomRail extends StatelessWidget {
listenable: kernel.panels, listenable: kernel.panels,
builder: (ctx, _) { builder: (ctx, _) {
final tabs = kernel.panels.tabsFor(slot); 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; final activeId = kernel.panels.activeTabIn(slot) ?? tabs.first.id;
return Container( return Container(
color: tokens.statusBarBackground, color: tokens.chromeBackground,
child: ClideIconRail( child: ClideIconRail(
items: [ items: [
for (final t in tabs) for (final t in tabs)
@@ -636,7 +636,7 @@ class StatusbarHost extends StatelessWidget {
final left = items.where((i) => i.priority < 100).toList(); final left = items.where((i) => i.priority < 100).toList();
final right = items.where((i) => i.priority >= 100).toList(); final right = items.where((i) => i.priority >= 100).toList();
return Container( return Container(
color: tokens.statusBarBackground, color: tokens.chromeBackground,
padding: const EdgeInsets.symmetric(horizontal: 8), padding: const EdgeInsets.symmetric(horizontal: 8),
alignment: Alignment.center, alignment: Alignment.center,
child: Row( child: Row(
+1 -1
View File
@@ -49,7 +49,7 @@ class _DragResizeHandleState extends State<DragResizeHandle> {
child: Container( child: Container(
width: widget.axis == Axis.horizontal ? widget.thickness : null, width: widget.axis == Axis.horizontal ? widget.thickness : null,
height: widget.axis == Axis.vertical ? 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( child: Align(
alignment: widget.slot == Slots.sidebar ? Alignment.centerRight : Alignment.centerLeft, alignment: widget.slot == Slots.sidebar ? Alignment.centerRight : Alignment.centerLeft,
child: Container( child: Container(
+7
View File
@@ -50,6 +50,9 @@ class ThemeResolver {
globalBorder: surface[TokenKeys.globalBorder]!, globalBorder: surface[TokenKeys.globalBorder]!,
globalFocus: surface[TokenKeys.globalFocus]!, globalFocus: surface[TokenKeys.globalFocus]!,
globalTextMuted: surface[TokenKeys.globalTextMuted]!, globalTextMuted: surface[TokenKeys.globalTextMuted]!,
chromeBackground: surface[TokenKeys.chromeBackground]!,
chromeForeground: surface[TokenKeys.chromeForeground]!,
chromeBorder: surface[TokenKeys.chromeBorder]!,
panelBackground: surface[TokenKeys.panelBackground]!, panelBackground: surface[TokenKeys.panelBackground]!,
panelBorder: surface[TokenKeys.panelBorder]!, panelBorder: surface[TokenKeys.panelBorder]!,
panelActiveBorder: surface[TokenKeys.panelActiveBorder]!, panelActiveBorder: surface[TokenKeys.panelActiveBorder]!,
@@ -196,6 +199,10 @@ const Map<String, List<String>> _defaultSurfaceMap = {
TokenKeys.globalBorder: ['border', 'semantic.surface'], TokenKeys.globalBorder: ['border', 'semantic.surface'],
TokenKeys.globalFocus: ['accent', 'semantic.focus'], TokenKeys.globalFocus: ['accent', 'semantic.focus'],
TokenKeys.globalTextMuted: ['textDim', 'semantic.text_muted'], 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 // panel
TokenKeys.panelBackground: ['bgSunken', 'semantic.mainchrome'], TokenKeys.panelBackground: ['bgSunken', 'semantic.mainchrome'],
TokenKeys.panelBorder: ['border', 'semantic.surface'], TokenKeys.panelBorder: ['border', 'semantic.surface'],
+16
View File
@@ -17,6 +17,10 @@ class SurfaceTokens {
required this.globalBorder, required this.globalBorder,
required this.globalFocus, required this.globalFocus,
required this.globalTextMuted, required this.globalTextMuted,
// chrome (hat bar, sidebar, status bar, spines — frame surfaces)
required this.chromeBackground,
required this.chromeForeground,
required this.chromeBorder,
// panel // panel
required this.panelBackground, required this.panelBackground,
required this.panelBorder, required this.panelBorder,
@@ -94,6 +98,10 @@ class SurfaceTokens {
final Color globalFocus; final Color globalFocus;
final Color globalTextMuted; final Color globalTextMuted;
final Color chromeBackground;
final Color chromeForeground;
final Color chromeBorder;
final Color panelBackground; final Color panelBackground;
final Color panelBorder; final Color panelBorder;
final Color panelActiveBorder; final Color panelActiveBorder;
@@ -179,6 +187,11 @@ abstract class TokenKeys {
static const globalFocus = 'global.focus'; static const globalFocus = 'global.focus';
static const globalTextMuted = 'global.textMuted'; static const globalTextMuted = 'global.textMuted';
// chrome
static const chromeBackground = 'chrome.background';
static const chromeForeground = 'chrome.foreground';
static const chromeBorder = 'chrome.border';
// panel // panel
static const panelBackground = 'panel.background'; static const panelBackground = 'panel.background';
static const panelBorder = 'panel.border'; static const panelBorder = 'panel.border';
@@ -266,6 +279,9 @@ abstract class TokenKeys {
globalBorder, globalBorder,
globalFocus, globalFocus,
globalTextMuted, globalTextMuted,
chromeBackground,
chromeForeground,
chromeBorder,
panelBackground, panelBackground,
panelBorder, panelBorder,
panelActiveBorder, panelActiveBorder,
+1 -1
View File
@@ -45,7 +45,7 @@ class _ClideSpineState extends State<ClideSpine> {
child: Container( child: Container(
width: ClideSpine.width, width: ClideSpine.width,
decoration: BoxDecoration( decoration: BoxDecoration(
color: _hovered ? tokens.sidebarItemHover : tokens.sidebarBackground, color: _hovered ? tokens.sidebarItemHover : tokens.chromeBackground,
border: Border( border: Border(
left: widget.side == SpineSide.right ? borderSide : BorderSide.none, left: widget.side == SpineSide.right ? borderSide : BorderSide.none,
right: widget.side == SpineSide.left ? borderSide : BorderSide.none, right: widget.side == SpineSide.left ? borderSide : BorderSide.none,