Skip to content

Release v0.4.0

Choose a tag to compare

@github-actions github-actions released this 22 Mar 05:31
· 7 commits to main since this release

Release Notes — Opaline v0.4.0

Released: 2026-03-22

Opaline v0.4.0 transforms the library from a Ratatui-focused theme engine into a multi-framework theme engine for all of Rust. Five new rendering adapters (crossterm, owo-colors, CSS, syntect, egui) join the existing ratatui and CLI adapters, the builtin theme collection nearly doubles from 20 to 39 themes across 17 colorscheme families, and the core theme contract is narrowed to 26 universal semantic tokens — removing all app-specific leaks.

🌟 Highlights

Multi-Framework Adapter Ecosystem

Five new adapters in src/adapters/ make Opaline themes portable across rendering targets. crossterm and owo-colors cover direct terminal styling, CSS generates custom properties and classes for web frameworks (Leptos, Yew, Dioxus, Tauri), syntect bridges into syntax highlighters like bat and delta, and egui maps themes to full Visuals for immediate-mode GUIs. Each adapter is independently feature-gated — pull in only what you need.

39 Builtin Themes Across 17 Families

19 new themes bring the collection to 39, adding complete families for Ayu (Dark, Mirage, Light), Night Owl (Dark, Light), Flexoki (Dark, Light), Palenight, GitHub (Dark Dimmed, Light), and Monokai Pro, plus missing variants for Catppuccin, Solarized, Gruvbox, One, Tokyo Night, and Kanagawa. Every theme passes the full contract test suite.

Focused Core Contract (26 Tokens, 13 Styles)

App-specific tokens (git.*, diff.*, mode.*, chat.*, code.hash, code.path) and their matching styles are removed from the core contract. Consuming apps now derive domain-specific semantics via register_default_token(). This makes Opaline a clean, universal base layer.

Theme TOML Export

New to_toml(), to_theme_file(), and save_to_file() methods on Theme enable round-trip serialization — load a theme, modify it, export valid TOML. Foundation for theme editor workflows.

File-Backed Theme Override Semantics

Theme discovery now searches file-backed themes first, then builtins. User themes in ~/.config/opaline/themes/ override builtin themes with the same id. New load_theme_by_name_in_dirs() and list_available_themes_in_dirs() functions give apps full control over search paths.

CI Migration to Shared Workflows

All three GitHub Actions workflows (cicd.yml, docs.yml, release.yml) are replaced with thin callers to hyperb1iss/shared-workflows. Total workflow code drops from ~354 lines to ~90 lines. Crates.io publishing now uses OIDC trusted publishing instead of long-lived tokens.

