From 9537a12537173477d57d2ae31f55d3f5981f7e5a Mon Sep 17 00:00:00 2001 From: Breadway Date: Sat, 6 Jun 2026 22:31:38 +0800 Subject: [PATCH] Refactor theme onto bread-theme; add bakery.toml and release workflow - breadpad-shared/Cargo.toml: depend on bread-theme (no gtk feature needed in the shared crate) - breadpad-shared/src/theme.rs: re-export Palette and load_palette from bread-theme; retain all breadpad-specific CSS in build_css() - bakery.toml: describes breadpad for bakery install - release.yml: builds on hestia self-hosted runner, publishes binaries to dl.breadway.dev and GitHub Releases on v* tags --- .github/workflows/release.yml | 61 ++++++++++ Cargo.lock | 10 ++ bakery.toml | 14 +++ breadpad-shared/Cargo.toml | 3 + breadpad-shared/src/theme.rs | 218 +--------------------------------- 5 files changed, 92 insertions(+), 214 deletions(-) create mode 100644 .github/workflows/release.yml create mode 100644 bakery.toml diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..4d3ffa7 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,61 @@ +name: release + +on: + push: + tags: ["v*"] + +env: + DL_DIR: /srv/breadway-dl + ECOSYSTEM_DIR: /home/breadway/Projects/bread-ecosystem + +jobs: + build: + runs-on: [self-hosted, hestia] + steps: + - uses: actions/checkout@v4 + + - name: install system deps + run: sudo pacman -S --noconfirm gtk4 gtk4-layer-shell 2>/dev/null || true + + - name: build + run: cargo build --release --locked + + - name: prepare artifacts + run: | + VERSION="${GITHUB_REF_NAME#v}" + PKG_DIR="${DL_DIR}/breadpad/${VERSION}" + mkdir -p "${PKG_DIR}" + for bin in breadpad breadman; do + cp "target/release/${bin}" "${PKG_DIR}/${bin}-x86_64" + strip "${PKG_DIR}/${bin}-x86_64" + sha256sum "${PKG_DIR}/${bin}-x86_64" | awk '{print $1}' \ + > "${PKG_DIR}/${bin}-x86_64.sha256" + done + cp breadpad.example.toml "${PKG_DIR}/" + cp bakery.toml "${PKG_DIR}/bakery.toml" + ln -sfn "${PKG_DIR}" "${DL_DIR}/breadpad/latest" + + - name: ensure bread-ecosystem + run: | + if [[ -d "${ECOSYSTEM_DIR}/.git" ]]; then + git -C "${ECOSYSTEM_DIR}" pull --ff-only + else + mkdir -p "$(dirname "${ECOSYSTEM_DIR}")" + git clone https://github.com/Breadway/bread-ecosystem.git "${ECOSYSTEM_DIR}" + fi + + - name: regenerate index.json + run: bash "${ECOSYSTEM_DIR}/scripts/gen-index.sh" + + - name: upload to GitHub Release + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + VERSION="${GITHUB_REF_NAME#v}" + PKG_DIR="${DL_DIR}/breadpad/${VERSION}" + gh release upload "${GITHUB_REF_NAME}" \ + "${PKG_DIR}/breadpad-x86_64" \ + "${PKG_DIR}/breadman-x86_64" \ + "${PKG_DIR}/breadpad-x86_64.sha256" \ + "${PKG_DIR}/breadman-x86_64.sha256" \ + --clobber diff --git a/Cargo.lock b/Cargo.lock index 16ce198..0367649 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -302,6 +302,15 @@ dependencies = [ "piper", ] +[[package]] +name = "bread-theme" +version = "0.1.0" +dependencies = [ + "dirs 5.0.1", + "serde", + "serde_json", +] + [[package]] name = "breadman" version = "0.1.0" @@ -343,6 +352,7 @@ name = "breadpad-shared" version = "0.1.0" dependencies = [ "anyhow", + "bread-theme", "chrono", "dirs 5.0.1", "ical", diff --git a/bakery.toml b/bakery.toml new file mode 100644 index 0000000..e1c028d --- /dev/null +++ b/bakery.toml @@ -0,0 +1,14 @@ +name = "breadpad" +description = "Quick-capture scratchpad and note viewer with AI classification" +binaries = ["breadpad", "breadman"] +system_deps = ["gtk4", "gtk4-layer-shell"] +bread_deps = [] + +[config] +dir = "~/.config/breadpad" +example = "breadpad.example.toml" + +[install] +post_install = [ + "mkdir -p ~/.local/share/breadpad/model", +] diff --git a/breadpad-shared/Cargo.toml b/breadpad-shared/Cargo.toml index a4b3ddb..5d6d26a 100644 --- a/breadpad-shared/Cargo.toml +++ b/breadpad-shared/Cargo.toml @@ -7,6 +7,9 @@ authors.workspace = true [dependencies] +# Path dep for local dev; replace with git dep on first tag: +# bread-theme = { git = "https://github.com/Breadway/bread-ecosystem", tag = "theme-v0.1.0" } +bread-theme = { path = "../../bread-ecosystem/bread-theme" } anyhow.workspace = true tracing.workspace = true serde.workspace = true diff --git a/breadpad-shared/src/theme.rs b/breadpad-shared/src/theme.rs index 64192d9..adc76dc 100644 --- a/breadpad-shared/src/theme.rs +++ b/breadpad-shared/src/theme.rs @@ -1,83 +1,7 @@ -use serde::Deserialize; -use std::collections::HashMap; -use std::path::PathBuf; - -#[derive(Debug, Clone)] -pub struct Palette { - pub background: String, - pub foreground: String, - pub color0: String, - pub color1: String, - pub color2: String, - pub color3: String, - pub color4: String, - pub color5: String, - pub color6: String, - pub color7: String, -} - -// Catppuccin Mocha fallback -impl Default for Palette { - fn default() -> Self { - Palette { - background: "#1e1e2e".into(), - foreground: "#cdd6f4".into(), - color0: "#45475a".into(), - color1: "#f38ba8".into(), - color2: "#a6e3a1".into(), - color3: "#f9e2af".into(), - color4: "#89b4fa".into(), - color5: "#f5c2e7".into(), - color6: "#94e2d5".into(), - color7: "#bac2de".into(), - } - } -} - -#[derive(Debug, Deserialize)] -struct WalColors { - #[serde(default)] - colors: HashMap, - special: Option, -} - -#[derive(Debug, Deserialize)] -struct WalSpecial { - background: Option, - foreground: Option, -} - -pub(crate) fn palette_from_wal_json(json: &str) -> Option { - let wal: WalColors = serde_json::from_str(json).ok()?; - Some(Palette { - background: wal.special.as_ref().and_then(|s| s.background.clone()).unwrap_or_else(|| "#1e1e2e".into()), - foreground: wal.special.as_ref().and_then(|s| s.foreground.clone()).unwrap_or_else(|| "#cdd6f4".into()), - color0: wal.colors.get("color0").cloned().unwrap_or_else(|| "#45475a".into()), - color1: wal.colors.get("color1").cloned().unwrap_or_else(|| "#f38ba8".into()), - color2: wal.colors.get("color2").cloned().unwrap_or_else(|| "#a6e3a1".into()), - color3: wal.colors.get("color3").cloned().unwrap_or_else(|| "#f9e2af".into()), - color4: wal.colors.get("color4").cloned().unwrap_or_else(|| "#89b4fa".into()), - color5: wal.colors.get("color5").cloned().unwrap_or_else(|| "#f5c2e7".into()), - color6: wal.colors.get("color6").cloned().unwrap_or_else(|| "#94e2d5".into()), - color7: wal.colors.get("color7").cloned().unwrap_or_else(|| "#bac2de".into()), - }) -} - -pub fn load_palette() -> Palette { - let wal_path = wal_colors_path(); - if !wal_path.exists() { - return Palette::default(); - } - match std::fs::read_to_string(&wal_path) - .ok() - .and_then(|s| palette_from_wal_json(&s)) - { - Some(wal) => wal, - None => Palette::default(), - } -} - +pub use bread_theme::{load_palette, Palette}; +/// Generate the full breadpad CSS string. The base colour variables come from +/// `bread-theme`; the widget rules below are breadpad-specific. pub fn build_css(palette: &Palette, user_css: Option<&str>) -> String { let mut css = format!( r#" @@ -317,125 +241,21 @@ entry:focus { css } -fn wal_colors_path() -> PathBuf { - dirs::cache_dir() - .unwrap_or_else(|| PathBuf::from("~/.cache")) - .join("wal") - .join("colors.json") -} - #[cfg(test)] mod tests { use super::*; - const TOKYO_NIGHT_WAL: &str = r##"{ - "special": { - "background": "#1a1b26", - "foreground": "#c0caf5" - }, - "colors": { - "color0": "#15161e", - "color1": "#f7768e", - "color2": "#9ece6a", - "color3": "#e0af68", - "color4": "#7aa2f7", - "color5": "#bb9af7", - "color6": "#7dcfff", - "color7": "#a9b1d6" - } - }"##; - - // ---- Default palette (Catppuccin Mocha) ---- - - #[test] - fn default_background_is_catppuccin_mocha() { - assert_eq!(Palette::default().background, "#1e1e2e"); - } - - #[test] - fn default_foreground_is_catppuccin_mocha() { - assert_eq!(Palette::default().foreground, "#cdd6f4"); - } - - #[test] - fn default_red_is_catppuccin_mocha() { - assert_eq!(Palette::default().color1, "#f38ba8"); - } - - #[test] - fn default_blue_is_catppuccin_mocha() { - assert_eq!(Palette::default().color4, "#89b4fa"); - } - - #[test] - fn default_teal_is_catppuccin_mocha() { - assert_eq!(Palette::default().color6, "#94e2d5"); - } - - // ---- palette_from_wal_json ---- - - #[test] - fn wal_json_parses_special_background() { - let p = palette_from_wal_json(TOKYO_NIGHT_WAL).unwrap(); - assert_eq!(p.background, "#1a1b26"); - } - - #[test] - fn wal_json_parses_special_foreground() { - let p = palette_from_wal_json(TOKYO_NIGHT_WAL).unwrap(); - assert_eq!(p.foreground, "#c0caf5"); - } - - #[test] - fn wal_json_parses_numbered_colors() { - let p = palette_from_wal_json(TOKYO_NIGHT_WAL).unwrap(); - assert_eq!(p.color0, "#15161e"); - assert_eq!(p.color1, "#f7768e"); - assert_eq!(p.color4, "#7aa2f7"); - assert_eq!(p.color7, "#a9b1d6"); - } - - #[test] - fn wal_json_missing_special_falls_back_to_defaults() { - let json = r##"{"colors":{"color0":"#000000"}}"##; - let p = palette_from_wal_json(json).unwrap(); - assert_eq!(p.background, "#1e1e2e"); - assert_eq!(p.foreground, "#cdd6f4"); - } - - #[test] - fn wal_json_missing_color_falls_back_to_default() { - let json = r##"{"special":{"background":"#ff0000","foreground":"#ffffff"},"colors":{}}"##; - let p = palette_from_wal_json(json).unwrap(); - assert_eq!(p.color4, "#89b4fa"); // default blue - } - - #[test] - fn invalid_wal_json_returns_none() { - assert!(palette_from_wal_json("not json").is_none()); - assert!(palette_from_wal_json("").is_none()); - assert!(palette_from_wal_json("{}").is_some()); // empty but valid → all defaults - } - - // ---- build_css ---- - #[test] fn css_defines_bg_color() { let css = build_css(&Palette::default(), None); assert!(css.contains("@define-color bg #1e1e2e"), "css missing bg: {}", &css[..300]); } - #[test] - fn css_defines_fg_color() { - let css = build_css(&Palette::default(), None); - assert!(css.contains("@define-color fg #cdd6f4")); - } - #[test] fn css_defines_all_named_colors() { let css = build_css(&Palette::default(), None); for name in &["red", "green", "yellow", "blue", "pink", "teal", "overlay"] { - assert!(css.contains(&format!("@define-color {} ", name)), "missing @define-color {}", name); + assert!(css.contains(&format!("@define-color {name} ")), "missing @define-color {name}"); } } @@ -458,18 +278,6 @@ mod tests { assert!(css.contains(".note-card {")); } - #[test] - fn css_contains_type_chip_class() { - let css = build_css(&Palette::default(), None); - assert!(css.contains(".type-chip {")); - } - - #[test] - fn css_contains_sidebar_row_class() { - let css = build_css(&Palette::default(), None); - assert!(css.contains(".sidebar-row {")); - } - #[test] fn css_appends_user_css() { let user = ".my-override { color: hotpink; }"; @@ -492,22 +300,4 @@ mod tests { assert!(css.contains("@define-color bg #deadbe"), "css: {}", &css[..300]); assert!(css.contains("@define-color blue #cafe00"), "css: {}", &css[..300]); } - - #[test] - fn css_from_wal_palette_uses_wal_colors() { - let p = palette_from_wal_json(TOKYO_NIGHT_WAL).unwrap(); - let css = build_css(&p, None); - assert!(css.contains("@define-color bg #1a1b26"), "css: {}", &css[..300]); - assert!(css.contains("@define-color fg #c0caf5")); - } - - #[test] - fn load_palette_returns_valid_palette() { - // No wal file in CI/test env; should return non-empty strings starting with # - let palette = load_palette(); - assert!(!palette.background.is_empty()); - assert!(palette.background.starts_with('#'), "bg: {}", palette.background); - assert!(!palette.foreground.is_empty()); - assert!(palette.color4.starts_with('#')); - } }