Skip to content

Commit ab7868b

Browse files
authored
feat(ui): add Catppuccin Frappé and Macchiato themes (#395)
1 parent e9b0d58 commit ab7868b

12 files changed

Lines changed: 145 additions & 22 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ All notable user-visible changes to Hunk are documented in this file.
77
### Added
88

99
- Show the newly selected theme in the footer status bar when switching themes.
10+
- Added Catppuccin Frappé and Macchiato as built-in themes, completing the four official Catppuccin flavors.
1011
- Added a Zenburn built-in theme (`theme = "zenburn"`), a warm low-contrast dark palette inspired by Jani Nurminen's original Zenburn. It also works as a custom-theme `base`.
1112

1213
### Changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ You can persist preferences to a config file:
119119
Example:
120120

121121
```toml
122-
theme = "graphite" # graphite, midnight, paper, ember, catppuccin-latte, catppuccin-mocha, zenburn, custom
122+
theme = "graphite" # graphite, midnight, paper, ember, catppuccin-latte, catppuccin-frappe, catppuccin-macchiato, catppuccin-mocha, zenburn, custom
123123
mode = "auto" # auto, split, stack
124124
vcs = "git" # git, jj
125125
watch = false
@@ -137,7 +137,7 @@ Custom themes can inherit from any built-in base theme and override only the col
137137
theme = "custom"
138138

139139
[custom_theme]
140-
base = "graphite" # graphite, midnight, paper, ember, catppuccin-latte, catppuccin-mocha, zenburn
140+
base = "graphite" # graphite, midnight, paper, ember, catppuccin-latte, catppuccin-frappe, catppuccin-macchiato, catppuccin-mocha, zenburn
141141
label = "My Theme"
142142
accent = "#7fd1ff"
143143
panel = "#10161d"

docs/opentui-component.md

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -190,19 +190,19 @@ If you need direct access to Pierre's parser, `parsePatchFiles(...)` is still re
190190

191191
## Common props
192192

193-
| Prop | Type | Default | Notes |
194-
| -------------------- | --------------------------------------------------------------------------------------------------------- | ------------ | ----------------------------------------------------------------------------------- |
195-
| `layout` | `"split" \| "stack"` | `"split"` | Chooses side-by-side or stacked rendering. |
196-
| `width` | `number` || Required content width in terminal columns. |
197-
| `theme` | `"graphite" \| "midnight" \| "paper" \| "ember" \| "catppuccin-latte" \| "catppuccin-mocha" \| "zenburn"` | `"graphite"` | Matches Hunk's built-in themes. |
198-
| `showLineNumbers` | `boolean` | `true` | Toggles line-number columns. |
199-
| `showHunkHeaders` | `boolean` | `true` | Toggles `@@ ... @@` hunk header rows. |
200-
| `showFileSeparators` | `boolean` | `true` | Toggles separator rows between files in `HunkReviewStream`. |
201-
| `wrapLines` | `boolean` | `false` | Wraps long lines instead of clipping horizontally. |
202-
| `horizontalOffset` | `number` | `0` | Scroll offset for non-wrapped code rows. |
203-
| `highlight` | `boolean` | `true` | Enables syntax highlighting. |
204-
| `selectedHunkIndex` | `number` | `0` | Highlights one hunk as the active target for single-file components. |
205-
| `scrollable` | `boolean` | `true` | `HunkDiffView` only; primitives should be wrapped in OpenTUI scrollbox when needed. |
193+
| Prop | Type | Default | Notes |
194+
| -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------ | ----------------------------------------------------------------------------------- |
195+
| `layout` | `"split" \| "stack"` | `"split"` | Chooses side-by-side or stacked rendering. |
196+
| `width` | `number` || Required content width in terminal columns. |
197+
| `theme` | `"graphite" \| "midnight" \| "paper" \| "ember" \| "catppuccin-latte" \| "catppuccin-frappe" \| "catppuccin-macchiato" \| "catppuccin-mocha" \| "zenburn"` | `"graphite"` | Matches Hunk's built-in themes. |
198+
| `showLineNumbers` | `boolean` | `true` | Toggles line-number columns. |
199+
| `showHunkHeaders` | `boolean` | `true` | Toggles `@@ ... @@` hunk header rows. |
200+
| `showFileSeparators` | `boolean` | `true` | Toggles separator rows between files in `HunkReviewStream`. |
201+
| `wrapLines` | `boolean` | `false` | Wraps long lines instead of clipping horizontally. |
202+
| `horizontalOffset` | `number` | `0` | Scroll offset for non-wrapped code rows. |
203+
| `highlight` | `boolean` | `true` | Enables syntax highlighting. |
204+
| `selectedHunkIndex` | `number` | `0` | Highlights one hunk as the active target for single-file components. |
205+
| `scrollable` | `boolean` | `true` | `HunkDiffView` only; primitives should be wrapped in OpenTUI scrollbox when needed. |
206206

207207
## Other exports
208208

src/core/config.test.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,8 @@ describe("config resolution", () => {
150150
"paper",
151151
"ember",
152152
"catppuccin-latte",
153+
"catppuccin-frappe",
154+
"catppuccin-macchiato",
153155
"catppuccin-mocha",
154156
"zenburn",
155157
])("accepts custom theme base id: %s", (base) => {
@@ -182,7 +184,7 @@ describe("config resolution", () => {
182184
env: { HOME: home },
183185
}),
184186
).toThrow(
185-
"Expected custom_theme.base to be one of: graphite, midnight, paper, ember, catppuccin-latte, catppuccin-mocha, zenburn.",
187+
"Expected custom_theme.base to be one of: graphite, midnight, paper, ember, catppuccin-latte, catppuccin-frappe, catppuccin-macchiato, catppuccin-mocha, zenburn.",
186188
);
187189
});
188190

src/core/config.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ const BUILT_IN_THEME_IDS = [
1818
"paper",
1919
"ember",
2020
"catppuccin-latte",
21+
"catppuccin-frappe",
22+
"catppuccin-macchiato",
2123
"catppuccin-mocha",
2224
"zenburn",
2325
] as const;

src/opentui/HunkDiffView.test.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,8 @@ describe("OpenTUI public components", () => {
161161
"paper",
162162
"ember",
163163
"catppuccin-latte",
164+
"catppuccin-frappe",
165+
"catppuccin-macchiato",
164166
"catppuccin-mocha",
165167
"zenburn",
166168
]);

src/opentui/themes.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ export const HUNK_DIFF_THEME_NAMES = [
44
"paper",
55
"ember",
66
"catppuccin-latte",
7+
"catppuccin-frappe",
8+
"catppuccin-macchiato",
79
"catppuccin-mocha",
810
"zenburn",
911
] as const;

src/ui/diff/pierre.test.ts

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -389,7 +389,14 @@ describe("Pierre diff rows", () => {
389389
test("remaps Pierre markdown reds and greens away from diff-semantic hues", async () => {
390390
const file = createMarkdownDiffFile();
391391

392-
for (const themeId of ["midnight", "paper", "catppuccin-latte", "catppuccin-mocha"] as const) {
392+
for (const themeId of [
393+
"midnight",
394+
"paper",
395+
"catppuccin-latte",
396+
"catppuccin-frappe",
397+
"catppuccin-macchiato",
398+
"catppuccin-mocha",
399+
] as const) {
393400
const theme = resolveTheme(themeId, null);
394401
const highlighted = await loadHighlightedDiff(file, theme.appearance);
395402
const rows = buildStackRows(file, highlighted, theme).filter(
@@ -515,7 +522,14 @@ describe("Pierre diff rows", () => {
515522
const file = createMarkdownDiffFile();
516523
const highlighted = await loadHighlightedDiff(file, "dark");
517524

518-
for (const themeId of ["graphite", "midnight", "ember", "catppuccin-mocha"] as const) {
525+
for (const themeId of [
526+
"graphite",
527+
"midnight",
528+
"ember",
529+
"catppuccin-frappe",
530+
"catppuccin-macchiato",
531+
"catppuccin-mocha",
532+
] as const) {
519533
const theme = resolveTheme(themeId, null);
520534
const rows = buildStackRows(file, highlighted, theme).filter(
521535
(row): row is Extract<DiffRow, { type: "stack-line" }> =>

src/ui/lib/ui-lib.test.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,8 @@ describe("ui helpers", () => {
201201
"Paper",
202202
"Ember",
203203
"Catppuccin Latte",
204+
"Catppuccin Frappé",
205+
"Catppuccin Macchiato",
204206
"Catppuccin Mocha",
205207
"Zenburn",
206208
]);
@@ -256,6 +258,8 @@ describe("ui helpers", () => {
256258
"Paper",
257259
"Ember",
258260
"Catppuccin Latte",
261+
"Catppuccin Frappé",
262+
"Catppuccin Macchiato",
259263
"Catppuccin Mocha",
260264
"Zenburn",
261265
"My Theme",
@@ -496,6 +500,8 @@ describe("ui helpers", () => {
496500
expect(resolveTheme("ember", null).syntaxStyle).toBeDefined();
497501
expect(custom.syntaxStyle).toBeDefined();
498502
expect(resolveTheme("catppuccin-latte", null).syntaxStyle).toBeDefined();
503+
expect(resolveTheme("catppuccin-frappe", null).syntaxStyle).toBeDefined();
504+
expect(resolveTheme("catppuccin-macchiato", null).syntaxStyle).toBeDefined();
499505
expect(resolveTheme("catppuccin-mocha", null).syntaxStyle).toBeDefined();
500506
});
501507
});

src/ui/themes.test.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,21 @@ import { blendHex, hexColorDistance } from "./lib/color";
33
import { CATPPUCCIN_PALETTES, resolveTheme } from "./themes";
44

55
describe("themes", () => {
6-
test("resolves Catppuccin Latte and Mocha by theme id", () => {
6+
test("resolves all Catppuccin flavors by theme id", () => {
77
const latte = resolveTheme("catppuccin-latte", null);
8+
const frappe = resolveTheme("catppuccin-frappe", null);
9+
const macchiato = resolveTheme("catppuccin-macchiato", null);
810
const mocha = resolveTheme("catppuccin-mocha", null);
911

1012
expect(latte.id).toBe("catppuccin-latte");
1113
expect(latte.label).toBe("Catppuccin Latte");
1214
expect(latte.appearance).toBe("light");
15+
expect(frappe.id).toBe("catppuccin-frappe");
16+
expect(frappe.label).toBe("Catppuccin Frappé");
17+
expect(frappe.appearance).toBe("dark");
18+
expect(macchiato.id).toBe("catppuccin-macchiato");
19+
expect(macchiato.label).toBe("Catppuccin Macchiato");
20+
expect(macchiato.appearance).toBe("dark");
1321
expect(mocha.id).toBe("catppuccin-mocha");
1422
expect(mocha.label).toBe("Catppuccin Mocha");
1523
expect(mocha.appearance).toBe("dark");
@@ -20,6 +28,14 @@ describe("themes", () => {
2028
expect(CATPPUCCIN_PALETTES.latte.mauve).toBe("#8839ef");
2129
expect(CATPPUCCIN_PALETTES.latte.green).toBe("#40a02b");
2230
expect(CATPPUCCIN_PALETTES.latte.red).toBe("#d20f39");
31+
expect(CATPPUCCIN_PALETTES.frappe.base).toBe("#303446");
32+
expect(CATPPUCCIN_PALETTES.frappe.mauve).toBe("#ca9ee6");
33+
expect(CATPPUCCIN_PALETTES.frappe.green).toBe("#a6d189");
34+
expect(CATPPUCCIN_PALETTES.frappe.red).toBe("#e78284");
35+
expect(CATPPUCCIN_PALETTES.macchiato.base).toBe("#24273a");
36+
expect(CATPPUCCIN_PALETTES.macchiato.mauve).toBe("#c6a0f6");
37+
expect(CATPPUCCIN_PALETTES.macchiato.green).toBe("#a6da95");
38+
expect(CATPPUCCIN_PALETTES.macchiato.red).toBe("#ed8796");
2339
expect(CATPPUCCIN_PALETTES.mocha.base).toBe("#1e1e2e");
2440
expect(CATPPUCCIN_PALETTES.mocha.mauve).toBe("#cba6f7");
2541
expect(CATPPUCCIN_PALETTES.mocha.green).toBe("#a6e3a1");
@@ -51,6 +67,8 @@ describe("themes", () => {
5167
test("keeps Catppuccin add and remove rows semantically distinct", () => {
5268
for (const theme of [
5369
resolveTheme("catppuccin-latte", null),
70+
resolveTheme("catppuccin-frappe", null),
71+
resolveTheme("catppuccin-macchiato", null),
5472
resolveTheme("catppuccin-mocha", null),
5573
]) {
5674
expect(theme.addedBg).not.toBe(theme.removedBg);

0 commit comments

Comments
 (0)