✨ New Adapters

  • crosstermFrom<OpalineColor>Color, From<OpalineStyle>ContentStyle with all 9 modifier attributes, Theme::crossterm_styled() helper, and per-character gradient rendering via gradient_styled() and gradient_bar()
  • owo-colors — Zero-allocation terminal coloring with OwoThemeExt trait providing owo_style(), owo_fg(), owo_bg() on Theme, plus gradient_string() for gradient text output
  • cssgenerate_css_vars() emits :root { --opaline-*: #hex; } custom properties, generate_css_classes() produces .opaline-* rule sets with mapped CSS properties (font-weight, opacity, text-decoration), and generate_stylesheet() combines both
  • syntectto_syntect_theme() generates a full syntect::highlighting::Theme with TextMate scope mappings from code.* tokens, ThemeSettings from bg/fg/accent tokens, and FontStyle flag conversion
  • eguito_egui_visuals() produces complete egui::Visuals from theme tokens, starting from dark or light base and overriding all panel, window, selection, widget, and stroke colors

🎨 Theme Collection

  • New families: GitHub (Dark Dimmed, Light), Monokai Pro, Ayu (Dark, Mirage, Light), Night Owl (Dark, Light), Palenight, Flexoki (Dark, Light)
  • Completed families: Catppuccin Frappé + Macchiato, Solarized Dark, Gruvbox Light, One Light, Tokyo Night Moon, Kanagawa Dragon + Lotus
  • Existing theme updates: All 20 existing themes trimmed to the new 26-token core contract — removed git.*, diff.*, mode.*, code.hash, code.path tokens and their associated styles
  • Light theme contrast fixes: Everforest Light accent/grey darkened to ~4:1 contrast ratios (up from ~2.4:1); Rose Pine Dawn and Solarized Light text.dim/text.muted overlap corrected

♻️ Core Engine

  • Strict TOML parsing: #[serde(deny_unknown_fields)] added to ThemeFile, ThemeMeta, and StyleDef — unknown keys in theme files now produce parse errors instead of being silently ignored
  • Resolver hardening: Hex literal color refs (#rrggbb) in style fg/bg and gradient stops are now parsed via OpalineColor::from_hex() with proper InvalidColor errors, separate from named token/palette resolution
  • Gradient deserialization: Custom Deserialize impl on Gradient routes through try_new(), catching empty-stop errors at parse time rather than panicking
  • Empty gradient guard: The resolver now returns OpalineError::EmptyGradient for zero-stop gradient definitions
  • ThemeInfo::load() — New method loads the theme represented by metadata, preferring file-backed path over builtin fallback
  • list_available_themes_for_app() / list_available_themes_in_dirs() — New discovery functions for app-specific and custom theme search paths with deduplication via push_or_replace_theme()
  • load_theme_by_name_in_dirs() — Directory-driven variant of load_theme_by_name for apps managing their own search paths

⚡ Unicode Correctness

  • All gradient rendering functions across ratatui, CLI, crossterm, and owo-colors adapters switched from chars() to graphemes(true) via unicode_segmentation — correct handling of combining marks, emoji with modifiers, and complex scripts
  • ThemeSelector widget text wrapping now uses UnicodeWidthStr for accurate visual width calculation instead of byte length

📝 Documentation

  • Five new VitePress guide pages: crossterm.md, css.md, egui.md, owo-colors.md, syntect.md
  • Updated all existing guide and reference pages for the new inherent API (no trait import) and 26-token contract
  • README broadened from "Ratatui TUI applications" to "Rust applications" throughout
  • Added "Used By" section to README listing downstream consumers (git-iris, unifly)
  • Cargo.toml description and keywords updated — added egui, gui; broadened categories

🔧 Infrastructure

  • CI/CD, docs, and release workflows migrated to hyperb1iss/shared-workflows reusable workflows (~75% line reduction)
  • Crates.io publishing switched to OIDC trusted publishing via rust-lang/crates-io-auth-action — tokens auto-revoked on job completion
  • ThemeSelector vim-style keybindings (j/k) removed — only standard Up/Down arrow keys remain

💥 Breaking Changes

  • Token contract narrowed from 36 to 26 tokens — Removed: git.staged, git.modified, git.untracked, git.deleted, diff.added, diff.removed, diff.hunk, diff.context, mode.active, mode.inactive, mode.hover, code.hash, code.path. Apps using these tokens must now derive them via register_default_token().
  • Style contract narrowed from 18 to 13 styles — Removed: file_path, commit_hash, git_staged, git_modified, git_untracked, git_deleted, diff_added, diff_removed, diff_hunk, diff_context, timestamp, author, mode_inactive. Apps must derive these via custom styles.
  • #[serde(deny_unknown_fields)] on schema types — Theme TOML files with unrecognized keys now fail to parse. Remove any non-standard fields from custom themes.
  • Theme loading priority reversedload_theme_by_name() now checks file-backed discovery paths before builtins (was builtins-first). User themes with the same id as a builtin now take precedence.
  • discovery::theme_dirs() scope narrowed — Returns only the Opaline-specific config directory. Use app_theme_dirs() for app-specific paths.

📋 Upgrade Notes

  1. Remove app-specific tokens from custom themes — If your .toml files define git.*, diff.*, mode.*, code.hash, or code.path tokens, delete them from the [tokens] section. Use register_default_token() in your app code instead.
  2. Clean up unknown TOML fields — Any non-standard keys in [meta], [styles], or [styles.*] sections will now cause parse errors. Audit custom theme files.
  3. Update code relying on load order — If you depend on builtins taking precedence over user themes, switch to load_by_name() (builtins-only) instead of load_theme_by_name() (discovery-first).
  4. Enable new adapters as needed — Add feature flags to Cargo.toml: crossterm, owo-colors, css, syntect, egui.
  5. ThemeSelector keybindings — If your app documentation references j/k for navigation, update to arrow keys only.