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:
2026-06-11 12:11:53 +02:00
co-authored by Claude Opus 4.8
parent bcea5f15b7
commit 6d0ebab721
444 changed files with 7587 additions and 12849 deletions
+48 -162
View File
@@ -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) {
+6 -23
View File
@@ -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>();
+1 -4
View File
@@ -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,
+2 -12
View File
@@ -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);
+1 -13
View File
@@ -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];
}