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
+9 -9
View File
@@ -19,13 +19,13 @@ class SearchExtension extends ClideExtension {
@override
List<ContributionPoint> get contributions => [
TabContribution(
id: 'search.findInFiles',
slot: Slots.sidebar,
title: 'Search',
icon: PhosphorIcons.byName('magnifying-glass'),
priority: -90,
build: (_) => const SearchPanelView(),
),
];
TabContribution(
id: 'search.findInFiles',
slot: Slots.sidebar,
title: 'Search',
icon: PhosphorIcons.byName('magnifying-glass'),
priority: -90,
build: (_) => const SearchPanelView(),
),
];
}
@@ -86,15 +86,18 @@ class FindInFilesController extends ChangeNotifier {
/// results. Returns the number of files changed and matches replaced.
/// Callers must gate on [isWorkingTreeClean] + user confirmation first.
Future<({int files, int count})> applyReplace() async {
final r = await ipc.request('search.replace', args: {
'pattern': pattern,
'regex': regex,
'ignoreCase': ignoreCase,
'include': _split(includeGlobs),
'exclude': _split(excludeGlobs),
'replacement': replacement,
'apply': true,
});
final r = await ipc.request(
'search.replace',
args: {
'pattern': pattern,
'regex': regex,
'ignoreCase': ignoreCase,
'include': _split(includeGlobs),
'exclude': _split(excludeGlobs),
'replacement': replacement,
'apply': true,
},
);
final files = (r.data['filesChanged'] as num?)?.toInt() ?? 0;
final count = (r.data['totalCount'] as num?)?.toInt() ?? 0;
await run(pattern); // refresh the match list against the new content
@@ -123,13 +126,10 @@ class FindInFilesController extends ChangeNotifier {
_running = true;
notifyListeners();
final resp = await ipc.request('search.grep', args: {
'pattern': pattern,
'regex': regex,
'ignoreCase': ignoreCase,
'include': _split(includeGlobs),
'exclude': _split(excludeGlobs),
});
final resp = await ipc.request(
'search.grep',
args: {'pattern': pattern, 'regex': regex, 'ignoreCase': ignoreCase, 'include': _split(includeGlobs), 'exclude': _split(excludeGlobs)},
);
if (!resp.ok) {
_error = resp.error?.message ?? 'search failed';
_running = false;
+57 -71
View File
@@ -50,17 +50,16 @@ class _SearchPanelViewState extends State<SearchPanelView> {
if (c.replacement.isEmpty || c.matchCount == 0) return;
final dialog = ClideKernel.of(context).dialog;
if (!await c.isWorkingTreeClean()) {
await dialog.show<Object>((ctx, dismiss) => _MessageDialog(
title: 'Working tree not clean',
body: 'Commit or stash your changes before replacing — git is the only undo.',
dismiss: dismiss,
));
await dialog.show<Object>(
(ctx, dismiss) =>
_MessageDialog(title: 'Working tree not clean', body: 'Commit or stash your changes before replacing — git is the only undo.', dismiss: dismiss),
);
return;
}
final confirmed = await dialog.show<bool>((ctx, dismiss) => _ConfirmDialog(
body: 'Replace ${c.matchCount} match(es) across ${c.fileCount} file(s)? This cannot be undone in clide.',
dismiss: dismiss,
));
final confirmed = await dialog.show<bool>(
(ctx, dismiss) =>
_ConfirmDialog(body: 'Replace ${c.matchCount} match(es) across ${c.fileCount} file(s)? This cannot be undone in clide.', dismiss: dismiss),
);
if (confirmed != true) return;
await c.applyReplace();
}
@@ -138,26 +137,34 @@ class _SearchPanelViewState extends State<SearchPanelView> {
Row(
children: [
Expanded(
child: ClideFilterBox(
address: 'search.findInFiles.replace', hint: 'Replace', icon: null, debounce: Duration.zero, onChanged: c.setReplacement)),
const SizedBox(width: 6),
_ReplaceAllButton(
enabled: c.replacement.isNotEmpty && c.matchCount > 0,
tokens: tokens,
onTap: _replaceAll,
child: ClideFilterBox(
address: 'search.findInFiles.replace',
hint: 'Replace',
icon: null,
debounce: Duration.zero,
onChanged: c.setReplacement,
),
),
const SizedBox(width: 6),
_ReplaceAllButton(enabled: c.replacement.isNotEmpty && c.matchCount > 0, tokens: tokens, onTap: _replaceAll),
],
),
const SizedBox(height: 6),
ClideFilterBox(
address: 'search.findInFiles.include',
hint: 'files to include (e.g. *.dart)',
icon: null,
debounce: Duration.zero,
onChanged: (v) => c.include = v),
address: 'search.findInFiles.include',
hint: 'files to include (e.g. *.dart)',
icon: null,
debounce: Duration.zero,
onChanged: (v) => c.include = v,
),
const SizedBox(height: 4),
ClideFilterBox(
address: 'search.findInFiles.exclude', hint: 'files to exclude', icon: null, debounce: Duration.zero, onChanged: (v) => c.exclude = v),
address: 'search.findInFiles.exclude',
hint: 'files to exclude',
icon: null,
debounce: Duration.zero,
onChanged: (v) => c.exclude = v,
),
],
),
),
@@ -171,14 +178,7 @@ class _SearchPanelViewState extends State<SearchPanelView> {
padding: EdgeInsets.zero,
children: [
for (final entry in groups.entries)
_FileGroup(
path: entry.key,
matches: entry.value,
tokens: tokens,
onTap: c.openMatch,
query: c.query,
replacement: c.replacement,
),
_FileGroup(path: entry.key, matches: entry.value, tokens: tokens, onTap: c.openMatch, query: c.query, replacement: c.replacement),
],
),
),
@@ -196,18 +196,15 @@ class _ModeSwitcher extends StatelessWidget {
final SurfaceTokens tokens;
final ValueChanged<SearchTabMode> onSelect;
static const _labels = {
SearchTabMode.find: 'Find',
SearchTabMode.vault: 'Vault',
SearchTabMode.query: 'Query',
SearchTabMode.markdown: 'Markdown',
};
static const _labels = {SearchTabMode.find: 'Find', SearchTabMode.vault: 'Vault', SearchTabMode.query: 'Query', SearchTabMode.markdown: 'Markdown'};
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 5),
decoration: BoxDecoration(border: Border(bottom: BorderSide(color: tokens.panelBorder))),
decoration: BoxDecoration(
border: Border(bottom: BorderSide(color: tokens.panelBorder)),
),
child: Row(
children: [
for (final m in SearchTabMode.values) ...[
@@ -233,13 +230,7 @@ class _ModeSwitcher extends StatelessWidget {
}
class _Toggle extends StatelessWidget {
const _Toggle({
required this.label,
required this.tooltip,
required this.active,
required this.tokens,
required this.onTap,
});
const _Toggle({required this.label, required this.tooltip, required this.active, required this.tokens, required this.onTap});
final String label;
final String tooltip;
@@ -296,14 +287,7 @@ class _StatusText extends StatelessWidget {
}
class _FileGroup extends StatelessWidget {
const _FileGroup({
required this.path,
required this.matches,
required this.tokens,
required this.onTap,
required this.query,
required this.replacement,
});
const _FileGroup({required this.path, required this.matches, required this.tokens, required this.onTap, required this.query, required this.replacement});
final String path;
final List<SearchMatch> matches;
@@ -336,13 +320,7 @@ class _FileGroup extends StatelessWidget {
}
class _MatchRow extends StatelessWidget {
const _MatchRow({
required this.match,
required this.tokens,
required this.onTap,
required this.query,
required this.replacement,
});
const _MatchRow({required this.match, required this.tokens, required this.onTap, required this.query, required this.replacement});
final SearchMatch match;
final SurfaceTokens tokens;
@@ -385,11 +363,17 @@ class _MatchRow extends StatelessWidget {
return RichText(
maxLines: 1,
overflow: TextOverflow.ellipsis,
text: TextSpan(style: _base, children: [
TextSpan(text: line.substring(0, start)),
TextSpan(text: line.substring(start, end), style: _base.copyWith(color: tokens.globalFocus, fontWeight: FontWeight.bold)),
TextSpan(text: line.substring(end)),
]),
text: TextSpan(
style: _base,
children: [
TextSpan(text: line.substring(0, start)),
TextSpan(
text: line.substring(start, end),
style: _base.copyWith(color: tokens.globalFocus, fontWeight: FontWeight.bold),
),
TextSpan(text: line.substring(end)),
],
),
);
}
@@ -403,12 +387,18 @@ class _MatchRow extends StatelessWidget {
RichText(
maxLines: 1,
overflow: TextOverflow.ellipsis,
text: TextSpan(text: match.preview, style: _base.copyWith(decoration: TextDecoration.lineThrough, color: tokens.globalTextMuted)),
text: TextSpan(
text: match.preview,
style: _base.copyWith(decoration: TextDecoration.lineThrough, color: tokens.globalTextMuted),
),
),
RichText(
maxLines: 1,
overflow: TextOverflow.ellipsis,
text: TextSpan(text: after, style: _base.copyWith(color: tokens.globalFocus)),
text: TextSpan(
text: after,
style: _base.copyWith(color: tokens.globalFocus),
),
),
],
);
@@ -437,11 +427,7 @@ class _ReplaceAllButton extends StatelessWidget {
border: Border.all(color: tokens.buttonBorder),
borderRadius: BorderRadius.circular(4),
),
child: ClideText(
'Replace all',
fontSize: clideFontCaption,
color: enabled ? tokens.sidebarForeground : tokens.globalTextMuted,
),
child: ClideText('Replace all', fontSize: clideFontCaption, color: enabled ? tokens.sidebarForeground : tokens.globalTextMuted),
),
),
);