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:
@@ -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(),
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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),
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user