From 29ac9b3cafbe93f8f277a9f72c36c603a3bc23cc Mon Sep 17 00:00:00 2001 From: Jeroen Schweitzer Date: Wed, 6 May 2026 15:38:19 +0200 Subject: [PATCH] consolidate theme-ui into ui-design skill (T-85) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replaces the single-file theme-ui skill with a routed ui-design skill backed by four references: - theme.md — token system, identity rule, palette layers, type - surface.md — token selection per surface (chrome, panels, tabs, buttons, status, overlays) - geometry.md — control spacing/alignment principles distilled from the MultitabPane work: uniform inner spacing rule, no double-edge padding, two-column control pattern, perceived mass over measured pixels - icons.md — Phosphor icons + clide-owned painters SKILL.md routes to the right reference and holds the universal rules. The trigger description widens to cover spacing/alignment questions in addition to token selection. geometry.md references T-86 (codify spacing constants); the doc uses literal pixel values until those constants land. Co-Authored-By: Claude --- .claude/skills/theme-ui/SKILL.md | 209 ------------------ .claude/skills/ui-design/SKILL.md | 51 +++++ .../skills/ui-design/references/geometry.md | 170 ++++++++++++++ .claude/skills/ui-design/references/icons.md | 89 ++++++++ .../skills/ui-design/references/surface.md | 120 ++++++++++ .claude/skills/ui-design/references/theme.md | 70 ++++++ .pql/pql-plan.json | 38 +++- CHANGELOG.md | 8 + 8 files changed, 543 insertions(+), 212 deletions(-) delete mode 100644 .claude/skills/theme-ui/SKILL.md create mode 100644 .claude/skills/ui-design/SKILL.md create mode 100644 .claude/skills/ui-design/references/geometry.md create mode 100644 .claude/skills/ui-design/references/icons.md create mode 100644 .claude/skills/ui-design/references/surface.md create mode 100644 .claude/skills/ui-design/references/theme.md diff --git a/.claude/skills/theme-ui/SKILL.md b/.claude/skills/theme-ui/SKILL.md deleted file mode 100644 index 089317db..00000000 --- a/.claude/skills/theme-ui/SKILL.md +++ /dev/null @@ -1,209 +0,0 @@ ---- -name: theme-ui -description: >- - Token selection guide for clide UI development. Use when building or - modifying widgets, panels, pane chrome, status indicators, icons, or - any visual surface. Ensures correct background, border, text, and hover - tokens are applied per surface type. Triggers on: new widget code, - theme-related changes, "which token", "what color", color/background - questions, visual inconsistency fixes, new panel/pane/view development, - adding or looking up Phosphor icons, icon codepoints. ---- - -# Theme-UI — token selection for clide surfaces - -All colors come from `SurfaceTokens` via `ClideTheme.of(context).surface`. -Never hardcode colors. Never use Material/Cupertino color constants. - -## Token selection by surface - -Pick tokens based on **where** the widget lives, not what it does. - -### Chrome (hat bar, status bar, sidebar, context panel, spines, drag handles) - -``` -background → chromeBackground -text → chromeForeground -border → chromeBorder (1px) -active text → globalForeground -``` - -### Side panels (sidebar, context panel) - -``` -background → chromeBackground (both sides — they're chrome frame) -text → sidebarForeground -hover → sidebarItemHover -selected → sidebarItemSelected -section head → sidebarSectionHeader (muted, used for "START", "FILES", etc.) -``` - -Padding: 2px on outer edges, 0px on divider edge. - -### Center column (workspace, Claude pane, editor) - -``` -background → panelBackground -text → globalForeground -``` - -No padding — content fills edge to edge. - -### Pane headers (`ClidePaneChrome`) - -``` -background → panelHeader -text (title) → panelHeaderForeground -text (sub) → globalTextMuted -``` - -### List items (decisions, tickets, file rows, backlinks) - -``` -background → (none / transparent) -hover bg → listItemHoverBackground -selected bg → listItemSelectedBackground -text → listItemForeground / sidebarForeground (in sidebar) -selected txt → listItemSelectedForeground -``` - -In sidebar context, use `sidebarItemHover` not `listItemHoverBackground`. - -### Buttons - -``` -normal → buttonBackground / buttonForeground / buttonBorder -hover → buttonHoverBackground -active → buttonActiveBackground -primary → buttonActiveBackground bg + globalBackground text -subtle → listItemBackground / listItemHoverBackground (no border) -``` - -### Dividers and separators - -``` -line → dividerColor (always, everywhere) -drag handle → 8px hit area, 1px visible line, panel bg fill -hover line → panelActiveBorder -``` - -### Status indicators - -``` -success/ok → statusSuccess (green: done, added, connected) -warning → statusWarning (amber: question, modified, missing) -error → statusError (red: deleted, rejected, cancelled) -info → statusInfo (blue: in_progress, modified) -``` - -Map semantic states, not visual styles: -- `done` / `added` / `ok` → `statusSuccess` -- `in_progress` / `modified` → `statusInfo` -- `question` / `warning` → `statusWarning` -- `cancelled` / `deleted` / `error` → `statusError` - -### Overlays (dialogs, palette, tooltips) - -``` -dialog bg → modalSurfaceBackground -dialog border→ modalSurfaceBorder -backdrop → modalOverlayBackground -tooltip → tooltipBackground / tooltipForeground / tooltipBorder -dropdown → dropdownBackground / dropdownForeground / dropdownBorder -``` - -## Typography - -Three constants — never hardcode sizes or families. - -``` -family UI → inherited from DefaultTextStyle (JosefinSans Light 300) -family mono → clideMonoFamily (JetBrainsMono) -body size → clideFontBody (15) -caption size → clideFontCaption (14) — status bar, section headers, git info -mono size → clideFontMono (14) — terminal, code, paths, IDs -``` - -Use `ClideText` for themed text. Set `muted: true` for secondary text -(resolves to `globalTextMuted`). Set `fontFamily: clideMonoFamily` for -code/paths/IDs. Don't set fontFamily for UI text — it inherits. - -## Token identity rule - -Every visual surface gets its own named token. Never borrow a token from -another surface just because they happen to resolve to the same color. - -**Wrong:** `sidebarBackground` for the hat bar (the hat isn't a sidebar). -**Right:** Create `chromeBackground` that resolves to the same palette key. - -When two surfaces share a color: -1. **If they're the same conceptual surface** (sidebar + context panel are - both "side panels") → one shared token set is fine. -2. **If they're different surfaces that happen to match** (hat bar + sidebar - + status bar are all "chrome frame") → create a shared primitive in the - palette/semantic layer (e.g. `bgChrome`) and give each surface its own - token that maps to that primitive. This lets themes diverge them later. - -The palette layer has these depth primitives: -- `bg` (`#20202C`) — outermost root, behind everything -- `bgSunken` (`#1A1A24`) — chrome frame: sidebar, hat, statusbar -- `surface` (`#242838`) — elevated: pane headers, active tabs -- `surfaceHi` (`#2C3046`) — interactive: hover states, selections - -Chrome tokens (`chromeBackground`/`chromeForeground`/`chromeBorder`) are -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..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. - -## Icons — Phosphor Icons - -The app bundles Phosphor Icons (v2.0.8, MIT) as TTF fonts at -`assets/fonts/phosphor/` (regular, bold, fill weights). - -**Codepoint reference:** `assets/fonts/phosphor/codepoints.csv` — -full mapping of all 1512 icon codepoints to kebab-case and PascalCase -names. Read this file to look up any icon by name or codepoint. - -**Adding an icon:** find the codepoint in `codepoints.csv`, then add -a `static const` entry to `PhosphorIcons` in -`lib/widgets/src/icons/phosphor.dart`: - -```dart -static const arrowClockwise = PhosphorIconPainter(0xe036); -``` - -Only add icons we actually use — don't bulk-import the full set. - -**Using an icon:** `ClideIcon(PhosphorIcons.arrowClockwise, size: 13)` -or as a `TabContribution` icon field: `icon: PhosphorIcons.lightbulb`. - -**Bold weight:** pass `family: 'Phosphor-Bold'` to `PhosphorIconPainter`. -Fill weight: `family: 'Phosphor-Fill'`. - -## Anti-patterns - -- Borrowing another surface's token (`sidebarBackground` for hat bar) -- `globalBackground` for panel fill → use `panelBackground` -- `listItemHoverBackground` in sidebar → use `sidebarItemHover` -- Hardcoded `Color(0xFF...)` → use a token -- `fontSize: 14` → use `clideFontCaption` or `clideFontMono` -- `fontFamily: 'JetBrainsMono'` → use `clideMonoFamily` - -## Reference - -Full token list: `lib/kernel/src/theme/tokens.dart` -Resolver fallbacks: `lib/kernel/src/theme/resolver.dart` -Theme YAML example: `lib/kernel/src/theme/themes/clide.yaml` diff --git a/.claude/skills/ui-design/SKILL.md b/.claude/skills/ui-design/SKILL.md new file mode 100644 index 00000000..76e2cb7f --- /dev/null +++ b/.claude/skills/ui-design/SKILL.md @@ -0,0 +1,51 @@ +--- +name: ui-design +description: >- + Visual design guide for clide UI development — covers theme tokens, + surface-specific token selection, control geometry/spacing/alignment, + and Phosphor icons. Use when building or modifying widgets, panels, + pane chrome, status indicators, tabs, list items, dialogs, or any + visual surface. Triggers on: new widget code, theme-related changes, + "which token", "what color", color/background questions, visual + inconsistency fixes, "alignment off", "spacing", "padding", control + geometry questions, new panel/pane/view development, adding or + looking up Phosphor icons, icon codepoints. +--- + +# UI design — clide visual surface guide + +This skill bundles four concerns that all surface in widget work: + +| Concern | Reference | When to read | +|---------|-----------|--------------| +| Token system, palette, typography | [`references/theme.md`](references/theme.md) | Designing or extending the theme pipeline; deciding whether to add a new token | +| Token selection per surface | [`references/surface.md`](references/surface.md) | Building a new widget or modifying an existing one — "which token does this need" | +| Spacing, alignment, control layout | [`references/geometry.md`](references/geometry.md) | Building tab strips, list items, buttons, anything where icons sit next to text or padded edges | +| Phosphor icon usage and codepoints | [`references/icons.md`](references/icons.md) | Adding or referencing an icon | + +Read the reference that matches the question. They cross-reference each +other where relevant; you don't need to read all four. + +## Universal rules + +These apply across every reference and every surface: + +- All colors come from `SurfaceTokens` via `ClideTheme.of(context).surface`. + Never hardcode `Color(0xFF...)`. +- Never use `Material*` or `Cupertino*` widgets or color constants — clide + is `WidgetsApp` only (D-7). +- Use `ClideText` for themed text; never bare `Text` in production widgets. +- Typography: `clideFontMono` for code/paths/IDs, `clideFontCaption` for + status/section headers, body inherits from `DefaultTextStyle`. + +## Anti-patterns (cross-cutting) + +- Borrowing another surface's token (`sidebarBackground` for hat bar) — give + each surface its own token even if they share a palette key. See `theme.md`. +- Hardcoded hex colors → use a token. See `surface.md` for which one. +- `fontSize: 14` literal → use `clideFontCaption` or `clideFontMono`. +- `fontFamily: 'JetBrainsMono'` literal → use `clideMonoFamily`. +- Stacking edge padding on a padded parent + a padded child action → see + `geometry.md` "no double edge padding". +- Eyeballing pixel margins instead of working back from the constraint — + the math matters; see `geometry.md` "uniform inner spacing". diff --git a/.claude/skills/ui-design/references/geometry.md b/.claude/skills/ui-design/references/geometry.md new file mode 100644 index 00000000..2d7bf0d1 --- /dev/null +++ b/.claude/skills/ui-design/references/geometry.md @@ -0,0 +1,170 @@ +# Geometry — spacing, alignment, control layout + +Principles for placing icons, buttons, and text inside controls. +Apply when building tab strips, list items, buttons with affordances, +or anything where actions sit next to content. + +> Numeric values in this doc will move to `ClideSpacing` constants +> (`lib/widgets/src/spacing.dart`) — see T-86. Until then, the +> constants used in the codebase: `12` (text inset), `6` (icon +> breathing), `8` (standard gap), `4` (tight gap), `16` (icon hit +> area), `28` (button / row height). + +## Uniform inner spacing rule + +Icons inside control surfaces should have **equal margin on every +constrained side**. The "constrained sides" are top, bottom, and the +side opposite to where content flows in. + +The remaining side — where the text or other content sits — gets a +larger, content-appropriate breathing room. + +Example: tab close button (16×16 inside a 28-tall tab): + +``` +top : 6 ┐ +bottom : 6 ├─ uniform: (28 − 16) / 2 = 6 +right : 6 ┘ +left : 8 ── content gap (separates from title text) +``` + +The visual effect: the close button looks like a deliberate +affordance with a calm, consistent border, not a glyph stuffed into +the corner. + +## No double-edge padding + +When a fixed-size action (icon button, close ×) sits at the edge of +a padded parent, the parent's padding on that edge should **not stack** +with the action's own internal margin. Pick one place to hold the +breathing room. + +Wrong: + +```dart +Container( + padding: EdgeInsets.symmetric(horizontal: 12), // tab pad: 12 right + child: Row(children: [ + Expanded(child: title), + SizedBox(width: 8), // gap: 8 + Container(width: 28, alignment: Center, // close: 6 internal margin + child: Icon(close, size: 16)), + ]), +) +// Visible margin from icon right to outer right = 12 + 6 = 18px → too much +``` + +Right: + +```dart +Container( + padding: EdgeInsets.only(left: 12, right: 6), // pad matches icon margin + child: Row(children: [ + Expanded(child: title), + SizedBox(width: 8), + Container(width: 16, height: 16, alignment: Center, // hit target = icon size + child: Icon(close, size: 10)), + ]), +) +// Visible margin = 6 (parent right pad) ≈ 6 (top/bottom auto) → uniform +``` + +## Two-column control pattern + +For tab-shaped or row-shaped controls with a primary content area and +a secondary action: + +```dart +Row(children: [ + Expanded(child: ), // takes remainder + if (action != null) ...[ + SizedBox(width: 8), // standard gap + , // shrinks to content + ], +]) +``` + +- **Left column**: `Expanded`, holds the primary content (title, + label, description). Aligned to the start of its space by default. +- **Right column**: fixed natural width, holds the action (close, + status, indicator). Sized to the icon, not to artificial padding. + +The parent container's padding sits flush against both columns (see +"no double-edge padding"). + +## Match perceived mass, not measured pixels + +Glyphs vary in visual weight. A bold `+` looks heavier than a thin +`×` at the same point size. When eyeballing alignment, trust the +optical center over the geometric center. + +In practice: if two icons measure to the same margin but one *looks* +crowded, give the heavier glyph slightly more breathing room and +trim the lighter one. For clide, this came up with the `×` close +glyph vs the `+` add glyph — both at 14pt, but `+` reads as denser +and is left in its 28-wide button without further padding, while +`×` sits in a 16×16 hit area with 6px symmetric margin. + +## Strip / row should fill the parent + +Tab strips, status bars, and divider rows should span the full +parent width, not size to their content. Without this, the strip +looks like it floats inside the pane. + +```dart +Column( + crossAxisAlignment: CrossAxisAlignment.stretch, // <-- this + children: [ + _TabStrip(...), + Expanded(child: _body(...)), + ], +) +``` + +Without `stretch`, Column gives loose width constraints and a +`Container(height: tabHeight)` child sizes to its child's natural +width — the strip ends mid-pane. + +## Anchor strips with a divider + +Add a 1px bottom border (`dividerColor`) to tab strips and any +header strip that sits above content. Without it, the strip looks +disconnected from the body and the perceived alignment slips. + +```dart +Container( + height: 28, + decoration: BoxDecoration( + color: tokens.tabBarBackground, + border: Border(bottom: BorderSide(color: tokens.dividerColor)), + ), + child: ..., +) +``` + +## Anti-patterns + +- Centering a glyph inside a "hover background" that's larger than + the natural icon size, then surrounding the whole thing with a + padded parent — the icon ends up far inside the visible edge. +- Hardcoded `padding: EdgeInsets.symmetric(horizontal: 12)` on every + control regardless of whether the right edge has an action — see + "no double-edge padding". +- Tab strip inside `Column` without `crossAxisAlignment.stretch` — + the strip ends mid-pane. +- `mainAxisSize.min` on the tab strip's outer Row when you actually + want it to fill parent width — only use `min` for pill-shaped + controls that should hug their content. +- Eyeballing alignment without working back from a target margin in + pixels. The math matters; see "uniform inner spacing". + +## Testing alignment + +When iterating on a control's spacing: + +1. State the target margin (e.g. "6px around the close icon, all + sides except left"). +2. Map every contributing source: parent padding, gap SizedBoxes, + container alignment offsets, icon-to-container size differences. +3. Sum them. Adjust until they hit the target. +4. Verify visually — perceived mass may justify a 1–2px tweak. diff --git a/.claude/skills/ui-design/references/icons.md b/.claude/skills/ui-design/references/icons.md new file mode 100644 index 00000000..9eede1ed --- /dev/null +++ b/.claude/skills/ui-design/references/icons.md @@ -0,0 +1,89 @@ +# Icons — Phosphor + clide-owned painters + +## Phosphor Icons + +The app bundles Phosphor Icons (v2.0.8, MIT) as TTF fonts at +`assets/fonts/phosphor/` (regular, bold, fill weights). + +**Codepoint reference:** `assets/fonts/phosphor/codepoints.csv` — full +mapping of all 1512 icon codepoints to kebab-case and PascalCase +names. Read this file to look up any icon by name or codepoint. + +### Adding an icon + +Find the codepoint in `codepoints.csv`, then add a `static const` +entry to `PhosphorIcons` in `lib/widgets/src/icons/phosphor.dart`: + +```dart +static const arrowClockwise = PhosphorIconPainter(0xe036); +``` + +Only add icons we actually use — don't bulk-import the full set. + +### Using an icon + +```dart +ClideIcon(PhosphorIcons.arrowClockwise, size: 13) +``` + +Or as a `TabContribution` icon field: `icon: PhosphorIcons.lightbulb`. + +**Bold weight:** pass `family: 'Phosphor-Bold'` to `PhosphorIconPainter`. +**Fill weight:** `family: 'Phosphor-Fill'`. + +## clide-owned painters + +Some shapes are simple enough to paint directly without an icon +font. Hand-rolled `ClideIconPainter` subclasses live under +`lib/widgets/src/icons/`: + +- `CheckIcon`, `ChevronIcon`, `CloseIcon` (`x.dart`) +- `DotIcon`, `FolderIcon`, `GearIcon` +- `GitBranchIcon`, `PlugIcon`, `SearchIcon` +- `TerminalIcon`, `WarningIcon` + +Use these for tiny, theme-aware glyphs (close ×, dropdown chevrons, +status dots) where pulling in the Phosphor font weight would be +overkill or where the visual needs to match the theme's stroke +weight conventions. + +Pattern for a new painter: + +```dart +class FoobarIcon extends ClideIconPainter { + const FoobarIcon(); + + @override + void paint(Canvas canvas, Color color) { + final p = Paint() + ..color = color + ..strokeWidth = 0.10 + ..strokeCap = StrokeCap.round; + // Coordinates are 0..1 (the painter is given a unit square). + canvas.drawLine(const Offset(0.2, 0.2), const Offset(0.8, 0.8), p); + } +} +``` + +## Sizing + +Icon sizes used in clide (subject to consolidation under +`ClideSpacing` — see T-86): + +- `10` — micro: close × inside a tab +- `13` — caption-row icons (sidebar, status bar) +- `14` — standard inline icons (icon rail) +- `16` — small icon hit-target outer container +- `18`–`20` — emphatic / standalone icons + +Pass `size:` to `ClideIcon`; the painter receives a unit-square +canvas regardless. Color defaults to `globalForeground`; pass +explicit `color:` for muted/active variants. + +## Anti-patterns + +- Importing all of Phosphor — only declare codepoints we use. +- Hand-painting a glyph that already exists in Phosphor at the right + weight — use the font. +- Hardcoded `Color` on icons — pass through the surface tokens + (`globalForeground`, `globalTextMuted`, `panelActiveBorder`, etc.). diff --git a/.claude/skills/ui-design/references/surface.md b/.claude/skills/ui-design/references/surface.md new file mode 100644 index 00000000..6a98446c --- /dev/null +++ b/.claude/skills/ui-design/references/surface.md @@ -0,0 +1,120 @@ +# Surface — token selection per surface type + +Pick tokens based on **where** the widget lives, not what it does. + +## Chrome (hat bar, status bar, sidebar, context panel, spines, drag handles) + +``` +background → chromeBackground +text → chromeForeground +border → chromeBorder (1px) +active text → globalForeground +``` + +## Side panels (sidebar, context panel) + +``` +background → chromeBackground (both sides — they're chrome frame) +text → sidebarForeground +hover → sidebarItemHover +selected → sidebarItemSelected +section head → sidebarSectionHeader (muted, used for "START", "FILES", etc.) +``` + +Padding: 2px on outer edges, 0px on divider edge. + +## Center column (workspace, Claude pane, editor) + +``` +background → panelBackground +text → globalForeground +``` + +No padding — content fills edge to edge. + +## Pane headers (`ClidePaneChrome`) + +``` +background → panelHeader +text (title) → panelHeaderForeground +text (sub) → globalTextMuted +``` + +## Tabs (`MultitabPane`, `ClideTabBar`) + +``` +strip bg → tabBarBackground +strip border → bottom: dividerColor (anchors strip to body) +active fg → tabActiveForeground +inactive fg → tabInactiveForeground +active bg → panelHeader (elevated chrome) +inactive bg → tabBarBackground (blends with strip) +active border→ panelActiveBorder (top accent, 1.5px) +side border → panelBorder +``` + +For control geometry inside tabs (close button placement, padding, +two-column title+action layout) see [`geometry.md`](geometry.md). + +## List items (decisions, tickets, file rows, backlinks) + +``` +background → (none / transparent) +hover bg → listItemHoverBackground +selected bg → listItemSelectedBackground +text → listItemForeground / sidebarForeground (in sidebar) +selected txt → listItemSelectedForeground +``` + +In sidebar context, use `sidebarItemHover` not `listItemHoverBackground`. + +## Buttons + +``` +normal → buttonBackground / buttonForeground / buttonBorder +hover → buttonHoverBackground +active → buttonActiveBackground +primary → buttonActiveBackground bg + globalBackground text +subtle → listItemBackground / listItemHoverBackground (no border) +``` + +## Dividers and separators + +``` +line → dividerColor (always, everywhere) +drag handle → 8px hit area, 1px visible line, panel bg fill +hover line → panelActiveBorder +``` + +## Status indicators + +``` +success/ok → statusSuccess (green: done, added, connected) +warning → statusWarning (amber: question, modified, missing) +error → statusError (red: deleted, rejected, cancelled) +info → statusInfo (blue: in_progress, modified) +``` + +Map semantic states, not visual styles: + +- `done` / `added` / `ok` → `statusSuccess` +- `in_progress` / `modified` → `statusInfo` +- `question` / `warning` → `statusWarning` +- `cancelled` / `deleted` / `error` → `statusError` + +## Overlays (dialogs, palette, tooltips) + +``` +dialog bg → modalSurfaceBackground +dialog border→ modalSurfaceBorder +backdrop → modalOverlayBackground +tooltip → tooltipBackground / tooltipForeground / tooltipBorder +dropdown → dropdownBackground / dropdownForeground / dropdownBorder +``` + +## Anti-patterns + +- `globalBackground` for panel fill → use `panelBackground` +- `listItemHoverBackground` in sidebar → use `sidebarItemHover` +- Tab active bg = `panelBackground` → use `panelHeader` (elevated chrome) +- Tab active border = `globalFocus` → use `panelActiveBorder` diff --git a/.claude/skills/ui-design/references/theme.md b/.claude/skills/ui-design/references/theme.md new file mode 100644 index 00000000..de3e4b01 --- /dev/null +++ b/.claude/skills/ui-design/references/theme.md @@ -0,0 +1,70 @@ +# Theme — token system, palette, typography + +## Token identity rule + +Every visual surface gets its own named token. Never borrow a token from +another surface just because they happen to resolve to the same color. + +**Wrong:** `sidebarBackground` for the hat bar (the hat isn't a sidebar). +**Right:** Create `chromeBackground` that resolves to the same palette key. + +When two surfaces share a color: + +1. **Same conceptual surface** (sidebar + context panel are both "side + panels") → one shared token set is fine. +2. **Different surfaces that happen to match** (hat bar + sidebar + + status bar are all "chrome frame") → create a shared primitive in the + palette/semantic layer (e.g. `bgChrome`) and give each surface its + own token that maps to that primitive. This lets themes diverge them + later without breaking widgets. + +## Palette depth primitives + +The palette layer has these depth primitives (defined in each theme YAML): + +- `bg` (`#20202C`) — outermost root, behind everything +- `bgSunken` (`#1A1A24`) — chrome frame: sidebar, hat, statusbar +- `surface` (`#242838`) — elevated: pane headers, active tabs +- `surfaceHi` (`#2C3046`) — interactive: hover states, selections + +Chrome tokens (`chromeBackground` / `chromeForeground` / `chromeBorder`) +are 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. + +## Typography + +Three constants — never hardcode sizes or families: + +``` +family UI → inherited from DefaultTextStyle (JosefinSans Light 300) +family mono → clideMonoFamily (JetBrainsMono) +body size → clideFontBody (15) +caption size → clideFontCaption (14) — status bar, section headers, git info +mono size → clideFontMono (14) — terminal, code, paths, IDs +``` + +Use `ClideText` for themed text. Set `muted: true` for secondary text +(resolves to `globalTextMuted`). Set `fontFamily: clideMonoFamily` for +code/paths/IDs. Don't set fontFamily for UI text — it inherits. + +## 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..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. + +## Where to look in the codebase + +- Full token list: `lib/kernel/src/theme/tokens.dart` +- Resolver fallbacks: `lib/kernel/src/theme/resolver.dart` +- Theme YAML example: `lib/kernel/src/theme/themes/clide.yaml` +- Decision: D-43 (handoff), D-44 (four bundled themes), D-45 (syntax tokens) diff --git a/.pql/pql-plan.json b/.pql/pql-plan.json index 6c4dc455..6d67b6df 100644 --- a/.pql/pql-plan.json +++ b/.pql/pql-plan.json @@ -1,5 +1,5 @@ { - "exported_at": "2026-05-06T13:21:08Z", + "exported_at": "2026-05-06T13:38:19Z", "decisions": [ { "id": "D-1", @@ -2073,11 +2073,11 @@ "type": "task", "parent_id": "T-3", "title": "secondary Claude pane UI wiring", - "status": "in_progress", + "status": "done", "priority": "medium", "decision_ref": "D-41", "created_at": "2026-04-22 14:08:40", - "updated_at": "2026-05-06 12:20:58" + "updated_at": "2026-05-06 13:21:15" }, { "id": "T-25", @@ -2691,6 +2691,24 @@ "priority": "medium", "created_at": "2026-05-06 10:16:02", "updated_at": "2026-05-06 12:17:27" + }, + { + "id": "T-85", + "type": "task", + "title": "consolidate theme-ui into ui-design skill with theme/surface/geometry/icons references", + "status": "in_progress", + "priority": "medium", + "created_at": "2026-05-06 13:28:58", + "updated_at": "2026-05-06 13:29:05" + }, + { + "id": "T-86", + "type": "task", + "title": "codify UI spacing constants (gaps, paddings, control sizes)", + "status": "backlog", + "priority": "medium", + "created_at": "2026-05-06 13:28:58", + "updated_at": "2026-05-06 13:28:58" } ], "ticket_deps": null, @@ -4065,6 +4083,20 @@ "old_value": "ready", "new_value": "in_progress", "changed_at": "2026-05-06 12:20:58" + }, + { + "ticket_id": "T-24", + "field": "status", + "old_value": "in_progress", + "new_value": "done", + "changed_at": "2026-05-06 13:21:15" + }, + { + "ticket_id": "T-85", + "field": "status", + "old_value": "backlog", + "new_value": "in_progress", + "changed_at": "2026-05-06 13:29:05" } ] } diff --git a/CHANGELOG.md b/CHANGELOG.md index 52ace974..9a322e29 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,6 +28,14 @@ heading, and (b) bumping `pubspec.yaml` `version:` in the same commit. type, supports pinned/non-closeable tabs (primary), drag-reorder, close × on hover, and an optional `+` add button. Used by the Claude pane to render primary + secondaries. +- `MultitabPane.keepAlive` mode — when set, all entry bodies stay + mounted via IndexedStack so switching tabs preserves their state + (PTY connections, scroll position, etc.). + +### Changed + +- Claude pane uses `MultitabPane` for primary + secondaries — drops + ~100 lines of bespoke tab-strip code, gains drag-to-reorder. ### Changed