Publish theme snapshots through IPC from explicit live state

This commit is contained in:
Samuel Onoja committed 2026-10-03 00:05:29 +01:00
1 parent 8195b82073
commit ccf7497b6f
23 files changed
+287 -130

No files matched your search

-32
View File
@@ -10,7 +10,6 @@ use serde_json::{Value, json};
use crate::Document;
pub use ferese_theme_model::*;
pub const SCHEMA_VERSION: u32 = 2;
pub const TRANSITION_MS: u64 = 250;
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
@@ -229,37 +228,6 @@ pub fn default_theme() -> ResolvedTheme {
}
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
pub struct Snapshot {
pub version: u32,
pub revision: u64,
pub mode: Mode,
pub theme: ResolvedTheme,
pub presented: ResolvedTheme,
pub warnings: Vec<String>,
pub error: Option<String>,
#[serde(default = "crate::families::builtins")]
pub families: Vec<crate::families::Family>,
#[serde(default)]
pub fallback_note: Option<String>,
}
impl Default for Snapshot {
fn default() -> Self {
Self {
version: SCHEMA_VERSION,
revision: 0,
mode: Mode::Dark,
theme: default_theme(),
presented: default_theme(),
warnings: vec![],
error: None,
families: crate::families::builtins(),
fallback_note: None,
}
}
}
#[derive(Clone)]
pub struct Candidate {
pub policy: Policy,
-266
View File
@@ -1,266 +0,0 @@
{
"version": 2,
"revision": 0,
"mode": "dark",
"theme": {
"appearance": "dark",
"tokens": {
"colors": {
"surface_base": "#111821",
"surface_raised": "#1E2530",
"application_background": "#171E27",
"text_primary": "#F4F7FB",
"text_muted": "#8793A2",
"accent": "#3D7BE6",
"on_accent": "#FFFFFF",
"border": "#FFFFFF18",
"shadow": "#00000055"
},
"material": {
"style": "solid",
"opacity": 0.78,
"blur_radius": 12.0,
"tint_strength": 0.5
},
"geometry": {
"border_width": 1.0,
"focus_ring_width": 2.0,
"window_radius": 14.0,
"shell_radius": 14.0,
"top_bar_height": 28.0,
"top_bar_margin_top": 0,
"top_bar_margin_horizontal": 0,
"top_bar_window_gap": 0,
"panel_padding": 12.0,
"control_gap": 12.0
},
"typography": {
"font_family": "Inter"
},
"background": {
"path": "<wallpaper>",
"lock_path": null,
"mode": "fill"
},
"surface": {
"bar": {
"background": "#111821",
"text_primary": "#F4F7FB",
"text_muted": "#8793A2"
}
},
"shadow": {
"soft": {
"offset_y": 4.0,
"blur": 18.0,
"opacity": 0.2
}
},
"border": {
"gradient": null
},
"focus_ring": {
"gradient": null
}
},
"requested_accent": "#3D7BE6",
"accessibility": {
"increase_contrast": false,
"reduce_transparency": false
},
"reduced_motion": false
},
"presented": {
"appearance": "dark",
"tokens": {
"colors": {
"surface_base": "#111821",
"surface_raised": "#1E2530",
"application_background": "#171E27",
"text_primary": "#F4F7FB",
"text_muted": "#8793A2",
"accent": "#3D7BE6",
"on_accent": "#FFFFFF",
"border": "#FFFFFF18",
"shadow": "#00000055"
},
"material": {
"style": "solid",
"opacity": 0.78,
"blur_radius": 12.0,
"tint_strength": 0.5
},
"geometry": {
"border_width": 1.0,
"focus_ring_width": 2.0,
"window_radius": 14.0,
"shell_radius": 14.0,
"top_bar_height": 28.0,
"top_bar_margin_top": 0,
"top_bar_margin_horizontal": 0,
"top_bar_window_gap": 0,
"panel_padding": 12.0,
"control_gap": 12.0
},
"typography": {
"font_family": "Inter"
},
"background": {
"path": "<wallpaper>",
"lock_path": null,
"mode": "fill"
},
"surface": {
"bar": {
"background": "#111821",
"text_primary": "#F4F7FB",
"text_muted": "#8793A2"
}
},
"shadow": {
"soft": {
"offset_y": 4.0,
"blur": 18.0,
"opacity": 0.2
}
},
"border": {
"gradient": null
},
"focus_ring": {
"gradient": null
}
},
"requested_accent": "#3D7BE6",
"accessibility": {
"increase_contrast": false,
"reduce_transparency": false
},
"reduced_motion": false
},
"warnings": [],
"error": null,
"families": [
{
"id": "ferese-blue",
"name": "Ferese Blue",
"light": {
"background": "#F5F7FB",
"bar": "#F5F7FB",
"surface": "#E7E9ED",
"text": "#182234",
"muted": "#586579",
"accent": "#3D7BE6"
},
"dark": {
"background": "#111821",
"bar": "#111821",
"surface": "#1E2530",
"text": "#F4F7FB",
"muted": "#8793A2",
"accent": "#3D7BE6"
}
},
{
"id": "catppuccin",
"name": "Catppuccin",
"light": {
"background": "#EFF1F5",
"bar": "#EFF1F5",
"surface": "#E6E9EF",
"text": "#4C4F69",
"muted": "#5C5F77",
"accent": "#8839EF"
},
"dark": {
"background": "#1E1E2E",
"bar": "#1E1E2E",
"surface": "#313244",
"text": "#CDD6F4",
"muted": "#A6ADC8",
"accent": "#CBA6F7"
}
},
{
"id": "gruvbox",
"name": "Gruvbox",
"light": {
"background": "#FBF1C7",
"bar": "#FBF1C7",
"surface": "#F2E5BC",
"text": "#3C3836",
"muted": "#665C54",
"accent": "#AF3A03"
},
"dark": {
"background": "#282828",
"bar": "#282828",
"surface": "#3C3836",
"text": "#EBDBB2",
"muted": "#BDAE93",
"accent": "#FE8019"
}
},
{
"id": "rose-pine",
"name": "Rosé Pine",
"light": {
"background": "#FAF4ED",
"bar": "#FAF4ED",
"surface": "#FFFAF3",
"text": "#464261",
"muted": "#716D89",
"accent": "#B4637A"
},
"dark": {
"background": "#232136",
"bar": "#232136",
"surface": "#2A273F",
"text": "#E0DEF4",
"muted": "#918DAA",
"accent": "#EA9A97"
}
},
{
"id": "tokyo-night",
"name": "Tokyo Night",
"light": {
"background": "#E1E2E7",
"bar": "#E1E2E7",
"surface": "#D0D5E3",
"text": "#3358B0",
"muted": "#4D5A8C",
"accent": "#2C76DD"
},
"dark": {
"background": "#1A1B26",
"bar": "#1A1B26",
"surface": "#292E42",
"text": "#C0CAF5",
"muted": "#A9B1D6",
"accent": "#7AA2F7"
}
},
{
"id": "everforest",
"name": "Everforest",
"light": {
"background": "#FFFBEF",
"bar": "#FFFBEF",
"surface": "#F8F5E4",
"text": "#5C6A72",
"muted": "#677367",
"accent": "#849601"
},
"dark": {
"background": "#272E33",
"bar": "#272E33",
"surface": "#2E383C",
"text": "#D3C6AA",
"muted": "#9DA9A0",
"accent": "#A7C080"
}
}
],
"fallback_note": null
}
-52
View File
@@ -1,52 +0,0 @@
use ferese_config::theme::Snapshot;
use serde_json::{Value, json};
fn fixture() -> Value {
serde_json::from_str(include_str!("fixtures/theme-v2.json")).unwrap()
}
#[test]
fn default_snapshot_preserves_wire_values_and_environment_wallpaper() {
let mut snapshot = Snapshot::default();
let wallpaper = Some(ferese_config::default_wallpaper().into());
assert_eq!(snapshot.theme.tokens.background.path, wallpaper);
assert_eq!(snapshot.presented.tokens.background.path, wallpaper);
snapshot.theme.tokens.background.path = Some("<wallpaper>".into());
snapshot.presented.tokens.background.path = Some("<wallpaper>".into());
assert_eq!(serde_json::to_value(snapshot).unwrap(), fixture());
}
#[test]
fn older_snapshots_supply_catalog_but_explicit_empty_catalog_stays_empty() {
let mut value = fixture();
value.as_object_mut().unwrap().remove("families");
value.as_object_mut().unwrap().remove("fallback_note");
let snapshot: Snapshot = serde_json::from_value(value.clone()).unwrap();
assert_eq!(snapshot.families, ferese_config::families::builtins());
assert_eq!(snapshot.fallback_note, None);
value["families"] = json!([]);
assert!(
serde_json::from_value::<Snapshot>(value.clone())
.unwrap()
.families
.is_empty()
);
value["families"] = Value::Null;
assert!(serde_json::from_value::<Snapshot>(value).is_err());
}
#[test]
fn offline_preview_resolves_without_changing_published_values() {
let before = Snapshot::default();
let document = ferese_config::Document::parse("theme { mode \"light\"; family \"gruvbox\"; }\n").unwrap();
let candidate = ferese_config::theme::resolve(
&document,
std::path::Path::new("/unused"),
"2026-10-02T12:00:00Z".parse().unwrap(),
|_| panic!("built-in preview must not read theme files"),
)
.unwrap();
assert_eq!(candidate.theme.appearance, ferese_config::theme::Appearance::Light);
assert_eq!(candidate.theme.tokens.colors.surface_base, "#FBF1C7");
assert_eq!(before, Snapshot::default());
}