Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 23 additions & 23 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 6 additions & 6 deletions crates/moon-core/src/config/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -286,7 +286,7 @@ pub struct AppConfig {
pub log_retention_days: u32,
/// Addition to base UI font sizes in logical pixels. Defaults to +3.
pub ui_font_delta: f32,
/// Dark/light MoonUI theme (plaintext settings.toml).
/// Interface theme mode (plaintext settings.toml); Graphite shares the dark colour set.
pub ui_theme_mode: UiThemeMode,
/// Overall UI geometry scale. Defaults to 1.0.
pub ui_scale: f32,
Expand All @@ -307,11 +307,11 @@ pub struct AppConfig {
pub(in crate::config) next_uid: UidCounter,
/// Terminal hotkeys (plaintext hotkeys.toml).
pub hotkeys: HotkeysConfig,
/// Chart theme per UI mode (dark/light) in separate portable theme.toml.
/// Chart theme per colour set (dark/light) in separate portable theme.toml.
pub theme: ChartThemeSet,
/// Order-line styles per theme (dark/light) in separate portable orders.toml.
/// Order-line styles per colour set (dark/light) in separate portable orders.toml.
pub orders: OrdersStyleSet,
/// Detect-type badges (code + per-type colors, per theme) in separate portable badges.json.
/// Detect-type badges (code plus per-type colours for each colour set) in badges.json.
pub badges: BadgesConfig,
/// Runtime flag (NOT serialized): `settings.toml` EXISTS but could not be read because of
/// permissions, a share, or an unhydrated cloud placeholder, so memory holds DEFAULTS rather
Expand Down Expand Up @@ -893,9 +893,9 @@ impl AppConfig {
!self.servers.is_empty() || self.next_uid.get() > Self::FIRST_ISSUED_UID
}

/// Chart theme for the active UI mode (dark/light according to `ui_theme_mode`).
/// Chart theme for the active UI mode's colour set; Graphite uses the dark entry.
pub fn chart_theme(&self) -> &ChartTheme {
self.theme.get(self.ui_theme_mode == UiThemeMode::Light)
self.theme.get(self.ui_theme_mode.is_light())
}

/// A group is meaningful only while at least one core references it. Do not save orphans,
Expand Down
18 changes: 13 additions & 5 deletions crates/moon-core/src/config/moonbot_import/apply.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,11 +59,19 @@ pub fn apply_local(
fn apply_item(cfg: &mut AppConfig, item: &SettingChange) -> bool {
match (&item.value, item.id.as_str()) {
(PlannedValue::UiThemeLight(light), "ui.theme_mode") => {
cfg.ui_theme_mode = if *light {
UiThemeMode::Light
} else {
UiThemeMode::Dark
};
// The plan carries a theme as ONE BOOLEAN, so "dark" cannot say WHICH dark theme it
// means. Writing `Dark` unconditionally would convert Graphite to Dark — and it would
// do it on a row the preview marks `same` (`plan.rs:145` compares the same boolean),
// so the user is told nothing changes and then loses their theme. Move the mode only
// when the colour SET actually differs; importing "dark" onto a mode that already
// draws the dark set is genuinely a no-op.
if *light != cfg.ui_theme_mode.is_light() {
cfg.ui_theme_mode = if *light {
UiThemeMode::Light
} else {
UiThemeMode::Dark
};
}
true
}
(PlannedValue::SplitParts(parts), "hotkey.split_parts") => {
Expand Down
27 changes: 26 additions & 1 deletion crates/moon-core/src/config/schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -121,14 +121,39 @@ pub fn clamp_chart_stack_height(value: u16) -> u16 {
value.clamp(120, 2000)
}

/// Interface theme the user picked on the General tab.
///
/// THREE modes, but only TWO colour sets. `Graphite` is a middle theme — mid-tone neutral
/// surfaces between `Dark`'s near-black and `Light` — and it shares the DARK set for badges,
/// order styles, lines and the chart theme. Nothing here is a third table.
///
/// [`Self::is_light`] is the only sanctioned way to ask which set a mode draws. A bare equality
/// test against the `Light` variant reads correctly today and then silently answers "not light,
/// therefore dark" for a fourth variant added later — which is exactly how Graphite would have
/// been handed the light set at half of its call sites.
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum UiThemeMode {
Light,
/// Mid-tone neutral surfaces with light text; draws the DARK colour set.
Graphite,
#[default]
Dark,
}

impl UiThemeMode {
/// Whether this mode draws the LIGHT colour set.
///
/// `Graphite` is dark-leaning and answers `false`, so every per-mode colour table
/// (`badges`, `orders`, `lines`, the chart theme) keeps its two entries.
///
/// Returns:
/// `true` only for [`UiThemeMode::Light`].
pub const fn is_light(self) -> bool {
matches!(self, UiThemeMode::Light)
}
}

/// Interface theme a profile receives when `settings.toml` has never been written.
///
/// Deliberately NOT the `Default` of [`UiThemeMode`]. `Default` is what serde substitutes for an
Expand Down Expand Up @@ -314,7 +339,7 @@ pub struct SettingsFile {
/// into 13 px at 1x without zooming the whole interface.
#[serde(default = "default_ui_font_delta")]
pub ui_font_delta: f32,
/// Dark/light MoonUI theme. This plaintext setting is neither a secret nor the chart theme.
/// Interface theme mode. Graphite shares dark colour data; this plaintext setting is not secret.
#[serde(default)]
pub ui_theme_mode: UiThemeMode,
/// Overall UI geometry scale. It currently has no public control but is stored beside
Expand Down
29 changes: 29 additions & 0 deletions crates/moon-core/src/config/schema/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,3 +82,32 @@ fn a_core_issued_strategy_id_cannot_break_the_whole_settings_file() {
);
}
}

/// Catches dropping `config/schema.rs:UiThemeMode`'s `serde(rename_all = "lowercase")`.
/// Without it, Graphite persists as `"Graphite"`, which the same build cannot read from settings.
#[test]
fn graphite_theme_mode_round_trips_as_the_lowercase_settings_value() {
let stored = SettingsFile {
ui_theme_mode: UiThemeMode::Graphite,
..SettingsFile::default()
};
let persisted = toml::to_string(&stored).expect("settings file must serialize");
assert!(
persisted
.lines()
.any(|line| line == "ui_theme_mode = \"graphite\""),
"settings.toml must persist the lowercase Graphite spelling"
);
let reread: SettingsFile =
toml::from_str("ui_theme_mode = \"graphite\"").expect("stored graphite must load");
assert_eq!(reread.ui_theme_mode, UiThemeMode::Graphite);
}

/// Catches changing `config/schema.rs:UiThemeMode::is_light` to include Graphite.
/// That would route Graphite through every light color branch in badges, orders, lines, and charts.
#[test]
fn graphite_is_dark_while_light_remains_the_only_light_mode() {
assert!(UiThemeMode::Light.is_light());
assert!(!UiThemeMode::Graphite.is_light());
assert!(!UiThemeMode::Dark.is_light());
}
11 changes: 10 additions & 1 deletion crates/moon-core/src/config/tests.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//! Runtime configuration-constructor regressions.

use super::{AppConfig, BadgesConfig, ChartThemeSet, OrdersStyleSet};
use super::{AppConfig, BadgesConfig, ChartThemeSet, OrdersStyleSet, UiThemeMode};

/// Regression target: removing `ensure_server_group_configs` from
/// `AppConfig::build_plaintext_config` leaves the environment-backed core without durable
Expand Down Expand Up @@ -111,3 +111,12 @@ fn a_plaintext_config_keeps_the_settings_it_was_given() {
// quiet minute takes the screenshot with it.
assert_eq!(config.main_idle_close_secs, 0);
}

/// Catches `config/mod.rs:AppConfig::chart_theme` treating Graphite as light.
/// If it selects the light chart set, Graphite's dark interface paints charts with the wrong palette.
#[test]
fn graphite_chart_theme_uses_the_existing_dark_chart_set() {
let mut config = AppConfig::blank(None);
config.ui_theme_mode = UiThemeMode::Graphite;
assert_eq!(config.chart_theme(), &config.theme.dark);
}
Loading