diff --git a/pi-slate/CHANGELOG.md b/pi-slate/CHANGELOG.md index e5a95ea3..75c51567 100644 --- a/pi-slate/CHANGELOG.md +++ b/pi-slate/CHANGELOG.md @@ -2,6 +2,23 @@ ## Unreleased +### Added + +- New `/theme` picker: Light/Dark sections, 15 palettes per flavor, live preview, Enter saves, Escape restores, responsive two-column/stacked/tiny layouts. `/theme slate` stays an alias and `/slate theme` opens the same picker. +- The selector groups Catppuccin under **Light** (Latte, Frappé) and **Dark** (Macchiato, Mocha) sections; Tokyo Night and Black Metal sit under Dark. Frappé is mid-dark and is grouped under Light by choice. +- Selected rows in the picker use body text on the selection background, keeping every light palette above 3:1 instead of ~1.45:1 for accent-colored selections. +- Catppuccin Latte (light), Frappé, and Macchiato flavors alongside Mocha, all taken from the official palette (https://catppuccin.com/palette). +- Each flavor ships the same 15 palettes: Rosewater, Flamingo, Pink, Mauve, Red, Maroon, Peach, Yellow, Green, Teal, Sky, Sapphire, Blue, Lavender, and Default. Palette choices survive navigation within the selector. +- On Pi 1.4, cancelling after preview restores the original palette but automatic terminal-appearance following resumes only after `/reload` (public theme-instance API limitation). Saved theme and light/dark settings are untouched. + +### Changed + +- `edit` and `write` keep their diff preview while collapsed instead of collapsing to a `+N -M` counter. The one-line summary is replaced by the tool's own diff block (16 rows, split for edits at 100+ columns, expand for the rest); diffs still fold into the usual grouped tool runs. +- `/theme` is the only theme surface: `/slate theme` opens the picker and takes no arguments. `/slate theme ` and the old `/slate theme ` forms are now plain invalid usage instead of a second 60-name command tree. +- `/slate style` is removed along with the theme arguments; the picker is the only theme surface. Pink is labelled `Pink` instead of its full theme name, and saved full names such as `catppuccin-latte-mauve` remain valid through Pi's `/settings`. +- Mocha Quiet remains a saved theme and now shows as the last palette in the Mocha row. +- Effort ramps follow each flavor: dark flavors brighten toward the accent, Latte deepens toward an accent darkened into body text, so every ramp stays monotonic. + ### Fixed - Native MCP validation is used on newer Pi versions by default. Missing validation is a warning, not a configuration failure. diff --git a/pi-slate/README.md b/pi-slate/README.md index 8eb240e4..ca64aece 100644 --- a/pi-slate/README.md +++ b/pi-slate/README.md @@ -34,7 +34,7 @@ While the model runs, the composer edge shows a progress animation — `/slate p ### Diff -With `tool-cards` selected, `edit` and `write` results are syntax-highlighted in the transcript. +With `tool-cards` selected, `edit` and `write` results are syntax-highlighted in the transcript even while collapsed: the first 16 diff rows render inline (split view for edits when the row has 100+ columns), and expanding the tool shows the rest. Diffs keep folding into `Done (N tool calls)` groups like any other row. ![Split YAML edit with word-level emphasis](https://raw.githubusercontent.com/GaganSD/coding-agent-toolkit/1e84be304270c8d859e04e7005dc3cf674fbd574/pi-slate/assets/slate-diff-yaml.png) @@ -66,22 +66,23 @@ Other extensions can publish detached terminals or jobs to the sidebar's task sh ### Themes -Slate supports the following themes and can be changed with `/slate theme `: +Open `/theme` for a larger, centered theme selector with live preview (`/theme slate` remains an alias): -| Themes | Accent | -| --- | --- | -| `tokyo-night` | 🟦 | -| `black-metal` | ⬛ | -| `quiet` | ⬜ | -| `mauve` | 🟪 | -| `sapphire` | 🟦 | -| `peach` | 🟧 | -| `teal` | 🟩 | -| `default` | Pi's system theme | +- **↑ / ↓** selects and immediately applies a theme; **← / →** changes its palette. Dedicated theme and color-swatch lists show selection, position, and overflow counts; both scroll without changing the dialog's shape. +- Themes are grouped into **Light** and **Dark** sections. Light holds Catppuccin Latte and Frappé; Dark holds Macchiato, Mocha, Tokyo Night, and Black Metal. Frappé is a mid-dark palette shipped under Light by choice; its effort ramp behaves like the other dark flavors. +- Wide terminals use two columns; narrow terminals stack the lists, and very small screens keep the selected identity and essential controls. The dialog resizes to fit the terminal, not the selected theme or palette. +- Every Catppuccin flavor carries the same **15 palettes**: Rosewater, Flamingo, Pink, Mauve, Red, Maroon, Peach, Yellow, Green, Teal, Sky, Sapphire, Blue, Lavender, and Default (a neutral frame that reuses the flavor's lavender accent). Each flavor remembers its palette while the selector is open, and Mocha additionally lists the legacy Quiet palette. +- **Enter** confirms and saves; **Escape** (or **Ctrl+C**) restores the original visible theme without changing saved theme settings or Slate configuration. + +All colors come from the official Catppuccin palette (https://catppuccin.com/palette). Every bundled theme is generated from one registry, so `npm run themes` reproduces `themes/*.json` exactly. + +The selector requires interactive terminal mode. Pi 1.4 has no built-in `/theme` command; core theme selection remains in `/settings`. After previewing and cancelling, Pi's public theme-instance API restores the original palette but pauses automatic terminal-appearance following (including `system` and light/dark pairs) until `/reload`. Merely opening and cancelling without previewing does not affect appearance following. + +Saved names such as `catppuccin-latte-mauve` stay valid and can still be set directly through Pi's own `/settings`. -Black Metal is an optional bundled theme, not an install default: pick it with `/slate theme black-metal` or the `/slate theme` menu. Every selection is saved and also appears in `/settings`. +Black Metal is an optional bundled theme, not an install default: pick it from `/theme` under Dark. Confirmed selections are saved and also appear in `/settings`. -With `/slate effort-border on`, bundled themes use a restrained effort ramp from their neutral border toward their border accent: blue for Tokyo Night, the selected Catppuccin style, cool grays for Quiet, and grayscale for Black Metal. Higher effort increases intensity instead of switching to unrelated hues; the effort label remains the precise cue. The sidebar stays neutral, bash mode keeps its separate color, and external themes retain their own effort colors. These are native theme tokens, so Pi's stock editor also uses the ramp when a bundled Slate theme is selected. Effort-colored Slate frames remain off by default. +With `/slate effort-border on`, bundled themes use a restrained effort ramp from their neutral border toward their border accent: blue for Tokyo Night, the selected Catppuccin palette, cool grays for Quiet, and grayscale for Black Metal. Higher effort increases intensity instead of switching to unrelated hues; on light flavors the ramp deepens toward the accent instead of brightening. The effort label remains the precise cue. The sidebar stays neutral, bash mode keeps its separate color, and external themes retain their own effort colors. These are native theme tokens, so Pi's stock editor also uses the ramp when a bundled Slate theme is selected. Effort-colored Slate frames remain off by default. ### Prompt Templates @@ -125,7 +126,7 @@ These work when `editor` is selected. No terminal configuration. | Message length | `/slate message-length [default\|all\|]` | How many chat messages stay on screen. `default` is 100. | | Density | `/slate density [comfortable\|compact]` | Comfortable shows a › prompt in the composer; compact is tighter. | | Composer metadata | `/slate footer [standard\|minimal]` | Legacy command alias: standard shows model and thinking on the composer; minimal hides them. Not footer-slot ownership. | -| Theme | `/slate theme [default\|tokyo-night\|black-metal\|quiet\|mauve\|sapphire\|peach\|teal]` | Default restores Pi's system theme. Tokyo Night and Black Metal are explicit; other styles select Catppuccin; `/slate style` retains Catppuccin aliases. | +| Theme | `/theme` (or `/slate theme`) | Opens the Light/Dark picker: Latte and Frappé under Light, Macchiato, Mocha, Tokyo Night, and Black Metal under Dark. 15 palettes per Catppuccin flavor, live preview, no arguments. | | Bugs | `/slate bug [file\|open]` | Copy a bug report, or open the npm package page. | ## Independent Surfaces diff --git a/pi-slate/extensions/pi-slate/catppuccin.ts b/pi-slate/extensions/pi-slate/catppuccin.ts index 2555d3e0..c97de475 100644 --- a/pi-slate/extensions/pi-slate/catppuccin.ts +++ b/pi-slate/extensions/pi-slate/catppuccin.ts @@ -1,27 +1,55 @@ -import { effortPalette } from "./theme-palette.ts"; +import { effortPalette, mixHex } from "./theme-palette.ts"; -export const FLAVORS = ["mocha"] as const; -export const STYLES = ["default", "quiet", "mauve", "sapphire", "peach", "teal"] as const; +/** Official Catppuccin flavors (https://catppuccin.com/palette). */ +export const FLAVORS = ["latte", "frappe", "macchiato", "mocha"] as const; export type Flavor = (typeof FLAVORS)[number]; -export type Style = (typeof STYLES)[number]; export const FLAVOR_LABELS: Record = { + latte: "Latte", + frappe: "Frappé", + macchiato: "Macchiato", mocha: "Mocha", }; +/** Picker grouping; Frappé is mid-tone, but it sits with Latte under Light. */ +export const FLAVOR_SECTION: Record = { + latte: "Light", frappe: "Light", macchiato: "Dark", mocha: "Dark", +}; + +/** Every accent hue Catppuccin defines, plus the neutral Default style. */ +const ACCENT_STYLES = [ + "rosewater", "flamingo", "pink", "mauve", "red", "maroon", "peach", "yellow", + "green", "teal", "sky", "sapphire", "blue", "lavender", +] as const; + +export const STYLES = [...ACCENT_STYLES, "default", "quiet"] as const; + +export type Style = (typeof STYLES)[number]; + +export function stylesFor(flavor: Flavor): readonly Style[] { + return flavor === "mocha" ? STYLES : STYLES.filter((style) => style !== "quiet"); +} + export const STYLE_LABELS: Record = { - default: "Default", - quiet: "Quiet", + rosewater: "Rosewater", + flamingo: "Flamingo", + pink: "Pink", mauve: "Mauve", - sapphire: "Sapphire", + red: "Red", + maroon: "Maroon", peach: "Peach", + yellow: "Yellow", + green: "Green", teal: "Teal", + sky: "Sky", + sapphire: "Sapphire", + blue: "Blue", + lavender: "Lavender", + default: "Default", + quiet: "Quiet", }; -export const DEFAULT_FLAVOR: Flavor = "mocha"; -export const DEFAULT_STYLE: Style = "mauve"; - type Swatch = | "rosewater" | "flamingo" | "pink" | "mauve" | "red" | "maroon" | "peach" | "yellow" | "green" | "teal" | "sky" | "sapphire" | "blue" | "lavender" @@ -31,6 +59,33 @@ type Swatch = type Palette = Record; const PALETTES: Record = { + latte: { + rosewater: "#dc8a78", flamingo: "#dd7878", pink: "#ea76cb", mauve: "#8839ef", + red: "#d20f39", maroon: "#e64553", peach: "#fe640b", yellow: "#df8e1d", + green: "#40a02b", teal: "#179299", sky: "#04a5e5", sapphire: "#209fb5", + blue: "#1e66f5", lavender: "#7287fd", text: "#4c4f69", subtext1: "#5c5f77", + subtext0: "#6c6f85", overlay2: "#7c7f93", overlay1: "#8c8fa1", overlay0: "#9ca0b0", + surface2: "#acb0be", surface1: "#bcc0cc", surface0: "#ccd0da", + base: "#eff1f5", mantle: "#e6e9ef", crust: "#dce0e8", + }, + frappe: { + rosewater: "#f2d5cf", flamingo: "#eebebe", pink: "#f4b8e4", mauve: "#ca9ee6", + red: "#e78284", maroon: "#ea999c", peach: "#ef9f76", yellow: "#e5c890", + green: "#a6d189", teal: "#81c8be", sky: "#99d1db", sapphire: "#85c1dc", + blue: "#8caaee", lavender: "#babbf1", text: "#c6d0f5", subtext1: "#b5bfe2", + subtext0: "#a5adce", overlay2: "#949cbb", overlay1: "#838ba7", overlay0: "#737994", + surface2: "#626880", surface1: "#51576d", surface0: "#414559", + base: "#303446", mantle: "#292c3c", crust: "#232634", + }, + macchiato: { + rosewater: "#f4dbd6", flamingo: "#f0c6c6", pink: "#f5bde6", mauve: "#c6a0f6", + red: "#ed8796", maroon: "#ee99a0", peach: "#f5a97f", yellow: "#eed49f", + green: "#a6da95", teal: "#8bd5ca", sky: "#91d7e3", sapphire: "#7dc4e4", + blue: "#8aadf4", lavender: "#b7bdf8", text: "#cad3f5", subtext1: "#b8c0e0", + subtext0: "#a5adcb", overlay2: "#939ab7", overlay1: "#8087a2", overlay0: "#6e738d", + surface2: "#5b6078", surface1: "#494d64", surface0: "#363a4f", + base: "#24273a", mantle: "#1e2030", crust: "#181926", + }, mocha: { rosewater: "#f5e0dc", flamingo: "#f2cdcd", pink: "#f5c2e7", mauve: "#cba6f7", red: "#f38ba8", maroon: "#eba0ac", peach: "#fab387", yellow: "#f9e2af", @@ -42,54 +97,15 @@ const PALETTES: Record = { }, }; -const STYLE_ROLES: Record = { +type StyleRoles = { accent: Swatch; border: Swatch; heading: Swatch }; + +const STYLE_ROLES: Record = { + ...Object.fromEntries(ACCENT_STYLES.map((style) => [style, { + accent: style, border: "overlay0", heading: style, + } satisfies StyleRoles])), default: { accent: "lavender", border: "overlay0", heading: "text" }, quiet: { accent: "subtext1", border: "surface2", heading: "subtext1" }, - mauve: { accent: "mauve", border: "overlay0", heading: "mauve" }, - sapphire: { accent: "sapphire", border: "overlay0", heading: "sapphire" }, - peach: { accent: "peach", border: "overlay0", heading: "peach" }, - teal: { accent: "teal", border: "overlay0", heading: "teal" }, -}; - -export function catppuccinThemeName(flavor: Flavor, style: Style): string { - return `catppuccin-${flavor}-${style}`; -} - -export function parseFlavor(raw: string): Flavor | undefined { - const value = raw.trim().toLowerCase().normalize("NFD").replace(/\p{M}/gu, ""); - return FLAVORS.find((flavor) => flavor === value); -} - -export function parseStyle(raw: string): Style | undefined { - const value = raw.trim().toLowerCase(); - if (value === "canonical") return "default"; - return STYLES.find((style) => style === value); -} - -export function parseCatppuccinTheme(name: string | undefined): { flavor: Flavor; style: Style } | undefined { - if (!name) return undefined; - const match = /^catppuccin-([a-z]+)-([a-z]+)$/.exec(name); - if (!match) return undefined; - const flavor = parseFlavor(match[1]); - const style = parseStyle(match[2]); - if (!flavor || !style) return undefined; - return { flavor, style }; -} - -export function resolveCatppuccinTheme( - name: string | undefined, - flavor?: Flavor, - style?: Style, -): { flavor: Flavor; style: Style; name: string } { - const current = parseCatppuccinTheme(name); - const nextFlavor = flavor ?? current?.flavor ?? DEFAULT_FLAVOR; - const nextStyle = style ?? current?.style ?? DEFAULT_STYLE; - return { flavor: nextFlavor, style: nextStyle, name: catppuccinThemeName(nextFlavor, nextStyle) }; -} - -export function themeMessage(flavor: Flavor, style: Style): string { - return `Theme set to ${FLAVOR_LABELS[flavor]} · ${STYLE_LABELS[style]}`; -} +} as Record; export const THEME_SCHEMA = "https://raw.githubusercontent.com/earendil-works/pi/main/packages/coding-agent/src/modes/interactive/theme/theme-schema.json"; @@ -101,10 +117,18 @@ export type CatppuccinThemeJson = { export: { pageBg: string; cardBg: string; infoBg: string }; }; +export function catppuccinThemeName(flavor: Flavor, style: Style): string { + return `catppuccin-${flavor}-${style}`; +} + export function buildCatppuccinTheme(flavor: Flavor, style: Style): CatppuccinThemeJson { const palette = PALETTES[flavor]; const roles = STYLE_ROLES[style]; - const effort = effortPalette(palette[roles.border], palette[roles.accent]); + // Latte's accents sit within a few percent of its neutral border, so a straight + // blend reverses; darkening the target toward body text keeps it monotonic. + const accent = palette[roles.accent]; + const target = flavor === "latte" ? mixHex(accent, palette.text, 0.5) : accent; + const effort = effortPalette(palette[roles.border], target); return { $schema: THEME_SCHEMA, name: catppuccinThemeName(flavor, style), @@ -178,5 +202,5 @@ export function buildCatppuccinTheme(flavor: Flavor, style: Style): CatppuccinTh } export function allCatppuccinThemes(): CatppuccinThemeJson[] { - return FLAVORS.flatMap((flavor) => STYLES.map((style) => buildCatppuccinTheme(flavor, style))); + return FLAVORS.flatMap((flavor) => stylesFor(flavor).map((style) => buildCatppuccinTheme(flavor, style))); } diff --git a/pi-slate/extensions/pi-slate/index.ts b/pi-slate/extensions/pi-slate/index.ts index cf7c2001..e3a3500b 100644 --- a/pi-slate/extensions/pi-slate/index.ts +++ b/pi-slate/extensions/pi-slate/index.ts @@ -80,15 +80,8 @@ import { } from "./bug.ts"; import { findChatContainer, layoutRootOf, syncMessageWindow, type MessageWindow } from "./message-window.ts"; import { installNewSessionNoticeFilter } from "./stock-notices.ts"; -import { - STYLE_LABELS, - STYLES, - resolveCatppuccinTheme, - themeMessage, - type Flavor, - type Style, -} from "./catppuccin.ts"; -import { persistTheme, SLATE_NATIVE_THEME, SLATE_THEME } from "./install-defaults.ts"; +import { persistTheme, SLATE_NATIVE_THEME } from "./install-defaults.ts"; +import { selectSlateTheme } from "./theme-selector.ts"; import { loadShortcuts, promptShortcutKey, type SlateShortcuts } from "./shortcuts.ts"; const PROGRESS_LABELS: Record = { @@ -862,51 +855,19 @@ export default function piSlate(pi: ExtensionAPI): void { return undefined; }; - const currentCatppuccin = (ctx: ExtensionContext) => resolveCatppuccinTheme(ctx.ui.theme.name); - - const pickStyle = async (ctx: ExtensionContext): Promise