diff --git a/lib/builtin/decisions/src/decision_detail_view.dart b/lib/builtin/decisions/src/decision_detail_view.dart index 597e077b..0ca9b9c0 100644 --- a/lib/builtin/decisions/src/decision_detail_view.dart +++ b/lib/builtin/decisions/src/decision_detail_view.dart @@ -16,17 +16,25 @@ class DecisionDetailView extends StatefulWidget { class _DecisionDetailViewState extends State { Map? _decision; bool _loading = false; + StreamSubscription? _sub; @override void didChangeDependencies() { super.didChangeDependencies(); - if (widget.initialId != null && _decision == null && !_loading) { + if (_sub != null) return; + final kernel = ClideKernel.of(context); + _sub = kernel.messages.subscribe(publisher: 'builtin.decisions', channel: 'selection').listen((msg) { + final id = msg.data['id'] as String?; + if (id != null) unawaited(_load(id)); + }); + if (widget.initialId != null) { unawaited(_load(widget.initialId!)); } } @override void dispose() { + _sub?.cancel(); super.dispose(); } @@ -35,12 +43,24 @@ class _DecisionDetailViewState extends State { final kernel = ClideKernel.of(context); final resp = await kernel.ipc.request('pql.decisions.read', args: {'id': id}); if (!mounted) return; + if (resp.ok) { + kernel.messages.publish('builtin.decisions', 'focus', {'id': id}); + } setState(() { _loading = false; _decision = resp.ok ? resp.data : null; }); } + void _navigateToRecord(BuildContext context, String id) { + final kernel = ClideKernel.of(context); + if (id.startsWith('T-')) { + kernel.messages.publish('builtin.tickets', 'selection', {'id': id}); + } else { + kernel.messages.publish('builtin.decisions', 'selection', {'id': id}); + } + } + @override Widget build(BuildContext context) { if (_loading) return const Center(child: ClideText('Loading…', muted: true)); @@ -83,13 +103,13 @@ class _DecisionDetailViewState extends State { child: Container(width: 10, height: 10, decoration: BoxDecoration(color: typeColor, shape: BoxShape.circle)), ), const SizedBox(width: 8), - ClideText(id, fontSize: 13, color: typeColor, fontFamily: clideMonoFamily), + ClideText(id, fontSize: clideFontSmall, color: typeColor, fontFamily: clideMonoFamily), const Spacer(), if (domain != null) Container( padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2), decoration: BoxDecoration(color: tokens.panelBorder, borderRadius: BorderRadius.circular(3)), - child: ClideText(domain, fontSize: 10, color: tokens.globalTextMuted, fontFamily: clideMonoFamily), + child: ClideText(domain, fontSize: clideFontBadge, color: tokens.globalTextMuted, fontFamily: clideMonoFamily), ), ], ), @@ -97,7 +117,7 @@ class _DecisionDetailViewState extends State { ClideText(title, fontSize: 15, fontWeight: FontWeight.w500), if (date != null) ...[ const SizedBox(height: 6), - ClideText(date, muted: true, fontSize: 12, fontFamily: clideMonoFamily), + ClideText(date, muted: true, fontSize: clideFontSmall, fontFamily: clideMonoFamily), ], if (status != null && status != 'active') ...[ const SizedBox(height: 8), @@ -108,11 +128,11 @@ class _DecisionDetailViewState extends State { ), if (body != null && body.isNotEmpty) ...[ const SizedBox(height: 12), - ClideMarkdown(body), + ClideMarkdown(body, onRecordTap: (id) => _navigateToRecord(context, id)), ], if (refs.isNotEmpty) ...[ const SizedBox(height: 16), - ClideText('CROSS-REFERENCES', fontSize: 11, color: tokens.sidebarSectionHeader, fontFamily: clideMonoFamily), + ClideText('CROSS-REFERENCES', fontSize: clideFontSmall, color: tokens.sidebarSectionHeader, fontFamily: clideMonoFamily), const SizedBox(height: 6), for (final ref in refs) _RefCard(ref: ref, tokens: tokens), ], @@ -144,9 +164,9 @@ class _RefCard extends StatelessWidget { ), child: Row( children: [ - ClideText(targetId, fontSize: 11, color: tokens.globalFocus, fontFamily: clideMonoFamily), + ClideText(targetId, fontSize: clideFontSmall, color: tokens.globalFocus, fontFamily: clideMonoFamily), const SizedBox(width: 8), - ClideText(refType, fontSize: 11, color: tokens.globalTextMuted), + ClideText(refType, fontSize: clideFontSmall, color: tokens.globalTextMuted), ], ), ), @@ -170,7 +190,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)), - child: ClideText(status.toUpperCase(), fontSize: 10, color: color, fontFamily: clideMonoFamily), + child: ClideText(status.toUpperCase(), fontSize: clideFontBadge, color: color, fontFamily: clideMonoFamily), ); } } diff --git a/lib/builtin/decisions/src/decisions_view.dart b/lib/builtin/decisions/src/decisions_view.dart index 3d194928..c09cfb19 100644 --- a/lib/builtin/decisions/src/decisions_view.dart +++ b/lib/builtin/decisions/src/decisions_view.dart @@ -17,11 +17,18 @@ class _DecisionsViewState extends State { String? _error; bool _loading = true; String _filter = ''; + String? _focusedId; + final _focusedKey = GlobalKey(); final Set _expanded = {'confirmed'}; + StreamSubscription? _focusSub; @override void didChangeDependencies() { super.didChangeDependencies(); + if (_focusSub == null) { + final kernel = ClideKernel.of(context); + _focusSub = kernel.messages.subscribe(publisher: 'builtin.decisions', channel: 'focus').listen(_onFocus); + } if (!_loading || _decisions.isNotEmpty) return; unawaited(_load()); } @@ -49,6 +56,27 @@ class _DecisionsViewState extends State { } } + @override + void dispose() { + _focusSub?.cancel(); + super.dispose(); + } + + void _onFocus(Message msg) { + final id = msg.data['id'] as String?; + if (id == null || id == _focusedId) return; + final entry = _decisions.where((d) => d.id == id).firstOrNull; + final section = entry?.type ?? 'confirmed'; + setState(() { + _focusedId = id; + _expanded.add(section); + }); + WidgetsBinding.instance.addPostFrameCallback((_) { + final ctx = _focusedKey.currentContext; + if (ctx != null) Scrollable.ensureVisible(ctx, duration: const Duration(milliseconds: 200), alignment: 0.3); + }); + } + void _toggleSection(String key) { setState(() { if (_expanded.contains(key)) { @@ -91,21 +119,21 @@ class _DecisionsViewState extends State { color: typeColors.confirmed, expanded: hasFilter || _expanded.contains('confirmed'), onToggle: () => _toggleSection('confirmed'), - children: [for (final d in confirmed) _DecisionCard(entry: d, tokens: tokens, typeColors: typeColors)], + children: [for (final d in confirmed) _DecisionCard(entry: d, tokens: tokens, typeColors: typeColors, focused: d.id == _focusedId, focusKey: d.id == _focusedId ? _focusedKey : null)], ), if (questions.isNotEmpty) _AccordionSection( label: 'QUESTIONS', count: questions.length, tokens: tokens, color: typeColors.question, expanded: hasFilter || _expanded.contains('question'), onToggle: () => _toggleSection('question'), - children: [for (final d in questions) _DecisionCard(entry: d, tokens: tokens, typeColors: typeColors)], + children: [for (final d in questions) _DecisionCard(entry: d, tokens: tokens, typeColors: typeColors, focused: d.id == _focusedId, focusKey: d.id == _focusedId ? _focusedKey : null)], ), if (rejected.isNotEmpty) _AccordionSection( label: 'REJECTED', count: rejected.length, tokens: tokens, color: typeColors.rejected, expanded: hasFilter || _expanded.contains('rejected'), onToggle: () => _toggleSection('rejected'), - children: [for (final d in rejected) _DecisionCard(entry: d, tokens: tokens, typeColors: typeColors)], + children: [for (final d in rejected) _DecisionCard(entry: d, tokens: tokens, typeColors: typeColors, focused: d.id == _focusedId, focusKey: d.id == _focusedId ? _focusedKey : null)], ), ], ), @@ -162,7 +190,7 @@ class _AccordionSection extends StatelessWidget { const SizedBox(width: 6), Container(width: 8, height: 8, decoration: BoxDecoration(color: color, shape: BoxShape.circle)), const SizedBox(width: 6), - ClideText('$label · $count', fontSize: 11, color: hovered ? tokens.globalForeground : tokens.sidebarSectionHeader, fontFamily: clideMonoFamily), + ClideText('$label · $count', fontSize: clideFontSmall, color: hovered ? tokens.globalForeground : tokens.sidebarSectionHeader, fontFamily: clideMonoFamily), ], ), ), @@ -174,24 +202,27 @@ class _AccordionSection extends StatelessWidget { } class _DecisionCard extends StatelessWidget { - const _DecisionCard({required this.entry, required this.tokens, required this.typeColors}); + const _DecisionCard({required this.entry, required this.tokens, required this.typeColors, this.focused = false, this.focusKey}); final _DecisionEntry entry; final SurfaceTokens tokens; final DecisionTypeColors typeColors; + final bool focused; + final GlobalKey? focusKey; @override Widget build(BuildContext context) { final typeColor = typeColors.forType(entry.type); return Padding( + key: focusKey, padding: const EdgeInsets.only(bottom: 4), child: ClideTappable( onTap: () => ClideKernel.of(context).messages.publish('builtin.decisions', 'selection', {'id': entry.id}), builder: (ctx, hovered, _) => Container( padding: const EdgeInsets.all(8), decoration: BoxDecoration( - color: hovered ? tokens.sidebarItemHover : tokens.panelBackground, + color: hovered ? tokens.sidebarItemHover : (focused ? tokens.sidebarItemSelected : tokens.panelBackground), borderRadius: BorderRadius.circular(4), - border: Border.all(color: hovered ? tokens.panelActiveBorder : tokens.panelBorder, width: 1), + border: Border.all(color: focused ? tokens.globalFocus : (hovered ? tokens.panelActiveBorder : tokens.panelBorder), width: 1), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -203,20 +234,20 @@ class _DecisionCard extends StatelessWidget { child: Container(width: 8, height: 8, decoration: BoxDecoration(color: typeColor, shape: BoxShape.circle)), ), const SizedBox(width: 6), - ClideText(entry.id, fontSize: 11, color: tokens.globalTextMuted, fontFamily: clideMonoFamily), + ClideText(entry.id, fontSize: clideFontSmall, color: tokens.globalTextMuted, fontFamily: clideMonoFamily), const Spacer(), if (entry.domain != null) - ClideText(entry.domain!, fontSize: 10, color: tokens.globalTextMuted, fontFamily: clideMonoFamily), + ClideText(entry.domain!, fontSize: clideFontBadge, color: tokens.globalTextMuted, fontFamily: clideMonoFamily), ], ), const SizedBox(height: 4), - ClideText(entry.title, fontSize: 13), + ClideText(entry.title, fontSize: clideFontCaption), if (entry.status == 'resolved') ...[ const SizedBox(height: 6), Container( padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2), decoration: BoxDecoration(color: tokens.statusSuccess.withAlpha(0x30), borderRadius: BorderRadius.circular(3)), - child: ClideText('resolved', fontSize: 10, color: tokens.statusSuccess, fontFamily: clideMonoFamily), + child: ClideText('resolved', fontSize: clideFontBadge, color: tokens.statusSuccess, fontFamily: clideMonoFamily), ), ], ], diff --git a/lib/builtin/graph/src/graph_view.dart b/lib/builtin/graph/src/graph_view.dart index 2149e150..e6c2391d 100644 --- a/lib/builtin/graph/src/graph_view.dart +++ b/lib/builtin/graph/src/graph_view.dart @@ -103,8 +103,8 @@ class _NodeRow extends StatelessWidget { padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4), child: Row( children: [ - Expanded(child: ClideText(node.path, fontSize: 13)), - ClideText('${node.inbound}in ${node.outbound}out', color: tokens.globalTextMuted, fontSize: 11), + Expanded(child: ClideText(node.path, fontSize: clideFontCaption)), + ClideText('${node.inbound}in ${node.outbound}out', color: tokens.globalTextMuted, fontSize: clideFontSmall), ], ), ), diff --git a/lib/builtin/markdown/src/markdown_viewer.dart b/lib/builtin/markdown/src/markdown_viewer.dart index 39f19710..e8a31ecf 100644 --- a/lib/builtin/markdown/src/markdown_viewer.dart +++ b/lib/builtin/markdown/src/markdown_viewer.dart @@ -67,6 +67,15 @@ class _MarkdownViewerState extends State { } } + void _navigateToRecord(BuildContext context, String id) { + final kernel = ClideKernel.of(context); + if (id.startsWith('T-')) { + kernel.messages.publish('builtin.tickets', 'selection', {'id': id}); + } else { + kernel.messages.publish('builtin.decisions', 'selection', {'id': id}); + } + } + @override Widget build(BuildContext context) { if (_error != null) { @@ -83,7 +92,7 @@ class _MarkdownViewerState extends State { subtitle: '${_content!.split('\n').length} lines', child: SingleChildScrollView( padding: const EdgeInsets.all(12), - child: ClideMarkdown(_content!), + child: ClideMarkdown(_content!, onRecordTap: (id) => _navigateToRecord(context, id)), ), ); } diff --git a/lib/builtin/tickets/src/ticket_detail_view.dart b/lib/builtin/tickets/src/ticket_detail_view.dart index 81d029f4..d6092b71 100644 --- a/lib/builtin/tickets/src/ticket_detail_view.dart +++ b/lib/builtin/tickets/src/ticket_detail_view.dart @@ -32,6 +32,15 @@ class _TicketDetailViewState extends State { super.dispose(); } + void _navigateToRecord(BuildContext context, String id) { + final kernel = ClideKernel.of(context); + if (id.startsWith('T-')) { + kernel.messages.publish('builtin.tickets', 'selection', {'id': id}); + } else { + kernel.messages.publish('builtin.decisions', 'selection', {'id': id}); + } + } + @override Widget build(BuildContext context) { final c = _controller; @@ -57,7 +66,7 @@ class _TicketDetailViewState extends State { _StatusControls(detail: d, tokens: tokens, controller: c), if (d.description != null && d.description!.isNotEmpty) ...[ const SizedBox(height: 12), - ClideText(d.description!, muted: true, fontSize: 13), + ClideMarkdown(d.description!, onRecordTap: (id) => _navigateToRecord(ctx, id)), ], if (d.parents.isNotEmpty) ...[ const SizedBox(height: 16), @@ -111,17 +120,17 @@ class _TicketHeader extends StatelessWidget { child: Container(width: 10, height: 10, decoration: BoxDecoration(color: typeColor, shape: BoxShape.circle)), ), const SizedBox(width: 8), - ClideText(detail.id, fontSize: 13, color: typeColor, fontFamily: clideMonoFamily), + ClideText(detail.id, fontSize: clideFontSmall, color: typeColor, fontFamily: clideMonoFamily), const Spacer(), if (detail.priority != null) - ClideText(detail.priority!, fontSize: 11, color: tokens.globalTextMuted, fontFamily: clideMonoFamily), + ClideText(detail.priority!, fontSize: clideFontSmall, color: tokens.globalTextMuted, fontFamily: clideMonoFamily), ], ), const SizedBox(height: 8), ClideText(detail.title, fontSize: 15, fontWeight: FontWeight.w500), if (detail.assignedTo != null) ...[ const SizedBox(height: 6), - ClideText('assigned: ${detail.assignedTo}', muted: true, fontSize: 12, fontFamily: clideMonoFamily), + ClideText('assigned: ${detail.assignedTo}', muted: true, fontSize: clideFontSmall, fontFamily: clideMonoFamily), ], ], ), @@ -159,7 +168,7 @@ class _StatusControls extends StatelessWidget { border: Border.all(color: active ? tokens.statusInfo : tokens.panelBorder), ), alignment: Alignment.center, - child: ClideText(_shortLabel(s), fontSize: 10, color: color, fontFamily: clideMonoFamily), + child: ClideText(_shortLabel(s), fontSize: clideFontBadge, color: color, fontFamily: clideMonoFamily), ); }, ), @@ -187,7 +196,7 @@ class _SectionLabel extends StatelessWidget { @override Widget build(BuildContext context) { - return ClideText(label, fontSize: 11, color: tokens.sidebarSectionHeader, fontFamily: clideMonoFamily); + return ClideText(label, fontSize: clideFontSmall, color: tokens.sidebarSectionHeader, fontFamily: clideMonoFamily); } } @@ -219,9 +228,9 @@ class _CompactCard extends StatelessWidget { children: [ Container(width: 6, height: 6, decoration: BoxDecoration(color: typeColor, shape: BoxShape.circle)), const SizedBox(width: 6), - ClideText(id, fontSize: 11, color: tokens.globalTextMuted, fontFamily: clideMonoFamily), + ClideText(id, fontSize: clideFontSmall, color: tokens.globalTextMuted, fontFamily: clideMonoFamily), const SizedBox(width: 8), - Expanded(child: ClideText(title, fontSize: 12)), + Expanded(child: ClideText(title, fontSize: clideFontSmall)), ], ), ), @@ -265,13 +274,13 @@ class _DecisionRefCard extends StatelessWidget { children: [ Container(width: 6, height: 6, decoration: BoxDecoration(color: color, shape: BoxShape.circle)), const SizedBox(width: 6), - ClideText(id, fontSize: 11, color: color, fontFamily: clideMonoFamily), + ClideText(id, fontSize: clideFontSmall, color: color, fontFamily: clideMonoFamily), const Spacer(), - if (domain != null) ClideText(domain, fontSize: 10, color: tokens.globalTextMuted, fontFamily: clideMonoFamily), + if (domain != null) ClideText(domain, fontSize: clideFontBadge, color: tokens.globalTextMuted, fontFamily: clideMonoFamily), ], ), const SizedBox(height: 3), - ClideText(title, fontSize: 12), + ClideText(title, fontSize: clideFontSmall), ], ), ), diff --git a/lib/builtin/tickets/src/tickets_view.dart b/lib/builtin/tickets/src/tickets_view.dart index 5fcab9c2..86311632 100644 --- a/lib/builtin/tickets/src/tickets_view.dart +++ b/lib/builtin/tickets/src/tickets_view.dart @@ -19,6 +19,7 @@ class _TicketsViewState extends State { bool _loading = true; String _filter = ''; String? _focusedId; + final _focusedKey = GlobalKey(); final Set _expanded = {'active', 'backlog'}; StreamSubscription? _focusSub; @@ -28,15 +29,34 @@ class _TicketsViewState extends State { }); } + static String _sectionForStatus(String? status) => switch (status) { + 'in_progress' => 'active', + 'backlog' || 'ready' => 'backlog', + 'done' => 'done', + _ => 'other', + }; + + void _onFocus(Message msg) { + final id = msg.data['id'] as String?; + if (id == null || id == _focusedId) return; + final entry = _tickets.where((t) => t.id == id).firstOrNull; + final section = _sectionForStatus(entry?.status); + setState(() { + _focusedId = id; + _expanded.add(section); + }); + WidgetsBinding.instance.addPostFrameCallback((_) { + final ctx = _focusedKey.currentContext; + if (ctx != null) Scrollable.ensureVisible(ctx, duration: const Duration(milliseconds: 200), alignment: 0.3); + }); + } + @override void didChangeDependencies() { super.didChangeDependencies(); if (_focusSub == null) { final kernel = ClideKernel.of(context); - _focusSub = kernel.messages.subscribe(publisher: 'builtin.tickets', channel: 'focus').listen((msg) { - final id = msg.data['id'] as String?; - if (id != _focusedId) setState(() => _focusedId = id); - }); + _focusSub = kernel.messages.subscribe(publisher: 'builtin.tickets', channel: 'focus').listen(_onFocus); } if (!_loading || _tickets.isNotEmpty) return; unawaited(_load()); @@ -98,10 +118,10 @@ class _TicketsViewState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ - if (active.isNotEmpty) _AccordionSection(label: 'ACTIVE', count: active.length, tokens: tokens, expanded: hasFilter || _expanded.contains('active'), onToggle: () => _toggle('active'), children: [for (final t in active) _TicketCard(entry: t, tokens: tokens, typeColors: typeColors, focused: t.id == _focusedId)]), - if (backlog.isNotEmpty) _AccordionSection(label: 'BACKLOG', count: backlog.length, tokens: tokens, expanded: hasFilter || _expanded.contains('backlog'), onToggle: () => _toggle('backlog'), children: [for (final t in backlog) _TicketCard(entry: t, tokens: tokens, typeColors: typeColors, focused: t.id == _focusedId)]), - if (done.isNotEmpty) _AccordionSection(label: 'DONE', count: done.length, tokens: tokens, expanded: hasFilter || _expanded.contains('done'), onToggle: () => _toggle('done'), children: [for (final t in done) _TicketCard(entry: t, tokens: tokens, typeColors: typeColors, focused: t.id == _focusedId)]), - if (other.isNotEmpty) _AccordionSection(label: 'OTHER', count: other.length, tokens: tokens, expanded: hasFilter || _expanded.contains('other'), onToggle: () => _toggle('other'), children: [for (final t in other) _TicketCard(entry: t, tokens: tokens, typeColors: typeColors, focused: t.id == _focusedId)]), + if (active.isNotEmpty) _AccordionSection(label: 'ACTIVE', count: active.length, tokens: tokens, expanded: hasFilter || _expanded.contains('active'), onToggle: () => _toggle('active'), children: [for (final t in active) _TicketCard(entry: t, tokens: tokens, typeColors: typeColors, focused: t.id == _focusedId, focusKey: t.id == _focusedId ? _focusedKey : null)]), + if (backlog.isNotEmpty) _AccordionSection(label: 'BACKLOG', count: backlog.length, tokens: tokens, expanded: hasFilter || _expanded.contains('backlog'), onToggle: () => _toggle('backlog'), children: [for (final t in backlog) _TicketCard(entry: t, tokens: tokens, typeColors: typeColors, focused: t.id == _focusedId, focusKey: t.id == _focusedId ? _focusedKey : null)]), + if (done.isNotEmpty) _AccordionSection(label: 'DONE', count: done.length, tokens: tokens, expanded: hasFilter || _expanded.contains('done'), onToggle: () => _toggle('done'), children: [for (final t in done) _TicketCard(entry: t, tokens: tokens, typeColors: typeColors, focused: t.id == _focusedId, focusKey: t.id == _focusedId ? _focusedKey : null)]), + if (other.isNotEmpty) _AccordionSection(label: 'OTHER', count: other.length, tokens: tokens, expanded: hasFilter || _expanded.contains('other'), onToggle: () => _toggle('other'), children: [for (final t in other) _TicketCard(entry: t, tokens: tokens, typeColors: typeColors, focused: t.id == _focusedId, focusKey: t.id == _focusedId ? _focusedKey : null)]), ], ), ), @@ -152,7 +172,7 @@ class _AccordionSection extends StatelessWidget { children: [ ClideIcon(expanded ? PhosphorIcons.caretDown : PhosphorIcons.caretRight, size: 10, color: tokens.globalTextMuted), const SizedBox(width: 6), - ClideText('$label · $count', fontSize: 11, color: hovered ? tokens.globalForeground : tokens.sidebarSectionHeader, fontFamily: clideMonoFamily), + ClideText('$label · $count', fontSize: clideFontSmall, color: hovered ? tokens.globalForeground : tokens.sidebarSectionHeader, fontFamily: clideMonoFamily), ], ), ), @@ -164,11 +184,12 @@ class _AccordionSection extends StatelessWidget { } class _TicketCard extends StatelessWidget { - const _TicketCard({required this.entry, required this.tokens, required this.typeColors, this.focused = false}); + const _TicketCard({required this.entry, required this.tokens, required this.typeColors, this.focused = false, this.focusKey}); final _TicketEntry entry; final SurfaceTokens tokens; final TicketTypeColors typeColors; final bool focused; + final GlobalKey? focusKey; @override Widget build(BuildContext context) { @@ -176,6 +197,7 @@ class _TicketCard extends StatelessWidget { final statusLabel = _statusLabel(entry.status); return Padding( + key: focusKey, padding: const EdgeInsets.only(bottom: 4), child: ClideTappable( onTap: () => ClideKernel.of(context).messages.publish('builtin.tickets', 'selection', {'id': entry.id}), @@ -200,15 +222,15 @@ class _TicketCard extends StatelessWidget { ), ), const SizedBox(width: 6), - ClideText(entry.id, fontSize: 11, color: tokens.globalTextMuted, fontFamily: clideMonoFamily), + ClideText(entry.id, fontSize: clideFontSmall, color: tokens.globalTextMuted, fontFamily: clideMonoFamily), if (entry.parentId != null) ...[ - ClideText(' ← ', fontSize: 11, color: tokens.globalTextMuted), - ClideText(entry.parentId!, fontSize: 11, color: tokens.globalTextMuted, fontFamily: clideMonoFamily), + ClideText(' ← ', fontSize: clideFontSmall, color: tokens.globalTextMuted), + ClideText(entry.parentId!, fontSize: clideFontSmall, color: tokens.globalTextMuted, fontFamily: clideMonoFamily), ], ], ), const SizedBox(height: 4), - ClideText(entry.title, fontSize: 13), + ClideText(entry.title, fontSize: clideFontCaption), if (statusLabel != null) ...[ const SizedBox(height: 6), _StatusBadge(label: statusLabel, tokens: tokens, status: entry.status), @@ -248,7 +270,7 @@ class _StatusBadge extends StatelessWidget { color: color.withAlpha(0x30), borderRadius: BorderRadius.circular(3), ), - child: ClideText(label, fontSize: 10, color: color, fontFamily: clideMonoFamily), + child: ClideText(label, fontSize: clideFontBadge, color: color, fontFamily: clideMonoFamily), ); } }