chore: adopt Dart 3.9 toolchain — honest floor + tall-style reformat (T-353)
Raise the declared minimums in pubspec.yaml to what our deps already require: Flutter >=3.35.0 / Dart >=3.9.0 (was 3.19.0 / 3.5.0). alchemist 0.12 needs Flutter 3.32; Dart 3.9 first ships in Flutter 3.35, so 3.35 is the binding floor. Pin the exact build toolchain in .fvmrc (Flutter 3.44.1). Moving to the Dart 3.9 language level switches `dart format` to the new "tall" style and enables two new lints. This commit is the resulting mechanical churn, isolated from any behaviour change: - whole-tree `dart format` reformat (tall style) - `dart fix` for unnecessary_underscores + use_null_aware_elements No runtime behaviour change; `make test` green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -7,12 +7,7 @@ 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,
|
||||
});
|
||||
const ContrastPair({required this.name, required this.foreground, required this.background, this.largeText = false});
|
||||
|
||||
final String name;
|
||||
final Color foreground;
|
||||
@@ -49,62 +44,18 @@ double minimumRatio(ContrastPair pair) => pair.largeText ? 3.0 : 4.5;
|
||||
/// muted text, status chips, syntax tokens, and the focus border lives
|
||||
/// in [extendedPairs], which only `-hc`/`-cb` variants must pass.
|
||||
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,
|
||||
),
|
||||
];
|
||||
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),
|
||||
];
|
||||
|
||||
/// Stricter pair set — only the high-contrast (`-hc`) and colour-blind
|
||||
/// (`-cb`) theme variants must clear it. See D-69. These are the
|
||||
@@ -112,94 +63,37 @@ List<ContrastPair> canonicalPairs(SurfaceTokens s) => [
|
||||
/// text, status chip foregrounds, syntax tokens on the code-block
|
||||
/// surface, and the focus-indicating panel border.
|
||||
List<ContrastPair> extendedPairs(SurfaceTokens s) => [
|
||||
ContrastPair(
|
||||
name: 'global.text_muted_on_background',
|
||||
foreground: s.globalTextMuted,
|
||||
background: s.globalBackground,
|
||||
),
|
||||
ContrastPair(
|
||||
name: 'global.text_muted_on_panel',
|
||||
foreground: s.globalTextMuted,
|
||||
background: s.panelBackground,
|
||||
),
|
||||
ContrastPair(
|
||||
name: 'status.success_on_statusbar',
|
||||
foreground: s.statusSuccess,
|
||||
background: s.statusBarBackground,
|
||||
),
|
||||
ContrastPair(
|
||||
name: 'status.warning_on_statusbar',
|
||||
foreground: s.statusWarning,
|
||||
background: s.statusBarBackground,
|
||||
),
|
||||
ContrastPair(
|
||||
name: 'status.error_on_statusbar',
|
||||
foreground: s.statusError,
|
||||
background: s.statusBarBackground,
|
||||
),
|
||||
ContrastPair(
|
||||
name: 'status.info_on_statusbar',
|
||||
foreground: s.statusInfo,
|
||||
background: s.statusBarBackground,
|
||||
),
|
||||
ContrastPair(
|
||||
name: 'syntax.keyword_on_panel',
|
||||
foreground: s.syntaxKeyword,
|
||||
background: s.panelBackground,
|
||||
),
|
||||
ContrastPair(
|
||||
name: 'syntax.type_on_panel',
|
||||
foreground: s.syntaxType,
|
||||
background: s.panelBackground,
|
||||
),
|
||||
ContrastPair(
|
||||
name: 'syntax.string_on_panel',
|
||||
foreground: s.syntaxString,
|
||||
background: s.panelBackground,
|
||||
),
|
||||
ContrastPair(
|
||||
name: 'syntax.number_on_panel',
|
||||
foreground: s.syntaxNumber,
|
||||
background: s.panelBackground,
|
||||
),
|
||||
ContrastPair(
|
||||
name: 'syntax.comment_on_panel',
|
||||
foreground: s.syntaxComment,
|
||||
background: s.panelBackground,
|
||||
),
|
||||
ContrastPair(
|
||||
name: 'syntax.method_on_panel',
|
||||
foreground: s.syntaxMethod,
|
||||
background: s.panelBackground,
|
||||
),
|
||||
ContrastPair(
|
||||
name: 'syntax.punct_on_panel',
|
||||
foreground: s.syntaxPunct,
|
||||
background: s.panelBackground,
|
||||
),
|
||||
// WCAG 1.4.11 wants 3:1 for non-text UI components like a focus
|
||||
// border against the adjacent surface.
|
||||
ContrastPair(
|
||||
name: 'panel.active_border_on_background',
|
||||
foreground: s.panelActiveBorder,
|
||||
background: s.globalBackground,
|
||||
largeText: true,
|
||||
),
|
||||
// selection.foreground_on_selection is intentionally omitted here.
|
||||
//
|
||||
// The `selectionBackground` token defaults to `globalFocus.withAlpha(0x66)`
|
||||
// — a semi-transparent tint composited onto the real content background at
|
||||
// runtime. The WCAG compositor in contrastRatio() blends onto neutral grey
|
||||
// (0x808080) rather than the actual dark panel background, which
|
||||
// systematically understates the readable contrast for all current bundled
|
||||
// themes. Adding the pair here would require retuning palettes, which D-69
|
||||
// forbids for user-contract themes.
|
||||
//
|
||||
// Enforcement is deferred to a follow-up ticket: -hc/-cb variants will
|
||||
// declare an explicit `surface.selectionBackground` override that is
|
||||
// opaque enough to clear 3:1 against the grey compositor, at which point
|
||||
// the pair can be added to extendedPairs.
|
||||
];
|
||||
ContrastPair(name: 'global.text_muted_on_background', foreground: s.globalTextMuted, background: s.globalBackground),
|
||||
ContrastPair(name: 'global.text_muted_on_panel', foreground: s.globalTextMuted, background: s.panelBackground),
|
||||
ContrastPair(name: 'status.success_on_statusbar', foreground: s.statusSuccess, background: s.statusBarBackground),
|
||||
ContrastPair(name: 'status.warning_on_statusbar', foreground: s.statusWarning, background: s.statusBarBackground),
|
||||
ContrastPair(name: 'status.error_on_statusbar', foreground: s.statusError, background: s.statusBarBackground),
|
||||
ContrastPair(name: 'status.info_on_statusbar', foreground: s.statusInfo, background: s.statusBarBackground),
|
||||
ContrastPair(name: 'syntax.keyword_on_panel', foreground: s.syntaxKeyword, background: s.panelBackground),
|
||||
ContrastPair(name: 'syntax.type_on_panel', foreground: s.syntaxType, background: s.panelBackground),
|
||||
ContrastPair(name: 'syntax.string_on_panel', foreground: s.syntaxString, background: s.panelBackground),
|
||||
ContrastPair(name: 'syntax.number_on_panel', foreground: s.syntaxNumber, background: s.panelBackground),
|
||||
ContrastPair(name: 'syntax.comment_on_panel', foreground: s.syntaxComment, background: s.panelBackground),
|
||||
ContrastPair(name: 'syntax.method_on_panel', foreground: s.syntaxMethod, background: s.panelBackground),
|
||||
ContrastPair(name: 'syntax.punct_on_panel', foreground: s.syntaxPunct, background: s.panelBackground),
|
||||
// WCAG 1.4.11 wants 3:1 for non-text UI components like a focus
|
||||
// border against the adjacent surface.
|
||||
ContrastPair(name: 'panel.active_border_on_background', foreground: s.panelActiveBorder, background: s.globalBackground, largeText: true),
|
||||
// selection.foreground_on_selection is intentionally omitted here.
|
||||
//
|
||||
// The `selectionBackground` token defaults to `globalFocus.withAlpha(0x66)`
|
||||
// — a semi-transparent tint composited onto the real content background at
|
||||
// runtime. The WCAG compositor in contrastRatio() blends onto neutral grey
|
||||
// (0x808080) rather than the actual dark panel background, which
|
||||
// systematically understates the readable contrast for all current bundled
|
||||
// themes. Adding the pair here would require retuning palettes, which D-69
|
||||
// forbids for user-contract themes.
|
||||
//
|
||||
// Enforcement is deferred to a follow-up ticket: -hc/-cb variants will
|
||||
// declare an explicit `surface.selectionBackground` override that is
|
||||
// opaque enough to clear 3:1 against the grey compositor, at which point
|
||||
// the pair can be added to extendedPairs.
|
||||
];
|
||||
|
||||
/// Convenience for tests: returns the list of [canonicalPairs] that
|
||||
/// fail WCAG AA.
|
||||
@@ -223,18 +117,15 @@ List<ContrastFailure> _failures(List<ContrastPair> pairs) {
|
||||
|
||||
@immutable
|
||||
class ContrastFailure {
|
||||
const ContrastFailure({
|
||||
required this.pair,
|
||||
required this.ratio,
|
||||
required this.minimum,
|
||||
});
|
||||
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)} < '
|
||||
String toString() =>
|
||||
'contrast ${pair.name}: ${ratio.toStringAsFixed(2)} < '
|
||||
'${minimum.toStringAsFixed(1)}';
|
||||
}
|
||||
|
||||
@@ -244,12 +135,7 @@ 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),
|
||||
);
|
||||
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) {
|
||||
|
||||
@@ -5,12 +5,7 @@ import 'package:flutter/widgets.dart';
|
||||
|
||||
@immutable
|
||||
class ClideThemeData {
|
||||
const ClideThemeData({
|
||||
required this.name,
|
||||
required this.displayName,
|
||||
required this.dark,
|
||||
required this.surface,
|
||||
});
|
||||
const ClideThemeData({required this.name, required this.displayName, required this.dark, required this.surface});
|
||||
|
||||
final String name;
|
||||
final String displayName;
|
||||
@@ -19,12 +14,9 @@ class ClideThemeData {
|
||||
}
|
||||
|
||||
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))) {
|
||||
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);
|
||||
@@ -68,21 +60,12 @@ class ThemeController extends ChangeNotifier {
|
||||
surfaceOverride: def.surfaceOverride,
|
||||
extensionOverride: def.extensionOverride,
|
||||
);
|
||||
return ClideThemeData(
|
||||
name: def.name,
|
||||
displayName: def.displayName,
|
||||
dark: def.dark,
|
||||
surface: tokens,
|
||||
);
|
||||
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);
|
||||
const ClideTheme({super.key, required ThemeController controller, required super.child}) : super(notifier: controller);
|
||||
|
||||
static ClideThemeData of(BuildContext context) {
|
||||
final w = context.dependOnInheritedWidgetOfExactType<ClideTheme>();
|
||||
|
||||
@@ -70,10 +70,7 @@ class ThemeLoader {
|
||||
final surface = doc['surface'];
|
||||
final extension = doc['extension'];
|
||||
|
||||
final mergedSurface = <String, String>{
|
||||
...syntaxSurface,
|
||||
if (surface is Map) ..._parseRefMap(surface),
|
||||
};
|
||||
final mergedSurface = <String, String>{...syntaxSurface, if (surface is Map) ..._parseRefMap(surface)};
|
||||
|
||||
return ThemeDefinition(
|
||||
name: name,
|
||||
|
||||
@@ -28,12 +28,7 @@ class ThemeResolver {
|
||||
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,
|
||||
);
|
||||
surface[key] = _resolveSurface(key: key, palette: palette, semantic: semantic, surfaceOverride: surfaceOverride);
|
||||
}
|
||||
|
||||
// selectionBackground defaults to globalFocus at ~40 % opacity (0x66 alpha)
|
||||
@@ -149,12 +144,7 @@ class ThemeResolver {
|
||||
return SemanticRoles(roles);
|
||||
}
|
||||
|
||||
Color _resolveSurface({
|
||||
required String key,
|
||||
required Palette palette,
|
||||
required SemanticRoles semantic,
|
||||
Map<String, String>? surfaceOverride,
|
||||
}) {
|
||||
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);
|
||||
|
||||
@@ -24,17 +24,5 @@ abstract class SemanticKeys {
|
||||
static const error = 'error';
|
||||
static const info = 'info';
|
||||
|
||||
static const all = <String>[
|
||||
mainchrome,
|
||||
calltoaction,
|
||||
focus,
|
||||
background,
|
||||
surface,
|
||||
text,
|
||||
textMuted,
|
||||
success,
|
||||
warning,
|
||||
error,
|
||||
info,
|
||||
];
|
||||
static const all = <String>[mainchrome, calltoaction, focus, background, surface, text, textMuted, success, warning, error, info];
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user