diff --git a/CHANGELOG.md b/CHANGELOG.md index 2b6d4fa..0f4f8e8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,16 @@ # Changelog +# 2026-10-06 + +- **New built-in theme: `catppuccin-latte`** — a second light palette alongside + `kanagawa-light`, using the official Catppuccin Latte colors + (https://github.com/catppuccin/catppuccin): blue `#1E66F5` primary, lavender + `#8839EF` unread, peach `#DF8E1D` dates, red `#D20F39` errors/read-authors, + green `#40A02B` success, on the `#EFF1F5` base. The reader picks glamour's + `light` style for it, so markdown emails render for a light background too + (`glamourStyleFor`, `internal/ui/styles.go`). Tests: `TestApplyTheme_KnownNames`, + `TestGlamourStyleFor`. + # 2026-10-05 - **The folder window is the newest mail by arrival date, not the highest UIDs (#34)** — `FetchHeaders` picked its `inbox_count` window as the highest `n` UIDs. IMAP MOVE/COPY give a message a fresh, highest UID in the destination mailbox (RFC 9051 per-mailbox UIDs), so every mail bulk-moved *into* a folder — `I` approve on ToScreen, `:reset-toscreen`, `U` undo — landed above all real recent mail, filled the window, and pushed the recent mail below the cutoff where no display sort (`,m`) could bring it back. Only folders with more than `inbox_count` messages were affected, which is why a HEY-style Inbox of a dozen rows never showed it while a 12'000-mail legacy Inbox after its first screening did (reproduced on Hostpoint/Dovecot, so not an Infomaniak quirk; the demo's `moves.log` shows `ToScreen uid=273 → INBOX destUID=1397`). `FetchHeaders`, `FetchLatest` (`:everything`) and the per-folder search cap (`searchFolderCap`, was a literal 100) now narrow the window with one cheap `UID FETCH (UID INTERNALDATE)` and keep the `n` most recently received (`newestUIDsByInternalDate`, `internal/imap/client.go`); INTERNALDATE survives MOVE/COPY on every server tested, and the SORT extension is not used because Infomaniak does not offer it. Folders at or below `inbox_count`, and `inbox_count = 0`, take the old single-round-trip path unchanged, so a daily Inbox sees no new traffic. Verified on the live Hostpoint demo: a 10-row window returned April mail before, October mail after. Tests: `TestMem_FetchHeaders_WindowIsNewestByInternalDateNotUID`, `TestMem_FetchHeaders_FolderWithinLimitUnchanged`, `TestMem_FetchLatest_WindowIsNewestByInternalDateNotUID`, `TestMem_SearchFolder_CapIsNewestByInternalDateNotUID`. diff --git a/README.md b/README.md index ce08d5d..4a02003 100644 --- a/README.md +++ b/README.md @@ -191,7 +191,7 @@ Keep your inbox clean without effort. - **IMAP + SMTP** — direct connection via RFC 6851 MOVE, no local sync daemon required; stays in sync if you use it on mobile or different device [→](https://neomd.ssp.sh/docs/configuration/) - **RFC 5322 compliant email delivery** — Message-IDs use sender's domain, proper MIME multipart/alternative structure (text/plain before text/html), quoted-printable encoding, and all required headers; ensures deliverability across all providers, spam filter compatibility, and correct email threading [→](https://neomd.ssp.sh/docs/configuration/email-standards/) -- **Themes** — six built-in palettes (`kanagawa` default, `kanagawa-paper`, `kanagawa-light` for daylight terminals, `rose-pine`, `gruvbox`, `osaka-jade`); pick via `[ui].theme = "..."` and override individual colour slots in an optional `[theme]` block [→](https://neomd.ssp.sh/docs/configuration/#theming) +- **Themes** — seven built-in palettes (`kanagawa` default, `kanagawa-paper`, `kanagawa-light` and `catppuccin-latte` for daylight terminals, `rose-pine`, `gruvbox`, `osaka-jade`); pick via `[ui].theme = "..."` and override individual colour slots in an optional `[theme]` block [→](https://neomd.ssp.sh/docs/configuration/#theming) > [!NOTE] > Folder switches you've seen before, deletes/archives/moves, and screening are **instant on any provider** — they're served from cache and update the list before the server answers (optimistic updates + a second IMAP connection). What still depends on your IMAP provider's latency: the **first** visit to a folder, **opening an email**, and **search**. On Hostpoint (the provider I use) those cost about 2 round trips at ~33ms each; on Gmail the same round trips run at ~570ms each. See [Benchmark](#benchmark) for full details and how to test your provider. diff --git a/docs/content/docs/_index.md b/docs/content/docs/_index.md index 12cdf90..4ff8a75 100644 --- a/docs/content/docs/_index.md +++ b/docs/content/docs/_index.md @@ -194,7 +194,7 @@ Keep your inbox clean without effort. - **IMAP + SMTP** — direct connection via RFC 6851 MOVE, no local sync daemon required; stays in sync if you use it on mobile or different device [→](https://neomd.ssp.sh/docs/configuration/) - **RFC 5322 compliant email delivery** — Message-IDs use sender's domain, proper MIME multipart/alternative structure (text/plain before text/html), quoted-printable encoding, and all required headers; ensures deliverability across all providers, spam filter compatibility, and correct email threading [→](https://neomd.ssp.sh/docs/configuration/email-standards/) -- **Themes** — six built-in palettes (`kanagawa` default, `kanagawa-paper`, `kanagawa-light` for daylight terminals, `rose-pine`, `gruvbox`, `osaka-jade`); pick via `[ui].theme = "..."` and override individual colour slots in an optional `[theme]` block [→](https://neomd.ssp.sh/docs/configuration/#theming) +- **Themes** — seven built-in palettes (`kanagawa` default, `kanagawa-paper`, `kanagawa-light` and `catppuccin-latte` for daylight terminals, `rose-pine`, `gruvbox`, `osaka-jade`); pick via `[ui].theme = "..."` and override individual colour slots in an optional `[theme]` block [→](https://neomd.ssp.sh/docs/configuration/#theming) {{< callout type="info" >}} Folder switches you've seen before, deletes/archives/moves, and screening are **instant on any provider** — they're served from cache and update the list before the server answers (optimistic updates + a second IMAP connection). What still depends on your IMAP provider's latency: the **first** visit to a folder, **opening an email**, and **search**. On Hostpoint (the provider I use) those cost about 2 round trips at ~33ms each; on Gmail the same round trips run at ~570ms each. See [Benchmark](#benchmark) for full details and how to test your provider. diff --git a/docs/content/docs/configuration/_index.md b/docs/content/docs/configuration/_index.md index 8241d2d..75e723d 100644 --- a/docs/content/docs/configuration/_index.md +++ b/docs/content/docs/configuration/_index.md @@ -89,7 +89,7 @@ spam = "spam" #check capitalization of your pre-existing Spam folder, so # Gmail uses different folder names — see docs/content/gmail.md for the correct mapping. [ui] -theme = "kanagawa" # kanagawa | kanagawa-paper | kanagawa-light | rose-pine | gruvbox | osaka-jade +theme = "kanagawa" # kanagawa | kanagawa-paper | kanagawa-light | catppuccin-latte | rose-pine | gruvbox | osaka-jade inbox_count = 200 # how many newest emails neomd loads per folder/reload auto_screen_on_load = true # screen inbox automatically on every load (default true) bg_sync_interval = 5 # background sync interval in minutes; 0 = disabled (default 5) @@ -450,13 +450,14 @@ For a business account where you want a logo, table layout, and CSS that goldmar ## Theming -Pick from six built-in palettes via `[ui].theme`: +Pick from seven built-in palettes via `[ui].theme`: | Name | Mode | Source | |---|---|---| | `kanagawa` (default) | dark | https://github.com/rebelot/kanagawa.nvim | | `kanagawa-paper` | dark | https://github.com/thesimonho/kanagawa-paper.nvim | | `kanagawa-light` | **light** | Lotus palette from kanagawa.nvim, paperwhite (#F2EFE9) background | +| `catppuccin-latte` | **light** | https://github.com/catppuccin/catppuccin — Latte | | `rose-pine` | dark | https://github.com/rose-pine/rose-pine-theme | | `gruvbox` | dark | https://github.com/morhetz/gruvbox | | `osaka-jade` | dark | https://github.com/Justikun/omarchy-osaka-jade-theme | diff --git a/internal/config/config.go b/internal/config/config.go index 5ad8da0..678fff9 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -811,7 +811,7 @@ func defaults() *Config { Spam: "Spam", }, UI: UIConfig{ - Theme: "kanagawa", // built-in: kanagawa | kanagawa-paper | kanagawa-light | rose-pine | gruvbox | osaka-jade + Theme: "kanagawa", // built-in: kanagawa | kanagawa-paper | kanagawa-light | catppuccin-latte | rose-pine | gruvbox | osaka-jade InboxCount: 200, BgSyncInterval: 5, MarkAsReadAfterSecs: 7, diff --git a/internal/ui/styles.go b/internal/ui/styles.go index 1fbd1ca..2fdbaac 100644 --- a/internal/ui/styles.go +++ b/internal/ui/styles.go @@ -43,6 +43,16 @@ var themes = map[string]config.Theme{ AuthorUnread: "#CC6D00", SubjectUnread: "#4E8CA2", Error: "#C84053", Success: "#6F894E", }, + "catppuccin-latte": { + // https://github.com/catppuccin/catppuccin — Latte (light) + Bg: "#EFF1F5", Border: "#9CA0B0", Subtle: "#CCD0DA", Selected: "#DCE0E8", + Text: "#4C4F69", Muted: "#6C6F85", + Primary: "#1E66F5", Unread: "#8839EF", + Number: "#1E66F5", Date: "#DF8E1D", + AuthorRead: "#D20F39", SubjectRead: "#179299", SizeCol: "#6C6F85", + AuthorUnread: "#FE640B", SubjectUnread: "#04A5E5", + Error: "#D20F39", Success: "#40A02B", + }, "rose-pine": { // https://github.com/rose-pine/rose-pine-theme — main variant Bg: "#191724", Border: "#26233A", Subtle: "#1F1D2E", Selected: "#403D52", @@ -128,7 +138,7 @@ func init() { // `dark`. The legacy "auto" was rarely useful in practice and would now // be ambiguous, so we collapse it to `dark` for predictability. func glamourStyleFor(themeName string) string { - if themeName == "kanagawa-light" || themeName == "light" { + if themeName == "kanagawa-light" || themeName == "catppuccin-latte" || themeName == "light" { return "light" } return "dark" diff --git a/internal/ui/styles_test.go b/internal/ui/styles_test.go index d2bbf4e..1e1ed4b 100644 --- a/internal/ui/styles_test.go +++ b/internal/ui/styles_test.go @@ -57,7 +57,7 @@ func TestKanagawaDefault(t *testing.T) { } func TestApplyTheme_KnownNames(t *testing.T) { - for _, name := range []string{"kanagawa", "kanagawa-paper", "kanagawa-light", "rose-pine", "gruvbox", "osaka-jade"} { + for _, name := range []string{"kanagawa", "kanagawa-paper", "kanagawa-light", "catppuccin-latte", "rose-pine", "gruvbox", "osaka-jade"} { ApplyTheme(name, config.Theme{}) if colorBg == "" || colorPrimary == "" { t.Errorf("ApplyTheme(%q): expected colors populated, got empty", name) @@ -111,16 +111,17 @@ func TestGlamourStyleFor(t *testing.T) { // falls back to "notty" which strips colours/wrapping. The mapper must // always return one of glamour's built-in style names. cases := map[string]string{ - "kanagawa": "dark", - "kanagawa-paper": "dark", - "kanagawa-light": "light", - "rose-pine": "dark", - "gruvbox": "dark", - "osaka-jade": "dark", - "": "dark", // empty config falls through to dark - "unknown-theme": "dark", // unknown names also fall through, never to notty - "light": "light", // legacy literal still respected - "auto": "dark", // legacy "auto" collapses to dark for predictability + "kanagawa": "dark", + "kanagawa-paper": "dark", + "kanagawa-light": "light", + "catppuccin-latte": "light", + "rose-pine": "dark", + "gruvbox": "dark", + "osaka-jade": "dark", + "": "dark", // empty config falls through to dark + "unknown-theme": "dark", // unknown names also fall through, never to notty + "light": "light", // legacy literal still respected + "auto": "dark", // legacy "auto" collapses to dark for predictability } for in, want := range cases { if got := glamourStyleFor(in); got != want {