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
+17 -17
View File
@@ -39,21 +39,21 @@ class TicketsExtension extends ClideExtension {
@override
List<ContributionPoint> get contributions => [
TabContribution(
id: 'tickets.panel',
slot: Slots.sidebar,
title: 'Tickets',
titleKey: 'tab.title',
i18nNamespace: id,
icon: PhosphorIcons.byName('ticket'),
build: (_) => const TicketsView(),
),
TabContribution(
id: 'tickets.detail',
slot: Slots.contextPanel,
title: 'Ticket',
icon: PhosphorIcons.byName('ticket'),
build: (_) => const TicketDetailView(),
),
];
TabContribution(
id: 'tickets.panel',
slot: Slots.sidebar,
title: 'Tickets',
titleKey: 'tab.title',
i18nNamespace: id,
icon: PhosphorIcons.byName('ticket'),
build: (_) => const TicketsView(),
),
TabContribution(
id: 'tickets.detail',
slot: Slots.contextPanel,
title: 'Ticket',
icon: PhosphorIcons.byName('ticket'),
build: (_) => const TicketDetailView(),
),
];
}
+8 -14
View File
@@ -1,13 +1,7 @@
import 'dart:ui' show Color;
class TicketTypeColors {
const TicketTypeColors({
required this.initiative,
required this.epic,
required this.story,
required this.task,
required this.bug,
});
const TicketTypeColors({required this.initiative, required this.epic, required this.story, required this.task, required this.bug});
final Color initiative;
final Color epic;
@@ -16,13 +10,13 @@ class TicketTypeColors {
final Color bug;
Color forType(String? type) => switch (type) {
'initiative' => initiative,
'epic' => epic,
'story' => story,
'task' => task,
'bug' => bug,
_ => task,
};
'initiative' => initiative,
'epic' => epic,
'story' => story,
'task' => task,
'bug' => bug,
_ => task,
};
static const dark = TicketTypeColors(
initiative: Color(0xFFC792EA),
+31 -25
View File
@@ -73,10 +73,7 @@ class _TicketDetailViewState extends State<TicketDetailView> {
return ClidePaneChrome(
title: d.id,
subtitle: d.title,
leading: ReaderPinButton(
pinned: _nav?.hasPinned ?? false,
onTap: _onPin,
),
leading: ReaderPinButton(pinned: _nav?.hasPinned ?? false, onTap: _onPin),
trailing: [
ReaderActionBar(
canGoBack: _nav?.canGoBack ?? false,
@@ -104,13 +101,7 @@ class _TicketDetailViewState extends State<TicketDetailView> {
const SizedBox(height: 16),
_SectionLabel(label: 'PARENT TREE', tokens: tokens),
const SizedBox(height: 6),
for (var i = 0; i < d.parents.length; i++)
_CompactCard(
data: d.parents[i],
tokens: tokens,
typeColors: typeColors,
indent: i,
),
for (var i = 0; i < d.parents.length; i++) _CompactCard(data: d.parents[i], tokens: tokens, typeColors: typeColors, indent: i),
],
if (d.decisions.isNotEmpty) ...[
const SizedBox(height: 16),
@@ -150,7 +141,11 @@ class _TicketHeader extends StatelessWidget {
children: [
ClideTooltip(
message: detail.type ?? 'task',
child: Container(width: 10, height: 10, decoration: BoxDecoration(color: typeColor, shape: BoxShape.circle)),
child: Container(
width: 10,
height: 10,
decoration: BoxDecoration(color: typeColor, shape: BoxShape.circle),
),
),
const SizedBox(width: 8),
ClideText(detail.id, fontSize: clideFontSmall, color: typeColor, fontFamily: clideMonoFamily),
@@ -188,10 +183,13 @@ class _StatusControls extends StatelessWidget {
onTap: detail.status == s
? null
: () async {
final resp = await controller.ipc.request('pql.tickets.status', args: {
'ids': [detail.id],
'status': s
});
final resp = await controller.ipc.request(
'pql.tickets.status',
args: {
'ids': [detail.id],
'status': s,
},
);
if (resp.ok) {
controller.messages.publish('builtin.tickets', 'changed', {'id': detail.id});
await controller.load(detail.id);
@@ -220,13 +218,13 @@ class _StatusControls extends StatelessWidget {
}
static String _shortLabel(String s) => switch (s) {
'backlog' => 'BACKLOG',
'ready' => 'READY',
'in_progress' => 'WIP',
'review' => 'REVIEW',
'done' => 'DONE',
_ => s.toUpperCase(),
};
'backlog' => 'BACKLOG',
'ready' => 'READY',
'in_progress' => 'WIP',
'review' => 'REVIEW',
'done' => 'DONE',
_ => s.toUpperCase(),
};
}
class _SectionLabel extends StatelessWidget {
@@ -266,7 +264,11 @@ class _CompactCard extends StatelessWidget {
),
child: Row(
children: [
Container(width: 6, height: 6, decoration: BoxDecoration(color: typeColor, shape: BoxShape.circle)),
Container(
width: 6,
height: 6,
decoration: BoxDecoration(color: typeColor, shape: BoxShape.circle),
),
const SizedBox(width: 6),
ClideText(id, fontSize: clideFontSmall, color: tokens.globalTextMuted, fontFamily: clideMonoFamily),
const SizedBox(width: 8),
@@ -312,7 +314,11 @@ class _DecisionRefCard extends StatelessWidget {
children: [
Row(
children: [
Container(width: 6, height: 6, decoration: BoxDecoration(color: color, shape: BoxShape.circle)),
Container(
width: 6,
height: 6,
decoration: BoxDecoration(color: color, shape: BoxShape.circle),
),
const SizedBox(width: 6),
ClideText(id, fontSize: clideFontSmall, color: color, fontFamily: clideMonoFamily),
const Spacer(),
+35 -41
View File
@@ -26,13 +26,7 @@ class _TicketsViewState extends State<TicketsView> {
/// ticket type; all on by default. An empty set never persists — toggling off
/// the last one snaps all back on, so the list is never mysteriously blank.
static const _allTypes = {'initiative', 'epic', 'story', 'task', 'bug'};
static const _typeOrder = [
('initiative', 'Initiative'),
('epic', 'Epic'),
('story', 'Story'),
('task', 'Task'),
('bug', 'Bug'),
];
static const _typeOrder = [('initiative', 'Initiative'), ('epic', 'Epic'), ('story', 'Story'), ('task', 'Task'), ('bug', 'Bug')];
final Set<String> _enabledTypes = {..._allTypes};
StreamSubscription<Message>? _focusSub;
StreamSubscription<SchedulerTick>? _schedulerSub;
@@ -107,9 +101,11 @@ class _TicketsViewState extends State<TicketsView> {
_focusSub = kernel.messages.subscribe(publisher: 'builtin.tickets', channel: 'focus').listen(_onFocus);
_changedSub = kernel.messages.subscribe(publisher: 'builtin.tickets', channel: 'changed').listen((msg) {
final id = msg.data['id'] as String?;
unawaited(_refresh().then((_) {
if (id != null && mounted) _scrollToFocused(id);
}));
unawaited(
_refresh().then((_) {
if (id != null && mounted) _scrollToFocused(id);
}),
);
});
_schedulerSub = kernel.events.on<SchedulerTick>().where((e) => e.tier == SchedulerTier.oneMinute).listen((_) => _refresh());
// The first load can fire before the project's workspace is wired into
@@ -208,7 +204,9 @@ class _TicketsViewState extends State<TicketsView> {
children: [
Row(
children: [
Expanded(child: ClideFilterBox(address: 'tickets.panel', hint: 'Filter tickets…', onChanged: (v) => setState(() => _filter = v))),
Expanded(
child: ClideFilterBox(address: 'tickets.panel', hint: 'Filter tickets…', onChanged: (v) => setState(() => _filter = v)),
),
Padding(
padding: const EdgeInsets.only(right: 8),
child: ClideTappable(
@@ -277,14 +275,7 @@ class _TicketsViewState extends State<TicketsView> {
/// type; double-click isolates it (chart-legend solo). One [GestureDetector]
/// owns both so Flutter disambiguates single vs double.
class _TypeChip extends StatelessWidget {
const _TypeChip({
required this.label,
required this.color,
required this.active,
required this.onToggle,
required this.onSolo,
required this.tokens,
});
const _TypeChip({required this.label, required this.color, required this.active, required this.onToggle, required this.onSolo, required this.tokens});
final String label;
final Color color;
@@ -317,7 +308,11 @@ class _TypeChip extends StatelessWidget {
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Container(width: 8, height: 8, decoration: BoxDecoration(color: dotColor, shape: BoxShape.circle)),
Container(
width: 8,
height: 8,
decoration: BoxDecoration(color: dotColor, shape: BoxShape.circle),
),
const SizedBox(width: 6),
ClideText(label, fontSize: clideFontSmall, fontFamily: clideMonoFamily, color: active ? tokens.globalForeground : tokens.globalTextMuted),
],
@@ -340,13 +335,13 @@ class _TicketEntry {
final String? parentId;
factory _TicketEntry.fromJson(Map<String, dynamic> json) => _TicketEntry(
id: json['id'] as String? ?? '',
title: json['title'] as String? ?? '',
type: json['type'] as String?,
status: json['status'] as String?,
priority: json['priority'] as String?,
parentId: json['parent_id'] as String?,
);
id: json['id'] as String? ?? '',
title: json['title'] as String? ?? '',
type: json['type'] as String?,
status: json['status'] as String?,
priority: json['priority'] as String?,
parentId: json['parent_id'] as String?,
);
}
class _TicketCard extends StatelessWidget {
@@ -412,15 +407,17 @@ class _TicketCard extends StatelessWidget {
),
const SizedBox(height: 4),
ClideText(entry.title, fontSize: clideFontCaption),
if (statusLabel != null) ...[
const SizedBox(height: 6),
_StatusBadge(label: statusLabel, tokens: tokens, status: entry.status),
],
if (statusLabel != null) ...[const SizedBox(height: 6), _StatusBadge(label: statusLabel, tokens: tokens, status: entry.status)],
],
),
// Hover affordance (T-327): hand the full ticket to the focused
// Claude pane via the message bus.
if (hovered) Positioned(top: 0, right: 0, child: _PickUpAction(id: entry.id, tokens: tokens)),
if (hovered)
Positioned(
top: 0,
right: 0,
child: _PickUpAction(id: entry.id, tokens: tokens),
),
],
),
),
@@ -429,11 +426,11 @@ class _TicketCard extends StatelessWidget {
}
static String? _statusLabel(String? status) => switch (status) {
'in_progress' => 'WIP',
'review' => 'REVIEW',
'cancelled' => 'CANCELLED',
_ => null,
};
'in_progress' => 'WIP',
'review' => 'REVIEW',
'cancelled' => 'CANCELLED',
_ => null,
};
}
/// The hover "pick up" run-icon (T-327): fetches the full ticket and publishes
@@ -482,10 +479,7 @@ class _StatusBadge extends StatelessWidget {
};
return Container(
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
decoration: BoxDecoration(
color: color.withAlpha(0x30),
borderRadius: BorderRadius.circular(3),
),
decoration: BoxDecoration(color: color.withAlpha(0x30), borderRadius: BorderRadius.circular(3)),
child: ClideText(label, fontSize: clideFontBadge, color: color, fontFamily: clideMonoFamily),
);
}