add ticket type color coding with dark/light presets
TicketTypeColors maps initiative/epic/story/task/bug to distinct colors, with dark and light presets auto-selected from the active theme. Ticket IDs are now color-coded by type (purple initiative, blue epic, green story, muted task, red bug). Status dot remains for state. Pattern documented in theme-ui skill as the standard for extension-owned domain colors. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -154,6 +154,19 @@ the shared root for all frame surfaces. They resolve to `bgSunken` /
|
||||
`textDim` / `border` in the palette. Themes can override them to diverge
|
||||
hat from sidebar from status bar if desired.
|
||||
|
||||
## Extension-owned domain colors
|
||||
|
||||
Extensions that need domain-specific color coding (ticket types, decision
|
||||
types, priority levels) should NOT add tokens to `SurfaceTokens`. Instead:
|
||||
|
||||
1. Create a color map class in the extension (e.g. `TicketTypeColors`)
|
||||
2. Ship dark and light presets, auto-selected via `ClideTheme.of(context).dark`
|
||||
3. Store user overrides under `ext.<id>.colors` in settings
|
||||
4. Reference: `lib/builtin/tickets/src/ticket_colors.dart`
|
||||
|
||||
This keeps the core token surface lean and lets each extension own its
|
||||
palette. The pattern scales to any extension needing domain colors.
|
||||
|
||||
## Anti-patterns
|
||||
|
||||
- Borrowing another surface's token (`sidebarBackground` for hat bar)
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
import 'dart:ui' show Color;
|
||||
|
||||
class TicketTypeColors {
|
||||
const TicketTypeColors({
|
||||
required this.initiative,
|
||||
required this.epic,
|
||||
required this.story,
|
||||
required this.task,
|
||||
required this.bug,
|
||||
});
|
||||
|
||||
final Color initiative;
|
||||
final Color epic;
|
||||
final Color story;
|
||||
final Color task;
|
||||
final Color bug;
|
||||
|
||||
Color forType(String? type) => switch (type) {
|
||||
'initiative' => initiative,
|
||||
'epic' => epic,
|
||||
'story' => story,
|
||||
'task' => task,
|
||||
'bug' => bug,
|
||||
_ => task,
|
||||
};
|
||||
|
||||
static const dark = TicketTypeColors(
|
||||
initiative: Color(0xFFC792EA),
|
||||
epic: Color(0xFF78A0F8),
|
||||
story: Color(0xFF7DD3A8),
|
||||
task: Color(0xFF78809C),
|
||||
bug: Color(0xFFE87D7D),
|
||||
);
|
||||
|
||||
static const light = TicketTypeColors(
|
||||
initiative: Color(0xFF7B2CB0),
|
||||
epic: Color(0xFF2962B8),
|
||||
story: Color(0xFF1D7A4E),
|
||||
task: Color(0xFF5A6070),
|
||||
bug: Color(0xFFC03030),
|
||||
);
|
||||
|
||||
static TicketTypeColors forTheme({required bool dark}) =>
|
||||
dark ? TicketTypeColors.dark : TicketTypeColors.light;
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:clide/builtin/tickets/src/ticket_colors.dart';
|
||||
import 'package:clide/kernel/kernel.dart';
|
||||
import 'package:clide/widgets/widgets.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
@@ -81,15 +82,17 @@ class _TicketsViewState extends State<TicketsView> {
|
||||
}
|
||||
|
||||
class _TicketEntry {
|
||||
const _TicketEntry({required this.id, required this.title, this.status, this.priority});
|
||||
const _TicketEntry({required this.id, required this.title, this.type, this.status, this.priority});
|
||||
final String id;
|
||||
final String title;
|
||||
final String? type;
|
||||
final String? status;
|
||||
final String? priority;
|
||||
|
||||
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?,
|
||||
);
|
||||
@@ -102,6 +105,9 @@ class _TicketRow extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final isDark = ClideTheme.of(context).dark;
|
||||
final typeColors = TicketTypeColors.forTheme(dark: isDark);
|
||||
final typeColor = typeColors.forType(entry.type);
|
||||
final statusColor = switch (entry.status) {
|
||||
'done' => tokens.statusSuccess,
|
||||
'in_progress' => tokens.statusInfo,
|
||||
@@ -114,7 +120,7 @@ class _TicketRow extends StatelessWidget {
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4),
|
||||
child: Row(
|
||||
children: [
|
||||
ClideText(entry.id, color: tokens.globalTextMuted, fontSize: 12),
|
||||
ClideText(entry.id, color: typeColor, fontSize: 12, fontFamily: clideMonoFamily),
|
||||
const SizedBox(width: 6),
|
||||
Container(
|
||||
width: 6,
|
||||
|
||||
Reference in New Issue
Block a user