add Catppuccin Mocha theme (+ high-contrast sibling)

Ships catppuccin-mocha (faithful to the official Catppuccin Mocha
palette) and catppuccin-mocha-hc (high-contrast sibling per D-69 — the
faithful palette is never retuned to pass the gate). Both bundled and
gated: mocha clears baseline AA, mocha-hc clears the strict extended
set. Catppuccin MIT palette recorded in licenses.yaml.

Closes T-82 (Mocha + hc meets every acceptance bullet; faithful Latte
deferred — it can't clear baseline AA without retuning, a separate call).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-03 16:51:06 +02:00
co-authored by Claude Opus 4.8
parent 4c77f32ad2
commit 884207c59e
8 changed files with 156 additions and 0 deletions
@@ -112,3 +112,7 @@ INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by,
Resolved (2026-06-03): NO new mechanism or decision needed -- the contract already existed. D-74''s CommandSchema.normalize (lib/src/ipc/command_schema.dart) already folds the argv shape {positional,flags} into named args using a declared positional ordering, and the dispatcher already runs normalize+validate. The arg-taking commands simply never registered a schema (adoption is opt-in per D-74). Fix = adopt it: added positional schemas (non-required, so missing-arg errors stay as handlers produce them; only effect is positional->named mapping + numeric coercion of line/cols/rows) to editor.open/activate/read/save/close, files.read/ls, pane.close/focus/resize/write. Handlers unchanged. Tests: CLI-shape ({positional:[...]}) dispatch now binds (editor/pane/files command tests). DEFERRED (still named-arg only; in-process UI works, CLI-arg niche): editor.insert/replace-selection/set-selection/set-content (content/selection encoding + active-buffer fallback make positional ambiguous), pane.spawn (argv is a list, not scalar positional), git.* arg verbs (agents use plain git; D-83 external-agent scope). NOTE: takes effect on app RESTART, not hot reload -- the dispatcher is built once at boot.', NULL, '2026-06-03 13:41:38', '2026-06-03 13:41:38', '2026-06-03 13:41:38', NULL, '0655a8efb4c977b404daedd86d926761', 1) ON CONFLICT(hash) DO NOTHING;
INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-232', 'status', 'in_progress', 'done', NULL, '2026-06-03 13:41:42', '2026-06-03 13:41:42', '2026-06-03 13:41:42', NULL, '17995cc58fd4260f4901357531eeafc4', 1) ON CONFLICT(hash) DO NOTHING;
INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-234', 'status', 'backlog', 'ready', NULL, '2026-06-03 14:25:47', '2026-06-03 14:25:47', '2026-06-03 14:25:47', NULL, 'e8216367ab4337bdbaa8fc97c596e111', 1) ON CONFLICT(hash) DO NOTHING;
INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-234', 'status', 'ready', 'in_progress', NULL, '2026-06-03 14:27:26', '2026-06-03 14:27:26', '2026-06-03 14:27:26', NULL, '3db43a5f8b3855b2a78b4f6294dfeb97', 1) ON CONFLICT(hash) DO NOTHING;
INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-82', 'status', 'ready', 'in_progress', NULL, '2026-06-03 14:27:26', '2026-06-03 14:27:26', '2026-06-03 14:27:26', NULL, '7a7042a498a0f09a072d911fad8d33d7', 1) ON CONFLICT(hash) DO NOTHING;
INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-230', 'status', 'ready', 'in_progress', NULL, '2026-06-03 14:27:26', '2026-06-03 14:27:26', '2026-06-03 14:27:26', NULL, 'ec0e54053e51dea1fecb48eebc69e387', 1) ON CONFLICT(hash) DO NOTHING;
+55
View File
@@ -303,3 +303,58 @@ Acceptance:
3. Selecting a theme applies it live (ThemeController.select) and closes the popout.
4. Esc / outside-click dismisses without changing the theme; the control is keyboard-operable.
5. theme.pick (palette/command) still works unchanged.', 'backlog', 'medium', NULL, NULL, NULL, '2026-06-03 13:49:44', '2026-06-03 13:49:44', NULL, '301e958c679661deb77d1e3ca5d45d72', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-230', 'story', 'T-132', 'Cluster meta messages into a collapsible activity card', 'The Claude pane renders every transcript item as its own row, so a heavy agent turn becomes a wall of tool-call/result rows (Bash <cmd> / Bash · result {…} / ''completed with no output'') that buries the messages that matter (user + Claude prose). Wireframe: docs/design/wireframes/claude/meta-activity-card.png.
Fold runs of consecutive ''meta'' items into one live, collapsible activity card:
- The card shows the MOST RECENT meta line as a live ticker and updates in place as new ones stream in — a running ''what''s happening now''. A step count (''14 steps'') sits on the right.
- Collapsed by default; click the chevron to expand the full list of folded steps, click to re-collapse.
- ''Sticky'' items never fold — they render first-class. A sticky item SEALS the current card and a new cluster begins after it. A cluster = one unbroken run of foldable items between two sticky items.
Taxonomy over the sealed ConversationItem set (transcript_reader.dart): UserMessage, AssistantTextMessage, AssistantThinkingMessage, AssistantToolUse, ToolResultMessage.
- Always sticky: UserMessage, AssistantTextMessage. Permission/AskUserQuestion prompts already live in the interaction zone (D-78) and are inherently sticky / cluster-breaking — no change, but note they break the run too.
- Foldable: AssistantToolUse + its paired ToolResultMessage (pairing already exists via toolUseById, T-168).
Refinement decisions (2026-06-03, user):
1. WHAT FOLDS is a 3-level setting (conservative → aggressive); DEFAULT = level 1. Build the foldable predicate so all three are switchable:
- L1 (default): fold tool calls + results only; diffs (Edit/Write results) AND AssistantThinkingMessage stay first-class/sticky.
- L2: also fold thinking; diffs stay sticky.
- L3: fold everything except UserMessage + AssistantTextMessage (incl. diffs + thinking).
2. ERRORS: a failed/error ToolResultMessage SURFACES — treated as sticky, stays visible and breaks the cluster (see wireframe state B). Start here; the user noted some non-fatal errors are themselves clutter, so expect to tune which errors surface vs. fold.
3. DEFAULT/RUN STATE: always collapsed, showing the last line + step count, with the ticker updating live. NO setting. (Rejected auto-expanding the in-flight cluster: it scrolls the last useful sticky message out of view during long runs and only collapses when done — worst timing. Collapsed-with-live-ticker keeps the last sticky message anchored while preserving the motion/dynamism.)
Implementation home: a ''collapse adjacent foldable items'' grouping pass over ConversationController.items, consumed by ConversationView; the card is a new conversation_card variant. Keep it accessible (keyboard expand/collapse + semantics: announce step count and collapsed/expanded state).
Acceptance:
1. Consecutive foldable items collapse into one card; the collapsed card shows the latest line + a live-updating step count.
2. A sticky message (user / Claude prose / surfaced error / interaction-zone prompt) seals the card and starts a new cluster after it.
3. Expanding shows every folded step (tool call + result pairs) in order; collapsing returns to the one-line view.
4. The foldable level is a setting (default L1: diffs + thinking stay first-class); switching levels re-groups the transcript.
5. Failed tool results surface as their own (sticky) row and break the cluster.
6. Keyboard + screen-reader accessible (expand/collapse, step count announced).', 'in_progress', 'medium', NULL, NULL, NULL, '2026-06-03 11:15:39', '2026-06-03 14:27:26', NULL, '3eb3d504dad69cc0c06e57bce8f10456', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-82', 'task', NULL, 'add Catppuccin theme to bundled themes', 'User request: ship a Catppuccin theme alongside the four bundled themes (clide, midnight, paper, terminal — D-44).
**Acceptance:**
- New theme YAML at `lib/kernel/src/theme/themes/catppuccin.yaml` (or split for the four Catppuccin flavours: latte / frappe / macchiato / mocha — pick one or all four; recommend Mocha as the dark default and Latte for the light variant).
- Tokens populated for every key in `SurfaceTokens` (lib/kernel/src/theme/tokens.dart) — chrome, panels, tabs, list items, status colours, syntax tokens (D-45).
- WCAG-AA contrast gate (D-22) passes against the theme.
- Listed in `assets/licenses.yaml` with the upstream license (MIT) and attribution.
- Theme picker (`builtin.theme-picker`) shows it in the live-switch menu.
**Source palettes:** https://github.com/catppuccin/catppuccin#-palette — official hex values, don`t paraphrase.
**Why now:** test user request; also a good exercise for any future user-contributed theme since Catppuccin is the most common one to ask for.', 'in_progress', 'low', NULL, NULL, NULL, '2026-05-06 08:30:15', '2026-06-03 14:27:26', NULL, '4cb876eb35abc00ee93eb0bced3315da', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-234', 'story', NULL, 'Theme-switcher popout in the status bar', 'Add a theme switcher to the far-right corner of the bottom status bar (the empty area to the right of the ''application ok'' / ipc-status item — see the user''s screenshot). Today switching themes is only reachable via the theme.pick command / palette (ThemePickerExtension opens a modal picker_view); there''s no always-visible affordance.
Add a compact status-bar control that opens a POPOUT (popover anchored to the control, not the full-screen modal) for quick theme switching:
- A small right-aligned status item — a palette/swatch icon (or the current theme name). Contribute via StatusItemContribution at Slots.statusbar with priority > 100 so it sits in the right group, to the right of the ipc/toolchain ''application ok'' item (priority 100).
- Click (or focus + Enter/Space) opens a popover listing ThemeController.available, the current (ThemeController.currentName) marked.
- Selecting an entry applies it immediately via ThemeController.select(name) and closes the popout.
- Esc / click-away dismisses with no change. Keyboard navigable (arrow + Enter), matching the palette/quick-open overlay pattern.
- Natural home: ThemePickerExtension (lib/builtin/theme_picker). Reuse the theme list + select logic from picker_view rather than duplicating; the modal theme.pick command stays as-is for parity (D-6).
Acceptance:
1. A theme control sits in the far-right of the status bar.
2. Clicking/activating it opens a popover of all available themes with the current one marked.
3. Selecting a theme applies it live (ThemeController.select) and closes the popout.
4. Esc / outside-click dismisses without changing the theme; the control is keyboard-operable.
5. theme.pick (palette/command) still works unchanged.', 'in_progress', 'medium', NULL, NULL, NULL, '2026-06-03 13:49:44', '2026-06-03 14:27:26', NULL, '9a5ac18d9e54e009f912b9c54584d796', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
+3
View File
@@ -18,6 +18,9 @@ heading, and (b) bumping `pubspec.yaml` `version:` in the same commit.
### Added
- Catppuccin Mocha theme, plus a high-contrast `catppuccin-mocha-hc` sibling,
added to the bundled themes — switchable from the theme picker. Faithful to
the official palette (D-69). (T-82)
- `clide ui open <reader> <id|path>` opens a doc in a GUI reader from the CLI —
`tickets`/`decisions` by id, `markdown` by path — so an agent can surface what
it's looking at on your screen. The drive-half complement to `clide status`. (T-231)
+11
View File
@@ -81,6 +81,17 @@ dependencies:
Fill weights bundled.
weights_bundled: [Regular, Bold, Fill]
- name: Catppuccin (palette)
kind: theme-palette
version: "mocha"
homepage: https://github.com/catppuccin/catppuccin
license: MIT
purpose: >-
Source palette for the bundled catppuccin-mocha theme and its
high-contrast sibling catppuccin-mocha-hc. Official hex values,
mapped to clide's SurfaceTokens (Mauve accent, Blue primary); the
faithful palette is not retuned (D-69).
- name: yaml
kind: dart-package
version: "3.1.3"
@@ -0,0 +1,40 @@
# catppuccin-mocha-hc — high-contrast sibling of `catppuccin-mocha`
#
# Same Catppuccin Mocha silhouette; muted text, status chips, syntax
# tokens, and the focus border bumped to clear the strict contrast gate
# (D-22 + D-69, `extendedPairs`). The faithful palette lives in
# catppuccin-mocha.yaml and is never retuned. License: MIT.
name: catppuccin-mocha-hc
display_name: Catppuccin Mocha (high contrast)
dark: true
palette:
background: "#1e1e2e" # Base
panel: "#181825" # Mantle
surface: "#313244" # Surface0
muted: "#bac2de" # Subtext1 (brightened for AA)
foreground: "#eff1f5" # near-white
secondary: "#cdd6f4" # Text
primary: "#a6c8ff" # brightened Blue
accent: "#d4bbfb" # brightened Mauve
success: "#b9ecb4" # brightened Green
warning: "#f9e2af" # Yellow
error: "#ffa3bd" # brightened Red
info: "#9fe1ee" # brightened Sky
onAccent: "#11111b" # Crust
surfaceHi: "#45475a" # Surface1
border: "#313244" # Surface0
borderHi: "#9399b2" # Overlay2 (visible focus border)
textDim: "#bac2de" # Subtext1
textMute: "#9399b2" # Overlay2
accentSoft: "#21cba6f7" # Mauve, low alpha
syntax:
keyword: "#cba6f7" # Mauve
type: "#f9e2af" # Yellow
string: "#a6e3a1" # Green
number: "#fab387" # Peach
comment: "#a6adc8" # Subtext0 (brightened for AA on the panel)
method: "#89b4fa" # Blue
punct: "#9399b2" # Overlay2
@@ -0,0 +1,39 @@
# catppuccin-mocha — the Catppuccin Mocha (dark) flavour
# Source palette: https://github.com/catppuccin/catppuccin (official hex).
# Mapping: Base/Mantle/Surface0/1/2 → chrome; Text/Subtext → foreground;
# Blue=primary, Mauve=accent (the Catppuccin signature). License: MIT,
# see assets/licenses.yaml.
name: catppuccin-mocha
display_name: Catppuccin Mocha
dark: true
palette:
background: "#1e1e2e" # Base
panel: "#181825" # Mantle
surface: "#313244" # Surface0
muted: "#9399b2" # Overlay2
foreground: "#cdd6f4" # Text
secondary: "#bac2de" # Subtext1
primary: "#89b4fa" # Blue
accent: "#cba6f7" # Mauve
success: "#a6e3a1" # Green
warning: "#f9e2af" # Yellow
error: "#f38ba8" # Red
info: "#89dceb" # Sky
onAccent: "#11111b" # Crust (dark text on the light accent)
surfaceHi: "#45475a" # Surface1
border: "#313244" # Surface0
borderHi: "#45475a" # Surface1
textDim: "#a6adc8" # Subtext0
textMute: "#7f849c" # Overlay1
accentSoft: "#21cba6f7" # Mauve, low alpha
syntax:
keyword: "#cba6f7" # Mauve
type: "#f9e2af" # Yellow
string: "#a6e3a1" # Green
number: "#fab387" # Peach
comment: "#7f849c" # Overlay1
method: "#89b4fa" # Blue
punct: "#9399b2" # Overlay2
+2
View File
@@ -425,6 +425,8 @@ Future<List<ThemeDefinition>> _loadBundledThemes() async {
'lib/kernel/src/theme/themes/midnight-hc.yaml',
'lib/kernel/src/theme/themes/paper-hc.yaml',
'lib/kernel/src/theme/themes/terminal-hc.yaml',
'lib/kernel/src/theme/themes/catppuccin-mocha.yaml',
'lib/kernel/src/theme/themes/catppuccin-mocha-hc.yaml',
];
final out = <ThemeDefinition>[];
for (final p in paths) {
+2
View File
@@ -23,6 +23,8 @@ void main() {
'lib/kernel/src/theme/themes/midnight-hc.yaml',
'lib/kernel/src/theme/themes/paper-hc.yaml',
'lib/kernel/src/theme/themes/terminal-hc.yaml',
'lib/kernel/src/theme/themes/catppuccin-mocha.yaml',
'lib/kernel/src/theme/themes/catppuccin-mocha-hc.yaml',
];
for (final path in bundledPaths) {