dissolve app/ into repo root (D-056)

Single Flutter package at the repo root. All code, tests, assets,
and platform directories moved from app/ to root. Package renamed
from clide_app to clide — all imports rewritten. Merged pubspec
combines core (ffi) and app (flutter, yaml, xterm) dependencies.
Makefile simplified: no APP_PRESENT conditionals, no cd, no daemon
lifecycle. 317 tests pass.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-23 00:37:20 +02:00
co-authored by Claude Opus 4.6
parent a526c5b9b7
commit 46329700d5
394 changed files with 978 additions and 1090 deletions
+151
View File
@@ -0,0 +1,151 @@
import 'dart:math' as math;
import 'dart:ui';
import 'package:clide/kernel/src/theme/tokens.dart';
import 'package:flutter/foundation.dart';
/// A foreground/background token pair the a11y contrast suite walks.
@immutable
class ContrastPair {
const ContrastPair({
required this.name,
required this.foreground,
required this.background,
this.largeText = false,
});
final String name;
final Color foreground;
final Color background;
/// WCAG AA threshold for "large text" (18pt, or 14pt bold) is 3:1;
/// normal text is 4.5:1. Mark a pair as [largeText] when the rendered
/// typography qualifies.
final bool largeText;
}
/// Compute the WCAG 2.x relative-luminance ratio between two colors.
///
/// Alpha is pre-composited against a neutral grey so semi-transparent
/// tokens don't spuriously pass. Returns a value in `[1, 21]`.
double contrastRatio(Color a, Color b, {Color onto = const Color(0xFF808080)}) {
final la = _relativeLuminance(_composite(a, onto));
final lb = _relativeLuminance(_composite(b, onto));
final brighter = math.max(la, lb);
final darker = math.min(la, lb);
return (brighter + 0.05) / (darker + 0.05);
}
/// Minimum ratio required for this pair per WCAG AA.
double minimumRatio(ContrastPair pair) => pair.largeText ? 3.0 : 4.5;
/// Canonical set of token pairs each bundled theme must honour.
///
/// The a11y contrast test walks this list per-theme.
List<ContrastPair> canonicalPairs(SurfaceTokens s) => [
ContrastPair(
name: 'global.text_on_background',
foreground: s.globalForeground,
background: s.globalBackground,
),
ContrastPair(
name: 'panel.header_foreground_on_panel',
foreground: s.panelHeaderForeground,
background: s.panelHeader,
),
ContrastPair(
name: 'sidebar.foreground_on_sidebar',
foreground: s.sidebarForeground,
background: s.sidebarBackground,
),
ContrastPair(
name: 'statusbar.foreground_on_statusbar',
foreground: s.statusBarForeground,
background: s.statusBarBackground,
),
ContrastPair(
name: 'tab.active_text_on_active_bg',
foreground: s.tabActiveForeground,
background: s.tabActive,
),
ContrastPair(
name: 'tab.inactive_text_on_inactive_bg',
foreground: s.tabInactiveForeground,
background: s.tabInactive,
),
ContrastPair(
name: 'button.text_on_button',
foreground: s.buttonForeground,
background: s.buttonBackground,
),
ContrastPair(
name: 'listItem.selected_text_on_selected_bg',
foreground: s.listItemSelectedForeground,
background: s.listItemSelectedBackground,
),
ContrastPair(
name: 'listItem.text_on_list',
foreground: s.listItemForeground,
background: s.listItemBackground,
),
ContrastPair(
name: 'tooltip.text_on_tooltip',
foreground: s.tooltipForeground,
background: s.tooltipBackground,
),
ContrastPair(
name: 'dropdown.text_on_dropdown',
foreground: s.dropdownForeground,
background: s.dropdownBackground,
),
];
/// Convenience for tests: returns the list of pairs that fail WCAG AA.
List<ContrastFailure> failingPairs(SurfaceTokens tokens) {
final out = <ContrastFailure>[];
for (final p in canonicalPairs(tokens)) {
final ratio = contrastRatio(p.foreground, p.background);
final need = minimumRatio(p);
if (ratio < need) {
out.add(ContrastFailure(pair: p, ratio: ratio, minimum: need));
}
}
return out;
}
@immutable
class ContrastFailure {
const ContrastFailure({
required this.pair,
required this.ratio,
required this.minimum,
});
final ContrastPair pair;
final double ratio;
final double minimum;
@override
String toString() => 'contrast ${pair.name}: ${ratio.toStringAsFixed(2)} < '
'${minimum.toStringAsFixed(1)}';
}
// -- internals ---------------------------------------------------------------
Color _composite(Color src, Color dst) {
final a = src.a;
if (a >= 0.999) return src;
double mix(double s, double d) => s * a + d * (1 - a);
return Color.from(
alpha: 1.0,
red: mix(src.r, dst.r),
green: mix(src.g, dst.g),
blue: mix(src.b, dst.b),
);
}
double _relativeLuminance(Color c) {
double chan(double v) =>
v <= 0.03928 ? v / 12.92 : math.pow((v + 0.055) / 1.055, 2.4).toDouble();
return 0.2126 * chan(c.r) + 0.7152 * chan(c.g) + 0.0722 * chan(c.b);
}
+106
View File
@@ -0,0 +1,106 @@
import 'package:clide/kernel/src/theme/loader.dart';
import 'package:clide/kernel/src/theme/resolver.dart';
import 'package:clide/kernel/src/theme/tokens.dart';
import 'package:flutter/widgets.dart';
@immutable
class ClideThemeData {
const ClideThemeData({
required this.name,
required this.displayName,
required this.dark,
required this.surface,
});
final String name;
final String displayName;
final bool dark;
final SurfaceTokens surface;
}
class ThemeController extends ChangeNotifier {
ThemeController({
required List<ThemeDefinition> bundled,
ThemeResolver resolver = const ThemeResolver(),
String? initialName,
}) : _resolver = resolver,
_defs = Map.fromEntries(bundled.map((d) => MapEntry(d.name, d))) {
final first = initialName != null && _defs.containsKey(initialName)
? initialName
: bundled.first.name;
_currentName = first;
_current = _build(first);
}
final ThemeResolver _resolver;
final Map<String, ThemeDefinition> _defs;
late String _currentName;
late ClideThemeData _current;
ClideThemeData get current => _current;
String get currentName => _currentName;
List<ThemeDefinition> get available => _defs.values.toList(growable: false);
void select(String name) {
if (!_defs.containsKey(name)) {
throw ArgumentError('Unknown theme: $name');
}
if (name == _currentName) return;
_currentName = name;
_current = _build(name);
notifyListeners();
}
void registerTheme(ThemeDefinition def) {
_defs[def.name] = def;
// If the user re-imported the current theme, rebuild so overrides
// take effect without a select().
if (def.name == _currentName) {
_current = _build(def.name);
notifyListeners();
}
}
ClideThemeData _build(String name) {
final def = _defs[name]!;
final tokens = _resolver.resolve(
palette: def.palette,
semanticOverride: def.semanticOverride,
surfaceOverride: def.surfaceOverride,
extensionOverride: def.extensionOverride,
);
return ClideThemeData(
name: def.name,
displayName: def.displayName,
dark: def.dark,
surface: tokens,
);
}
}
class ClideTheme extends InheritedNotifier<ThemeController> {
const ClideTheme({
super.key,
required ThemeController controller,
required super.child,
}) : super(notifier: controller);
static ClideThemeData of(BuildContext context) {
final w = context.dependOnInheritedWidgetOfExactType<ClideTheme>();
if (w == null) {
throw FlutterError(
'ClideTheme.of() called with a context that is not a descendant of a ClideTheme.');
}
return w.notifier!.current;
}
static ThemeController controllerOf(BuildContext context) {
final w = context.dependOnInheritedWidgetOfExactType<ClideTheme>();
if (w == null) {
throw FlutterError(
'ClideTheme.controllerOf() called with a context that is not a descendant of a ClideTheme.');
}
return w.notifier!;
}
}
+131
View File
@@ -0,0 +1,131 @@
import 'dart:io';
import 'package:clide/kernel/src/theme/palette.dart';
import 'package:clide/kernel/src/theme/semantic.dart';
import 'package:flutter/services.dart';
import 'package:yaml/yaml.dart';
class ThemeDefinition {
const ThemeDefinition({
required this.name,
required this.displayName,
required this.dark,
required this.palette,
this.semanticOverride,
this.surfaceOverride,
this.extensionOverride,
});
final String name;
final String displayName;
final bool dark;
final Palette palette;
final SemanticRoles? semanticOverride;
final Map<String, String>? surfaceOverride;
final Map<String, String>? extensionOverride;
}
class ThemeLoader {
const ThemeLoader();
ThemeDefinition fromYamlString(String text, {String? fallbackName}) {
final doc = loadYaml(text);
if (doc is! Map) {
throw FormatException('Theme root is not a map');
}
final name = (doc['name'] as String?) ?? fallbackName;
if (name == null || name.isEmpty) {
throw const FormatException('Theme missing `name`');
}
final displayName = (doc['display_name'] as String?) ?? name;
final dark = (doc['dark'] as bool?) ?? true;
final paletteYaml = doc['palette'];
if (paletteYaml is! Map) {
throw const FormatException('Theme missing `palette`');
}
final palette = _parsePalette(paletteYaml);
// Syntax colours inject into the surface override layer so
// TokenKeys.syntax* resolve directly from the theme YAML.
final syntaxYaml = doc['syntax'];
final syntaxSurface = <String, String>{};
if (syntaxYaml is Map) {
const syntaxMap = {
'keyword': 'syntax.keyword',
'type': 'syntax.type',
'string': 'syntax.string',
'number': 'syntax.number',
'comment': 'syntax.comment',
'method': 'syntax.method',
'punct': 'syntax.punct',
};
syntaxYaml.forEach((k, v) {
final key = syntaxMap['$k'];
if (key != null && v is String) syntaxSurface[key] = v;
});
}
final semantic = doc['semantic'];
final surface = doc['surface'];
final extension = doc['extension'];
final mergedSurface = <String, String>{
...syntaxSurface,
if (surface is Map) ..._parseRefMap(surface),
};
return ThemeDefinition(
name: name,
displayName: displayName,
dark: dark,
palette: palette,
semanticOverride:
semantic is Map ? _parseSemantic(semantic, palette) : null,
surfaceOverride: mergedSurface.isNotEmpty ? mergedSurface : null,
extensionOverride: extension is Map ? _parseRefMap(extension) : null,
);
}
Future<ThemeDefinition> fromAsset(
AssetBundle bundle, String assetPath) async {
final txt = await bundle.loadString(assetPath);
final fallback = assetPath.split('/').last.replaceAll('.yaml', '');
return fromYamlString(txt, fallbackName: fallback);
}
Future<ThemeDefinition> fromFile(File f) async {
final txt = await f.readAsString();
final fallback = f.uri.pathSegments.last.replaceAll('.yaml', '');
return fromYamlString(txt, fallbackName: fallback);
}
}
Palette _parsePalette(Map src) {
final colors = <String, Color>{};
src.forEach((k, v) {
if (v is! String) return;
final c = Palette.parseHex(v);
if (c != null) colors['$k'] = c;
});
return Palette(colors);
}
SemanticRoles _parseSemantic(Map src, Palette palette) {
final roles = <String, Color>{};
src.forEach((k, v) {
if (v is! String) return;
final resolved =
v.startsWith('#') ? Palette.parseHex(v) : palette.lookup(v);
if (resolved != null) roles['$k'] = resolved;
});
return SemanticRoles(roles);
}
Map<String, String> _parseRefMap(Map src) {
final out = <String, String>{};
src.forEach((k, v) {
if (v is String) out['$k'] = v;
});
return out;
}
+22
View File
@@ -0,0 +1,22 @@
import 'dart:ui';
import 'package:flutter/foundation.dart';
@immutable
class Palette {
const Palette(this._colors);
final Map<String, Color> _colors;
Color? lookup(String name) => _colors[name];
Iterable<String> get names => _colors.keys;
static Color? parseHex(String s) {
var v = s.trim();
if (v.startsWith('#')) v = v.substring(1);
if (v.length == 6) v = 'FF$v';
if (v.length != 8) return null;
final n = int.tryParse(v, radix: 16);
if (n == null) return null;
return Color(n);
}
}
+266
View File
@@ -0,0 +1,266 @@
import 'dart:ui';
import 'package:clide/kernel/src/theme/palette.dart';
import 'package:clide/kernel/src/theme/semantic.dart';
import 'package:clide/kernel/src/theme/tokens.dart';
/// Three-tier theme resolution.
///
/// palette (raw colors)
/// ↓ (ref-chain; defaults inherited)
/// semantic (role → palette)
/// ↓ (ref-chain; defaults inherited)
/// surface (token → semantic|palette|literal)
///
/// References take the form:
/// `semantic.<role>` — look up in [semantic]
/// `#rrggbb` / `#aarrggbb` — literal hex
/// bare name — palette lookup
class ThemeResolver {
const ThemeResolver();
SurfaceTokens resolve({
required Palette palette,
SemanticRoles? semanticOverride,
Map<String, String>? surfaceOverride,
Map<String, String>? extensionOverride,
}) {
final semantic = _buildSemantic(palette, semanticOverride);
final surface = <String, Color>{};
for (final key in TokenKeys.all) {
surface[key] = _resolveSurface(
key: key,
palette: palette,
semantic: semantic,
surfaceOverride: surfaceOverride,
);
}
final extTokens = <String, Color>{};
if (extensionOverride != null) {
for (final entry in extensionOverride.entries) {
final resolved = _resolveRef(entry.value, palette, semantic);
if (resolved != null) extTokens[entry.key] = resolved;
}
}
return SurfaceTokens(
globalForeground: surface[TokenKeys.globalForeground]!,
globalBackground: surface[TokenKeys.globalBackground]!,
globalBorder: surface[TokenKeys.globalBorder]!,
globalFocus: surface[TokenKeys.globalFocus]!,
globalTextMuted: surface[TokenKeys.globalTextMuted]!,
panelBackground: surface[TokenKeys.panelBackground]!,
panelBorder: surface[TokenKeys.panelBorder]!,
panelActiveBorder: surface[TokenKeys.panelActiveBorder]!,
panelHeader: surface[TokenKeys.panelHeader]!,
panelHeaderForeground: surface[TokenKeys.panelHeaderForeground]!,
sidebarBackground: surface[TokenKeys.sidebarBackground]!,
sidebarForeground: surface[TokenKeys.sidebarForeground]!,
sidebarItemHover: surface[TokenKeys.sidebarItemHover]!,
sidebarItemSelected: surface[TokenKeys.sidebarItemSelected]!,
sidebarSectionHeader: surface[TokenKeys.sidebarSectionHeader]!,
statusBarBackground: surface[TokenKeys.statusBarBackground]!,
statusBarForeground: surface[TokenKeys.statusBarForeground]!,
statusBarItemActiveBackground:
surface[TokenKeys.statusBarItemActiveBackground]!,
statusBarItemHoverBackground:
surface[TokenKeys.statusBarItemHoverBackground]!,
tabBarBackground: surface[TokenKeys.tabBarBackground]!,
tabActive: surface[TokenKeys.tabActive]!,
tabInactive: surface[TokenKeys.tabInactive]!,
tabActiveForeground: surface[TokenKeys.tabActiveForeground]!,
tabInactiveForeground: surface[TokenKeys.tabInactiveForeground]!,
tabActiveBorder: surface[TokenKeys.tabActiveBorder]!,
tabCloseHover: surface[TokenKeys.tabCloseHover]!,
buttonBackground: surface[TokenKeys.buttonBackground]!,
buttonForeground: surface[TokenKeys.buttonForeground]!,
buttonHoverBackground: surface[TokenKeys.buttonHoverBackground]!,
buttonActiveBackground: surface[TokenKeys.buttonActiveBackground]!,
buttonBorder: surface[TokenKeys.buttonBorder]!,
listItemBackground: surface[TokenKeys.listItemBackground]!,
listItemForeground: surface[TokenKeys.listItemForeground]!,
listItemHoverBackground: surface[TokenKeys.listItemHoverBackground]!,
listItemSelectedBackground:
surface[TokenKeys.listItemSelectedBackground]!,
listItemSelectedForeground:
surface[TokenKeys.listItemSelectedForeground]!,
scrollbarSlider: surface[TokenKeys.scrollbarSlider]!,
scrollbarSliderHover: surface[TokenKeys.scrollbarSliderHover]!,
scrollbarTrack: surface[TokenKeys.scrollbarTrack]!,
tooltipBackground: surface[TokenKeys.tooltipBackground]!,
tooltipForeground: surface[TokenKeys.tooltipForeground]!,
tooltipBorder: surface[TokenKeys.tooltipBorder]!,
dropdownBackground: surface[TokenKeys.dropdownBackground]!,
dropdownForeground: surface[TokenKeys.dropdownForeground]!,
dropdownBorder: surface[TokenKeys.dropdownBorder]!,
modalOverlayBackground: surface[TokenKeys.modalOverlayBackground]!,
modalSurfaceBackground: surface[TokenKeys.modalSurfaceBackground]!,
modalSurfaceBorder: surface[TokenKeys.modalSurfaceBorder]!,
dividerColor: surface[TokenKeys.dividerColor]!,
statusSuccess: surface[TokenKeys.statusSuccess]!,
statusWarning: surface[TokenKeys.statusWarning]!,
statusError: surface[TokenKeys.statusError]!,
statusInfo: surface[TokenKeys.statusInfo]!,
syntaxKeyword: surface[TokenKeys.syntaxKeyword]!,
syntaxType: surface[TokenKeys.syntaxType]!,
syntaxString: surface[TokenKeys.syntaxString]!,
syntaxNumber: surface[TokenKeys.syntaxNumber]!,
syntaxComment: surface[TokenKeys.syntaxComment]!,
syntaxMethod: surface[TokenKeys.syntaxMethod]!,
syntaxPunct: surface[TokenKeys.syntaxPunct]!,
extensionTokens: extTokens,
);
}
SemanticRoles _buildSemantic(Palette palette, SemanticRoles? override) {
final roles = <String, Color>{};
for (final role in SemanticKeys.all) {
final fromOverride = override?.lookup(role);
if (fromOverride != null) {
roles[role] = fromOverride;
continue;
}
for (final candidate in _defaultSemanticFallbacks[role] ?? [role]) {
final fromPalette = palette.lookup(candidate);
if (fromPalette != null) {
roles[role] = fromPalette;
break;
}
}
// If still unresolved, fall back to foreground/background so the
// theme never has a null surface color. Themes that omit these
// will land readable if uninspired.
roles.putIfAbsent(role, () {
return palette.lookup('foreground') ??
palette.lookup('background') ??
const Color(0xFFFFFFFF);
});
}
return SemanticRoles(roles);
}
Color _resolveSurface({
required String key,
required Palette palette,
required SemanticRoles semantic,
Map<String, String>? surfaceOverride,
}) {
final override = surfaceOverride?[key];
if (override != null) {
final resolved = _resolveRef(override, palette, semantic);
if (resolved != null) return resolved;
}
final candidates = _defaultSurfaceMap[key];
if (candidates != null) {
for (final ref in candidates) {
final resolved = _resolveRef(ref, palette, semantic);
if (resolved != null) return resolved;
}
}
return semantic.lookup(SemanticKeys.text) ?? const Color(0xFFFFFFFF);
}
Color? _resolveRef(String ref, Palette palette, SemanticRoles semantic) {
if (ref.startsWith('#')) return Palette.parseHex(ref);
if (ref.startsWith('semantic.')) {
return semantic.lookup(ref.substring('semantic.'.length));
}
return palette.lookup(ref);
}
}
/// Default palette names a semantic role will try, in order, when the
/// theme doesn't override the role explicitly.
const Map<String, List<String>> _defaultSemanticFallbacks = {
SemanticKeys.mainchrome: ['bgSunken', 'panel', 'surface', 'background'],
SemanticKeys.calltoaction: ['accent', 'primary'],
SemanticKeys.focus: ['accent', 'primary'],
SemanticKeys.background: ['bg', 'background'],
SemanticKeys.surface: ['surface', 'panel'],
SemanticKeys.text: ['textHi', 'foreground'],
SemanticKeys.textMuted: ['textDim', 'muted', 'secondary', 'foreground'],
SemanticKeys.success: ['ok', 'success'],
SemanticKeys.warning: ['warn', 'warning'],
SemanticKeys.error: ['err', 'error'],
SemanticKeys.info: ['info', 'primary'],
};
/// Default surface map. Every entry resolves through the semantic layer
/// where it makes sense; raw palette refs are used only where the
/// semantic layer doesn't have a role that fits.
const Map<String, List<String>> _defaultSurfaceMap = {
// global — try design keys first, then legacy semantic
TokenKeys.globalForeground: ['textHi', 'semantic.text'],
TokenKeys.globalBackground: ['bg', 'semantic.background'],
TokenKeys.globalBorder: ['border', 'semantic.surface'],
TokenKeys.globalFocus: ['accent', 'semantic.focus'],
TokenKeys.globalTextMuted: ['textDim', 'semantic.text_muted'],
// panel
TokenKeys.panelBackground: ['bgSunken', 'semantic.mainchrome'],
TokenKeys.panelBorder: ['border', 'semantic.surface'],
TokenKeys.panelActiveBorder: ['borderHi', 'semantic.focus'],
TokenKeys.panelHeader: ['surface', 'semantic.mainchrome'],
TokenKeys.panelHeaderForeground: ['text', 'semantic.text'],
// sidebar
TokenKeys.sidebarBackground: ['bgSunken', 'semantic.mainchrome'],
TokenKeys.sidebarForeground: ['text', 'semantic.text'],
TokenKeys.sidebarItemHover: ['surface', 'semantic.surface'],
TokenKeys.sidebarItemSelected: ['surfaceHi', 'semantic.focus'],
TokenKeys.sidebarSectionHeader: ['textMute', 'semantic.text_muted'],
// statusbar
TokenKeys.statusBarBackground: ['bgSunken', 'semantic.mainchrome'],
TokenKeys.statusBarForeground: ['text', 'semantic.text'],
TokenKeys.statusBarItemActiveBackground: ['accent', 'semantic.focus'],
TokenKeys.statusBarItemHoverBackground: ['surface', 'semantic.surface'],
// tabs
TokenKeys.tabBarBackground: ['bgSunken', 'semantic.mainchrome'],
TokenKeys.tabActive: ['bg', 'semantic.background'],
TokenKeys.tabInactive: ['bgSunken', 'semantic.mainchrome'],
TokenKeys.tabActiveForeground: ['textHi', 'semantic.text'],
TokenKeys.tabInactiveForeground: ['textDim', 'semantic.text_muted'],
TokenKeys.tabActiveBorder: ['accent', 'semantic.focus'],
TokenKeys.tabCloseHover: ['err', 'semantic.error'],
// buttons
TokenKeys.buttonBackground: ['accent', 'semantic.calltoaction'],
TokenKeys.buttonForeground: ['onAccent', 'semantic.background'],
TokenKeys.buttonHoverBackground: ['accentPress', 'semantic.focus'],
TokenKeys.buttonActiveBackground: ['accentPress', 'semantic.focus'],
TokenKeys.buttonBorder: ['border', 'semantic.surface'],
// list items
TokenKeys.listItemBackground: ['bg', 'semantic.background'],
TokenKeys.listItemForeground: ['text', 'semantic.text'],
TokenKeys.listItemHoverBackground: ['surface', 'semantic.surface'],
TokenKeys.listItemSelectedBackground: ['surfaceHi', 'semantic.focus'],
TokenKeys.listItemSelectedForeground: ['textHi', 'semantic.text'],
// scrollbar
TokenKeys.scrollbarSlider: ['border', 'semantic.surface'],
TokenKeys.scrollbarSliderHover: ['borderHi', 'semantic.text_muted'],
TokenKeys.scrollbarTrack: ['bgSunken', 'semantic.mainchrome'],
// tooltip
TokenKeys.tooltipBackground: ['surface', 'semantic.surface'],
TokenKeys.tooltipForeground: ['textHi', 'semantic.text'],
TokenKeys.tooltipBorder: ['borderHi', 'semantic.mainchrome'],
// dropdown
TokenKeys.dropdownBackground: ['surface', 'semantic.surface'],
TokenKeys.dropdownForeground: ['text', 'semantic.text'],
TokenKeys.dropdownBorder: ['border', 'semantic.mainchrome'],
// modal
TokenKeys.modalOverlayBackground: ['#C0000000'],
TokenKeys.modalSurfaceBackground: ['surface', 'semantic.mainchrome'],
TokenKeys.modalSurfaceBorder: ['accent', 'semantic.focus'],
// divider
TokenKeys.dividerColor: ['border', 'semantic.surface'],
// status
TokenKeys.statusSuccess: ['ok', 'semantic.success'],
TokenKeys.statusWarning: ['warn', 'semantic.warning'],
TokenKeys.statusError: ['err', 'semantic.error'],
TokenKeys.statusInfo: ['info', 'semantic.info'],
TokenKeys.syntaxKeyword: ['semantic.calltoaction'],
TokenKeys.syntaxType: ['semantic.info'],
TokenKeys.syntaxString: ['semantic.success'],
TokenKeys.syntaxNumber: ['semantic.warning'],
TokenKeys.syntaxComment: ['semantic.text_muted'],
TokenKeys.syntaxMethod: ['semantic.focus'],
TokenKeys.syntaxPunct: ['semantic.text_muted'],
};
+40
View File
@@ -0,0 +1,40 @@
import 'dart:ui';
import 'package:flutter/foundation.dart';
@immutable
class SemanticRoles {
const SemanticRoles(this._roles);
final Map<String, Color> _roles;
Color? lookup(String role) => _roles[role];
Iterable<String> get roles => _roles.keys;
}
abstract class SemanticKeys {
static const mainchrome = 'mainchrome';
static const calltoaction = 'calltoaction';
static const focus = 'focus';
static const background = 'background';
static const surface = 'surface';
static const text = 'text';
static const textMuted = 'text_muted';
static const success = 'success';
static const warning = 'warning';
static const error = 'error';
static const info = 'info';
static const all = <String>[
mainchrome,
calltoaction,
focus,
background,
surface,
text,
textMuted,
success,
warning,
error,
info,
];
}
+36
View File
@@ -0,0 +1,36 @@
# clide — cool near-black + periwinkle (default)
# Source: docs/claude-design/tokens/clide.yaml
name: clide
display_name: Clide
dark: true
palette:
background: "#20202C"
panel: "#1A1A24"
surface: "#242838"
muted: "#78809C"
foreground: "#E6E8F2"
secondary: "#B1BBE3"
primary: "#78A0F8"
accent: "#6C90DC"
success: "#7DD3A8"
warning: "#E6C370"
error: "#E87D7D"
info: "#78A0F8"
# extended palette keys for the design's richer vocabulary
surfaceHi: "#2C3046"
border: "#343850"
borderHi: "#3C445C"
textDim: "#8890AC"
textMute: "#545C84"
accentSoft: "#2178A0F8"
syntax:
keyword: "#C792EA"
type: "#78A0F8"
string: "#A8D99B"
number: "#E6C370"
comment: "#545C84"
method: "#82B1FF"
punct: "#78809C"
+36
View File
@@ -0,0 +1,36 @@
# midnight — VS Code-adjacent muted dark
# Source: docs/claude-design/tokens/midnight.yaml
name: midnight
display_name: Midnight
dark: true
palette:
background: "#1E1E1E"
panel: "#181818"
surface: "#252526"
muted: "#858585"
foreground: "#D4D4D4"
secondary: "#BBBBBB"
primary: "#569CD6"
accent: "#4785BD"
success: "#89D185"
warning: "#D7BA7D"
error: "#F48771"
info: "#569CD6"
onAccent: "#0B1220"
surfaceHi: "#2D2D2E"
border: "#333333"
borderHi: "#3F3F3F"
textDim: "#858585"
textMute: "#6A6A6A"
accentSoft: "#21569CD6"
syntax:
keyword: "#C586C0"
type: "#4EC9B0"
string: "#CE9178"
number: "#B5CEA8"
comment: "#6A9955"
method: "#DCDCAA"
punct: "#858585"
+35
View File
@@ -0,0 +1,35 @@
# paper — drafting sheet, red-pencil accent, light
# Source: docs/claude-design/tokens/paper.yaml
name: paper
display_name: Paper
dark: false
palette:
background: "#F4F1EA"
panel: "#ECE7DB"
surface: "#FBF8F1"
muted: "#8A8A82"
foreground: "#1A1A1A"
secondary: "#4A4A4A"
primary: "#C14B2A"
accent: "#A03D20"
success: "#2D8A52"
warning: "#B88A2A"
error: "#B03A2A"
info: "#2A6FC1"
surfaceHi: "#ECE7DB"
border: "#1A1A1A"
borderHi: "#4A4A4A"
textDim: "#5E5E56"
textMute: "#A8A89E"
accentSoft: "#21C14B2A"
syntax:
keyword: "#7B3F8C"
type: "#2A6FC1"
string: "#2D8A52"
number: "#B88A2A"
comment: "#8A8A82"
method: "#1E5D9E"
punct: "#4A4A4A"
@@ -0,0 +1,29 @@
# Summer Night — ported from legacy clide v1.2.0.
# Palette-only; the three-tier resolver fills semantic + surface from
# defaults. Override sections land here as the token surface grows.
name: summer-night
display_name: Summer Night
dark: true
palette:
# Accents (legacy names: primary/secondary/accent)
primary: "#00a3d2" # cyan
secondary: "#00a9b9" # teal
accent: "#fa5f8b" # pink
# Backgrounds
background: "#21262f"
surface: "#393e48"
panel: "#292e38"
# Text. `muted` is WCAG-AA-calibrated against `panel` — don't darken
# without re-running the a11y/contrast suite.
foreground: "#e2e8f5"
muted: "#a6adbb"
# Status
success: "#00ab9a"
warning: "#d08447"
error: "#f06c6f"
info: "#00a3d2"
+35
View File
@@ -0,0 +1,35 @@
# terminal — near-black + amber, tmux feel
# Source: docs/claude-design/tokens/terminal.yaml
name: terminal
display_name: Terminal
dark: true
palette:
background: "#0A0A0A"
panel: "#000000"
surface: "#111111"
muted: "#7A7A7A"
foreground: "#E6E6E6"
secondary: "#BDBDBD"
primary: "#E0B050"
accent: "#C29438"
success: "#8FDC9B"
warning: "#E0B050"
error: "#E05050"
info: "#A3C4FF"
surfaceHi: "#181818"
border: "#242424"
borderHi: "#2E2E2E"
textDim: "#7A7A7A"
textMute: "#4A4A4A"
accentSoft: "#21E0B050"
syntax:
keyword: "#E05050"
type: "#E0B050"
string: "#8FDC9B"
number: "#C792EA"
comment: "#4A4A4A"
method: "#A3C4FF"
punct: "#7A7A7A"
+325
View File
@@ -0,0 +1,325 @@
import 'dart:ui';
import 'package:flutter/foundation.dart';
/// Resolved surface tokens — the only thing widgets consume.
///
/// The token surface grows as features need more of it. Every token
/// declared here must have a default resolution in
/// [DefaultSurfaceMap] so legacy palette-only themes produce a complete
/// SurfaceTokens without declaring the full surface.
@immutable
class SurfaceTokens {
const SurfaceTokens({
// global
required this.globalForeground,
required this.globalBackground,
required this.globalBorder,
required this.globalFocus,
required this.globalTextMuted,
// panel
required this.panelBackground,
required this.panelBorder,
required this.panelActiveBorder,
required this.panelHeader,
required this.panelHeaderForeground,
// sidebar
required this.sidebarBackground,
required this.sidebarForeground,
required this.sidebarItemHover,
required this.sidebarItemSelected,
required this.sidebarSectionHeader,
// statusbar
required this.statusBarBackground,
required this.statusBarForeground,
required this.statusBarItemActiveBackground,
required this.statusBarItemHoverBackground,
// tabs
required this.tabBarBackground,
required this.tabActive,
required this.tabInactive,
required this.tabActiveForeground,
required this.tabInactiveForeground,
required this.tabActiveBorder,
required this.tabCloseHover,
// buttons
required this.buttonBackground,
required this.buttonForeground,
required this.buttonHoverBackground,
required this.buttonActiveBackground,
required this.buttonBorder,
// list items
required this.listItemBackground,
required this.listItemForeground,
required this.listItemHoverBackground,
required this.listItemSelectedBackground,
required this.listItemSelectedForeground,
// scrollbar
required this.scrollbarSlider,
required this.scrollbarSliderHover,
required this.scrollbarTrack,
// tooltip
required this.tooltipBackground,
required this.tooltipForeground,
required this.tooltipBorder,
// dropdown
required this.dropdownBackground,
required this.dropdownForeground,
required this.dropdownBorder,
// modal
required this.modalOverlayBackground,
required this.modalSurfaceBackground,
required this.modalSurfaceBorder,
// divider
required this.dividerColor,
// status
required this.statusSuccess,
required this.statusWarning,
required this.statusError,
required this.statusInfo,
// syntax
required this.syntaxKeyword,
required this.syntaxType,
required this.syntaxString,
required this.syntaxNumber,
required this.syntaxComment,
required this.syntaxMethod,
required this.syntaxPunct,
required this.extensionTokens,
});
final Color globalForeground;
final Color globalBackground;
final Color globalBorder;
final Color globalFocus;
final Color globalTextMuted;
final Color panelBackground;
final Color panelBorder;
final Color panelActiveBorder;
final Color panelHeader;
final Color panelHeaderForeground;
final Color sidebarBackground;
final Color sidebarForeground;
final Color sidebarItemHover;
final Color sidebarItemSelected;
final Color sidebarSectionHeader;
final Color statusBarBackground;
final Color statusBarForeground;
final Color statusBarItemActiveBackground;
final Color statusBarItemHoverBackground;
final Color tabBarBackground;
final Color tabActive;
final Color tabInactive;
final Color tabActiveForeground;
final Color tabInactiveForeground;
final Color tabActiveBorder;
final Color tabCloseHover;
final Color buttonBackground;
final Color buttonForeground;
final Color buttonHoverBackground;
final Color buttonActiveBackground;
final Color buttonBorder;
final Color listItemBackground;
final Color listItemForeground;
final Color listItemHoverBackground;
final Color listItemSelectedBackground;
final Color listItemSelectedForeground;
final Color scrollbarSlider;
final Color scrollbarSliderHover;
final Color scrollbarTrack;
final Color tooltipBackground;
final Color tooltipForeground;
final Color tooltipBorder;
final Color dropdownBackground;
final Color dropdownForeground;
final Color dropdownBorder;
final Color modalOverlayBackground;
final Color modalSurfaceBackground;
final Color modalSurfaceBorder;
final Color dividerColor;
final Color statusSuccess;
final Color statusWarning;
final Color statusError;
final Color statusInfo;
final Color syntaxKeyword;
final Color syntaxType;
final Color syntaxString;
final Color syntaxNumber;
final Color syntaxComment;
final Color syntaxMethod;
final Color syntaxPunct;
/// Extension-declared tokens keyed by their dotted path
/// (e.g. `ext.sqlite.table.background`).
final Map<String, Color> extensionTokens;
}
/// Canonical surface-token keys as they appear in YAML.
///
/// Keeping them in one place lets the loader, the resolver, and the
/// default map reference the same strings without typos.
abstract class TokenKeys {
// global
static const globalForeground = 'global.foreground';
static const globalBackground = 'global.background';
static const globalBorder = 'global.border';
static const globalFocus = 'global.focus';
static const globalTextMuted = 'global.textMuted';
// panel
static const panelBackground = 'panel.background';
static const panelBorder = 'panel.border';
static const panelActiveBorder = 'panel.activeBorder';
static const panelHeader = 'panel.header';
static const panelHeaderForeground = 'panel.headerForeground';
// sidebar
static const sidebarBackground = 'sidebar.background';
static const sidebarForeground = 'sidebar.foreground';
static const sidebarItemHover = 'sidebar.itemHover';
static const sidebarItemSelected = 'sidebar.itemSelected';
static const sidebarSectionHeader = 'sidebar.sectionHeader';
// statusbar
static const statusBarBackground = 'statusBar.background';
static const statusBarForeground = 'statusBar.foreground';
static const statusBarItemActiveBackground = 'statusBar.itemActiveBackground';
static const statusBarItemHoverBackground = 'statusBar.itemHoverBackground';
// tabs
static const tabBarBackground = 'tabBar.background';
static const tabActive = 'tabBar.tabActive';
static const tabInactive = 'tabBar.tabInactive';
static const tabActiveForeground = 'tabBar.tabActiveForeground';
static const tabInactiveForeground = 'tabBar.tabInactiveForeground';
static const tabActiveBorder = 'tabBar.tabActiveBorder';
static const tabCloseHover = 'tabBar.tabCloseHover';
// buttons
static const buttonBackground = 'button.background';
static const buttonForeground = 'button.foreground';
static const buttonHoverBackground = 'button.hoverBackground';
static const buttonActiveBackground = 'button.activeBackground';
static const buttonBorder = 'button.border';
// list items
static const listItemBackground = 'listItem.background';
static const listItemForeground = 'listItem.foreground';
static const listItemHoverBackground = 'listItem.hoverBackground';
static const listItemSelectedBackground = 'listItem.selectedBackground';
static const listItemSelectedForeground = 'listItem.selectedForeground';
// scrollbar
static const scrollbarSlider = 'scrollbar.slider';
static const scrollbarSliderHover = 'scrollbar.sliderHover';
static const scrollbarTrack = 'scrollbar.track';
// tooltip
static const tooltipBackground = 'tooltip.background';
static const tooltipForeground = 'tooltip.foreground';
static const tooltipBorder = 'tooltip.border';
// dropdown
static const dropdownBackground = 'dropdown.background';
static const dropdownForeground = 'dropdown.foreground';
static const dropdownBorder = 'dropdown.border';
// modal
static const modalOverlayBackground = 'modal.overlayBackground';
static const modalSurfaceBackground = 'modal.surfaceBackground';
static const modalSurfaceBorder = 'modal.surfaceBorder';
// divider
static const dividerColor = 'divider.color';
// status
static const statusSuccess = 'status.success';
static const statusWarning = 'status.warning';
static const statusError = 'status.error';
static const statusInfo = 'status.info';
// syntax
static const syntaxKeyword = 'syntax.keyword';
static const syntaxType = 'syntax.type';
static const syntaxString = 'syntax.string';
static const syntaxNumber = 'syntax.number';
static const syntaxComment = 'syntax.comment';
static const syntaxMethod = 'syntax.method';
static const syntaxPunct = 'syntax.punct';
static const all = <String>[
globalForeground,
globalBackground,
globalBorder,
globalFocus,
globalTextMuted,
panelBackground,
panelBorder,
panelActiveBorder,
panelHeader,
panelHeaderForeground,
sidebarBackground,
sidebarForeground,
sidebarItemHover,
sidebarItemSelected,
sidebarSectionHeader,
statusBarBackground,
statusBarForeground,
statusBarItemActiveBackground,
statusBarItemHoverBackground,
tabBarBackground,
tabActive,
tabInactive,
tabActiveForeground,
tabInactiveForeground,
tabActiveBorder,
tabCloseHover,
buttonBackground,
buttonForeground,
buttonHoverBackground,
buttonActiveBackground,
buttonBorder,
listItemBackground,
listItemForeground,
listItemHoverBackground,
listItemSelectedBackground,
listItemSelectedForeground,
scrollbarSlider,
scrollbarSliderHover,
scrollbarTrack,
tooltipBackground,
tooltipForeground,
tooltipBorder,
dropdownBackground,
dropdownForeground,
dropdownBorder,
modalOverlayBackground,
modalSurfaceBackground,
modalSurfaceBorder,
dividerColor,
statusSuccess,
statusWarning,
statusError,
statusInfo,
syntaxKeyword,
syntaxType,
syntaxString,
syntaxNumber,
syntaxComment,
syntaxMethod,
syntaxPunct,
];
}