Decouple `theme_importer` from old GPUI 1 crates (#3866)

Marshall Bowers created

This PR decouples the `theme_importer` from the `theme` and `gpui`
crates.

We achieve this by inlining all of the relevant bits needed to
deserialize Zed1 themes.

This will allow us to continue to import Zed1 themes after we land
#3862.

Release Notes:

- N/A

Change summary

Cargo.lock                                  |    3 
crates/theme_importer/Cargo.toml            |    5 
crates/theme_importer/src/main.rs           |   25 
crates/theme_importer/src/zed1.rs           |    1 
crates/theme_importer/src/zed1/converter.rs |   20 
crates/theme_importer/src/zed1/theme.rs     | 1518 +++++++++++++++++++++++
6 files changed, 1,542 insertions(+), 30 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -9958,17 +9958,16 @@ dependencies = [
  "anyhow",
  "clap 4.4.4",
  "convert_case 0.6.0",
- "gpui",
  "gpui2",
  "indexmap 1.9.3",
  "json_comments",
  "log",
  "palette",
+ "pathfinder_color",
  "rust-embed",
  "serde",
  "simplelog",
  "strum",
- "theme",
  "theme2",
  "uuid 1.4.1",
 ]

crates/theme_importer/Cargo.toml 🔗

@@ -9,16 +9,15 @@ any_ascii = "0.3.2"
 anyhow.workspace = true
 clap = { version = "4.4", features = ["derive"] }
 convert_case = "0.6.0"
-gpui = { package = "gpui2", path = "../gpui2", features = ["allow-multiple-gpui-versions"] }
-gpui1 = { package = "gpui", path = "../gpui" }
+gpui = { package = "gpui2", path = "../gpui2" }
 indexmap = { version = "1.6.2", features = ["serde"] }
 json_comments = "0.2.2"
 log.workspace = true
 palette = { version = "0.7.3", default-features = false, features = ["std"] }
+pathfinder_color = "0.5"
 rust-embed.workspace = true
 serde.workspace = true
 simplelog = "0.9"
 strum = { version = "0.25.0", features = ["derive"] }
 theme = { package = "theme2", path = "../theme2", features = ["importing-themes"] }
-theme1 = { package = "theme", path = "../theme" }
 uuid.workspace = true

crates/theme_importer/src/main.rs 🔗

@@ -11,25 +11,23 @@ use std::io::Write;
 use std::path::PathBuf;
 use std::process::Command;
 use std::str::FromStr;
-use std::sync::Arc;
 
 use any_ascii::any_ascii;
 use anyhow::{anyhow, Context, Result};
 use clap::Parser;
 use convert_case::{Case, Casing};
-use gpui::{serde_json, AssetSource};
+use gpui::serde_json;
 use indexmap::IndexMap;
 use json_comments::StripComments;
 use log::LevelFilter;
 use serde::Deserialize;
 use simplelog::{TermLogger, TerminalMode};
 use theme::{Appearance, UserTheme, UserThemeFamily};
-use theme1::Theme as Zed1Theme;
 
-use crate::assets::Assets;
 use crate::theme_printer::UserThemeFamilyPrinter;
 use crate::vscode::VsCodeTheme;
 use crate::vscode::VsCodeThemeConverter;
+use crate::zed1::theme::Theme as Zed1Theme;
 use crate::zed1::Zed1ThemeConverter;
 
 #[derive(Debug, Deserialize)]
@@ -210,19 +208,6 @@ fn main() -> Result<()> {
             .map(|family| (family.to_string(), Vec::new())),
     );
 
-    let platform = gpui1::platform::current::platform();
-    let zed1_font_cache = Arc::new(gpui1::FontCache::new(platform.fonts()));
-
-    let mut embedded_fonts = Vec::new();
-    for font_path in Assets.list("fonts")? {
-        if font_path.ends_with(".ttf") {
-            let font_bytes = Assets.load(&font_path)?.to_vec();
-            embedded_fonts.push(Arc::from(font_bytes));
-        }
-    }
-
-    platform.fonts().add_fonts(&embedded_fonts)?;
-
     for entry in fs::read_dir(&zed1_themes_path)? {
         let entry = entry?;
 
@@ -251,10 +236,8 @@ fn main() -> Result<()> {
 
         let theme_without_comments = StripComments::new(theme_file);
 
-        let zed1_theme: Zed1Theme = gpui1::fonts::with_font_cache(zed1_font_cache.clone(), || {
-            serde_json::from_reader(theme_without_comments)
-                .context(format!("failed to parse theme {theme_file_path:?}"))
-        })?;
+        let zed1_theme: Zed1Theme = serde_json::from_reader(theme_without_comments)
+            .context(format!("failed to parse theme {theme_file_path:?}"))?;
 
         let theme_name = zed1_theme.meta.name.clone();
 

crates/theme_importer/src/zed1/converter.rs 🔗

@@ -1,13 +1,15 @@
 use anyhow::{Context, Result};
 use gpui::{serde_json, Hsla, Rgba};
-use gpui1::color::Color as Zed1Color;
-use gpui1::fonts::HighlightStyle as Zed1HighlightStyle;
 use theme::{
     color_alpha, Appearance, PlayerColor, PlayerColors, StatusColorsRefinement,
     ThemeColorsRefinement, UserFontStyle, UserFontWeight, UserHighlightStyle, UserSyntaxTheme,
     UserTheme, UserThemeStylesRefinement,
 };
-use theme1::{ColorScheme, Theme as Zed1Theme};
+
+use crate::zed1::theme::{
+    Color as Zed1Color, ColorScheme, HighlightStyle as Zed1HighlightStyle, Theme as Zed1Theme,
+    Weight,
+};
 
 fn zed1_color_to_hsla(color: Zed1Color) -> Hsla {
     let r = color.r as f32 / 255.;
@@ -30,7 +32,17 @@ fn zed1_highlight_style_to_user_highlight_style(
                 UserFontStyle::Normal
             }
         }),
-        font_weight: highlight.weight.map(|weight| UserFontWeight(weight.0)),
+        font_weight: highlight.weight.map(|weight| match weight {
+            Weight::thin => UserFontWeight::THIN,
+            Weight::extra_light => UserFontWeight::EXTRA_LIGHT,
+            Weight::light => UserFontWeight::LIGHT,
+            Weight::normal => UserFontWeight::NORMAL,
+            Weight::medium => UserFontWeight::MEDIUM,
+            Weight::semibold => UserFontWeight::SEMIBOLD,
+            Weight::bold => UserFontWeight::BOLD,
+            Weight::extra_bold => UserFontWeight::EXTRA_BOLD,
+            Weight::black => UserFontWeight::BLACK,
+        }),
     }
 }
 

crates/theme_importer/src/zed1/theme.rs 🔗

@@ -0,0 +1,1518 @@
+#![allow(unused)]
+
+use std::borrow::Cow;
+use std::collections::HashMap;
+use std::fmt;
+use std::ops::{Deref, DerefMut};
+use std::sync::Arc;
+
+use gpui::serde_json::{self, Value};
+use pathfinder_color::ColorU;
+use serde::de::{self, DeserializeOwned, Unexpected};
+use serde::{Deserialize, Deserializer};
+
+#[derive(Clone, Copy, Default, PartialEq, Eq, Hash, PartialOrd, Ord)]
+#[repr(transparent)]
+pub struct Color(pub ColorU);
+
+impl Color {
+    pub fn from_u32(rgba: u32) -> Self {
+        Self(ColorU::from_u32(rgba))
+    }
+}
+
+impl Deref for Color {
+    type Target = ColorU;
+    fn deref(&self) -> &Self::Target {
+        &self.0
+    }
+}
+
+impl DerefMut for Color {
+    fn deref_mut(&mut self) -> &mut Self::Target {
+        &mut self.0
+    }
+}
+
+impl fmt::Debug for Color {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        self.0.fmt(f)
+    }
+}
+
+impl<'de> Deserialize<'de> for Color {
+    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+    where
+        D: Deserializer<'de>,
+    {
+        let literal: Cow<str> = Deserialize::deserialize(deserializer)?;
+        if let Some(digits) = literal.strip_prefix('#') {
+            if let Ok(value) = u32::from_str_radix(digits, 16) {
+                if digits.len() == 6 {
+                    return Ok(Color::from_u32((value << 8) | 0xFF));
+                } else if digits.len() == 8 {
+                    return Ok(Color::from_u32(value));
+                }
+            }
+        }
+        Err(de::Error::invalid_value(
+            Unexpected::Str(literal.as_ref()),
+            &"#RRGGBB[AA]",
+        ))
+    }
+}
+
+#[derive(Clone, Debug, Default, Deserialize)]
+pub struct TextStyle {
+    pub color: Color,
+}
+
+#[derive(Clone, Debug, Default)]
+pub struct HighlightStyle {
+    pub color: Option<Color>,
+    pub weight: Option<Weight>,
+    pub italic: Option<bool>,
+}
+
+impl<'de> Deserialize<'de> for HighlightStyle {
+    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+    where
+        D: serde::Deserializer<'de>,
+    {
+        #[derive(Deserialize)]
+        struct HighlightStyleJson {
+            color: Option<Color>,
+            weight: Option<Weight>,
+            italic: Option<bool>,
+        }
+
+        let json = serde_json::Value::deserialize(deserializer)?;
+        if json.is_object() {
+            let style: HighlightStyleJson =
+                serde_json::from_value(json).map_err(de::Error::custom)?;
+
+            Ok(Self {
+                color: style.color,
+                weight: style.weight,
+                italic: style.italic,
+            })
+        } else {
+            Ok(Self {
+                color: serde_json::from_value(json).map_err(de::Error::custom)?,
+                ..Default::default()
+            })
+        }
+    }
+}
+
+#[allow(non_camel_case_types)]
+#[derive(Clone, Debug, Deserialize)]
+pub enum Weight {
+    thin,
+    extra_light,
+    light,
+    normal,
+    medium,
+    semibold,
+    bold,
+    extra_bold,
+    black,
+}
+
+#[derive(Clone, Copy, Debug, Default, Deserialize)]
+pub struct ContainerStyle {
+    #[serde(rename = "background")]
+    pub background_color: Option<Color>,
+    #[serde(rename = "overlay")]
+    pub overlay_color: Option<Color>,
+    #[serde(default)]
+    pub border: Border,
+}
+
+#[derive(Clone, Deserialize, Default)]
+pub struct SvgStyle {
+    pub color: Color,
+}
+
+#[derive(Clone, Deserialize, Default)]
+pub struct IconStyle {
+    pub icon: SvgStyle,
+    pub container: ContainerStyle,
+}
+
+#[derive(Copy, Clone, Debug, Default)]
+pub struct Border {
+    pub color: Color,
+    pub width: f32,
+    pub overlay: bool,
+    pub top: bool,
+    pub bottom: bool,
+    pub left: bool,
+    pub right: bool,
+}
+
+impl<'de> Deserialize<'de> for Border {
+    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+    where
+        D: serde::Deserializer<'de>,
+    {
+        #[derive(Deserialize)]
+        struct BorderData {
+            pub width: f32,
+            pub color: Color,
+            #[serde(default)]
+            pub overlay: bool,
+            #[serde(default)]
+            pub top: bool,
+            #[serde(default)]
+            pub right: bool,
+            #[serde(default)]
+            pub bottom: bool,
+            #[serde(default)]
+            pub left: bool,
+        }
+
+        let data = BorderData::deserialize(deserializer)?;
+        let mut border = Border {
+            width: data.width,
+            color: data.color,
+            overlay: data.overlay,
+            top: data.top,
+            bottom: data.bottom,
+            left: data.left,
+            right: data.right,
+        };
+        if !border.top && !border.bottom && !border.left && !border.right {
+            border.top = true;
+            border.bottom = true;
+            border.left = true;
+            border.right = true;
+        }
+        Ok(border)
+    }
+}
+
+#[derive(Clone, Deserialize, Default)]
+pub struct TooltipStyle {
+    #[serde(flatten)]
+    pub container: ContainerStyle,
+    pub text: TextStyle,
+    keystroke: KeystrokeStyle,
+    pub max_text_width: Option<f32>,
+}
+
+#[derive(Clone, Deserialize, Default)]
+pub struct KeystrokeStyle {
+    #[serde(flatten)]
+    container: ContainerStyle,
+    #[serde(flatten)]
+    text: TextStyle,
+}
+
+#[derive(Copy, Clone, Default, Deserialize)]
+pub struct ImageStyle {
+    #[serde(default)]
+    pub border: Border,
+    #[serde(default)]
+    pub corner_radius: f32,
+    #[serde(default)]
+    pub height: Option<f32>,
+    #[serde(default)]
+    pub width: Option<f32>,
+    #[serde(default)]
+    pub grayscale: bool,
+}
+
+#[derive(Deserialize, Default)]
+pub struct Theme {
+    #[serde(default)]
+    pub meta: ThemeMeta,
+    pub workspace: Workspace,
+    pub context_menu: ContextMenu,
+    pub toolbar_dropdown_menu: DropdownMenu,
+    pub copilot: Copilot,
+    pub collab_panel: CollabPanel,
+    pub project_panel: ProjectPanel,
+    pub chat_panel: ChatPanel,
+    pub notification_panel: NotificationPanel,
+    pub command_palette: CommandPalette,
+    pub picker: Picker,
+    pub editor: Editor,
+    pub search: Search,
+    pub project_diagnostics: ProjectDiagnostics,
+    pub shared_screen: ContainerStyle,
+    pub contact_notification: ContactNotification,
+    pub update_notification: UpdateNotification,
+    pub simple_message_notification: MessageNotification,
+    pub project_shared_notification: ProjectSharedNotification,
+    pub incoming_call_notification: IncomingCallNotification,
+    pub tooltip: TooltipStyle,
+    pub terminal: TerminalStyle,
+    pub assistant: AssistantStyle,
+    pub feedback: FeedbackStyle,
+    pub welcome: WelcomeStyle,
+    pub titlebar: Titlebar,
+    // Nathan: New elements are styled in Rust, directly from the base theme.
+    // We store it on the legacy theme so we can mix both kinds of elements during the transition.
+    pub base_theme: serde_json::Value,
+}
+
+#[derive(Deserialize, Default, Clone)]
+pub struct ThemeMeta {
+    #[serde(skip_deserializing)]
+    pub id: usize,
+    pub name: String,
+    pub is_light: bool,
+}
+
+#[derive(Deserialize, Default)]
+pub struct Workspace {
+    pub background: Color,
+    pub blank_pane: BlankPaneStyle,
+    pub tab_bar: TabBar,
+    pub pane_divider: Border,
+    pub leader_border_opacity: f32,
+    pub leader_border_width: f32,
+    pub dock: Dock,
+    pub status_bar: StatusBar,
+    pub toolbar: Toolbar,
+    pub disconnected_overlay: ContainedText,
+    pub modal: ContainerStyle,
+    pub zoomed_panel_foreground: ContainerStyle,
+    pub zoomed_pane_foreground: ContainerStyle,
+    pub zoomed_background: ContainerStyle,
+    pub notification: ContainerStyle,
+    pub notifications: Notifications,
+    pub joining_project_avatar: ImageStyle,
+    pub joining_project_message: ContainedText,
+    pub external_location_message: ContainedText,
+    pub drop_target_overlay_color: Color,
+}
+
+#[derive(Clone, Deserialize, Default)]
+pub struct BlankPaneStyle {
+    pub logo: SvgStyle,
+    pub logo_shadow: SvgStyle,
+    pub logo_container: ContainerStyle,
+    pub keyboard_hints: ContainerStyle,
+    pub keyboard_hint: Interactive<ContainedText>,
+    pub keyboard_hint_width: f32,
+}
+
+#[derive(Clone, Deserialize, Default)]
+pub struct Titlebar {
+    #[serde(flatten)]
+    pub container: ContainerStyle,
+    pub height: f32,
+    pub menu: TitlebarMenu,
+    pub project_menu_button: Toggleable<Interactive<ContainedText>>,
+    pub git_menu_button: Toggleable<Interactive<ContainedText>>,
+    pub project_host: Interactive<ContainedText>,
+    pub item_spacing: f32,
+    pub face_pile_spacing: f32,
+    pub avatar_ribbon: AvatarRibbon,
+    pub follower_avatar_overlap: f32,
+    pub leader_selection: ContainerStyle,
+    pub offline_icon: OfflineIcon,
+    pub leader_avatar: AvatarStyle,
+    pub follower_avatar: AvatarStyle,
+    pub inactive_avatar_grayscale: bool,
+    pub sign_in_button: Toggleable<Interactive<ContainedText>>,
+    pub outdated_warning: ContainedText,
+    pub share_button: Toggleable<Interactive<ContainedText>>,
+    pub muted: Color,
+    pub speaking: Color,
+    pub screen_share_button: Toggleable<Interactive<IconButton>>,
+    pub toggle_contacts_button: Toggleable<Interactive<IconButton>>,
+    pub toggle_microphone_button: Toggleable<Interactive<IconButton>>,
+    pub toggle_speakers_button: Toggleable<Interactive<IconButton>>,
+    pub leave_call_button: Interactive<IconButton>,
+    pub toggle_contacts_badge: ContainerStyle,
+    pub user_menu: UserMenu,
+}
+
+#[derive(Clone, Deserialize, Default)]
+pub struct TitlebarMenu {
+    pub width: f32,
+    pub height: f32,
+}
+
+#[derive(Clone, Deserialize, Default)]
+pub struct UserMenu {
+    pub user_menu_button_online: UserMenuButton,
+    pub user_menu_button_offline: UserMenuButton,
+}
+
+#[derive(Clone, Deserialize, Default)]
+pub struct UserMenuButton {
+    pub user_menu: Toggleable<Interactive<Icon>>,
+    pub avatar: AvatarStyle,
+    pub icon: Icon,
+}
+
+#[derive(Copy, Clone, Deserialize, Default)]
+pub struct AvatarStyle {
+    #[serde(flatten)]
+    pub image: ImageStyle,
+    pub outer_width: f32,
+    pub outer_corner_radius: f32,
+}
+
+#[derive(Deserialize, Default, Clone)]
+pub struct Copilot {
+    pub out_link_icon: Interactive<IconStyle>,
+    // pub modal: ModalStyle,
+    pub auth: CopilotAuth,
+}
+
+pub type CopilotCTAButton = Interactive<ContainedText>;
+
+#[derive(Deserialize, Default, Clone)]
+pub struct CopilotAuth {
+    pub content_width: f32,
+    pub prompting: CopilotAuthPrompting,
+    pub not_authorized: CopilotAuthNotAuthorized,
+    pub authorized: CopilotAuthAuthorized,
+    pub cta_button: CopilotCTAButton,
+    pub header: IconStyle,
+}
+
+#[derive(Deserialize, Default, Clone)]
+pub struct CopilotAuthPrompting {
+    pub subheading: ContainedText,
+    pub hint: ContainedText,
+    pub device_code: DeviceCode,
+}
+
+#[derive(Deserialize, Default, Clone)]
+pub struct DeviceCode {
+    pub text: TextStyle,
+    pub cta: CopilotCTAButton,
+    pub left: f32,
+    pub left_container: ContainerStyle,
+    pub right: f32,
+    pub right_container: Interactive<ContainerStyle>,
+}
+
+#[derive(Deserialize, Default, Clone)]
+pub struct CopilotAuthNotAuthorized {
+    pub subheading: ContainedText,
+    pub warning: ContainedText,
+}
+
+#[derive(Deserialize, Default, Clone)]
+pub struct CopilotAuthAuthorized {
+    pub subheading: ContainedText,
+    pub hint: ContainedText,
+}
+
+#[derive(Deserialize, Default)]
+pub struct CollabPanel {
+    #[serde(flatten)]
+    pub container: ContainerStyle,
+    // pub disclosure: DisclosureStyle<()>,
+    pub list_empty_state: Toggleable<Interactive<ContainedText>>,
+    pub list_empty_icon: Icon,
+    pub list_empty_label_container: ContainerStyle,
+    pub log_in_button: Interactive<ContainedText>,
+    pub channel_editor: ContainerStyle,
+    pub channel_hash: Icon,
+    pub channel_note_active_color: Color,
+    pub tabbed_modal: TabbedModal,
+    pub contact_finder: ContactFinder,
+    pub channel_modal: ChannelModal,
+    pub user_query_editor: FieldEditor,
+    pub user_query_editor_height: f32,
+    pub leave_call_button: Toggleable<Interactive<IconButton>>,
+    pub add_contact_button: Toggleable<Interactive<IconButton>>,
+    pub add_channel_button: Toggleable<Interactive<IconButton>>,
+    pub header_row: ContainedText,
+    pub dragged_over_header: ContainerStyle,
+    pub subheader_row: Toggleable<Interactive<ContainedText>>,
+    pub leave_call: Interactive<ContainedText>,
+    pub contact_row: Toggleable<Interactive<ContainerStyle>>,
+    pub channel_row: Toggleable<Interactive<ContainerStyle>>,
+    pub channel_name: Toggleable<ContainedText>,
+    pub row_height: f32,
+    pub project_row: Toggleable<Interactive<ProjectRow>>,
+    pub tree_branch: Toggleable<Interactive<TreeBranch>>,
+    pub contact_avatar: ImageStyle,
+    pub channel_avatar: ImageStyle,
+    pub extra_participant_label: ContainedText,
+    pub contact_status_free: ContainerStyle,
+    pub contact_status_busy: ContainerStyle,
+    pub contact_username: ContainedText,
+    pub contact_button: Interactive<IconButton>,
+    pub contact_button_spacing: f32,
+    pub channel_indent: f32,
+    pub disabled_button: IconButton,
+    pub section_icon_size: f32,
+    pub calling_indicator: ContainedText,
+    pub face_overlap: f32,
+}
+
+#[derive(Deserialize, Default)]
+pub struct TabbedModal {
+    pub tab_button: Toggleable<Interactive<ContainedText>>,
+    pub modal: ContainerStyle,
+    pub header: ContainerStyle,
+    pub body: ContainerStyle,
+    pub title: ContainedText,
+    pub visibility_toggle: Interactive<ContainedText>,
+    pub channel_link: Interactive<ContainedText>,
+    pub picker: Picker,
+    pub max_height: f32,
+    pub max_width: f32,
+    pub row_height: f32,
+}
+
+#[derive(Deserialize, Default)]
+pub struct ChannelModal {
+    pub contact_avatar: ImageStyle,
+    pub contact_username: ContainerStyle,
+    pub remove_member_button: ContainedText,
+    pub cancel_invite_button: ContainedText,
+    pub member_icon: IconButton,
+    pub invitee_icon: IconButton,
+    pub member_tag: ContainedText,
+}
+
+#[derive(Deserialize, Default)]
+pub struct ProjectRow {
+    #[serde(flatten)]
+    pub container: ContainerStyle,
+    pub icon: Icon,
+    pub name: ContainedText,
+}
+
+#[derive(Deserialize, Default, Clone, Copy)]
+pub struct TreeBranch {
+    pub width: f32,
+    pub color: Color,
+}
+
+#[derive(Deserialize, Default)]
+pub struct ContactFinder {
+    pub contact_avatar: ImageStyle,
+    pub contact_username: ContainerStyle,
+    pub contact_button: IconButton,
+    pub disabled_contact_button: IconButton,
+}
+
+#[derive(Deserialize, Default)]
+pub struct DropdownMenu {
+    #[serde(flatten)]
+    pub container: ContainerStyle,
+    pub header: Interactive<DropdownMenuItem>,
+    pub section_header: ContainedText,
+    pub item: Toggleable<Interactive<DropdownMenuItem>>,
+    pub row_height: f32,
+}
+
+#[derive(Deserialize, Default)]
+pub struct DropdownMenuItem {
+    #[serde(flatten)]
+    pub container: ContainerStyle,
+    #[serde(flatten)]
+    pub text: TextStyle,
+    pub secondary_text: Option<TextStyle>,
+    #[serde(default)]
+    pub secondary_text_spacing: f32,
+}
+
+#[derive(Clone, Deserialize, Default)]
+pub struct TabBar {
+    #[serde(flatten)]
+    pub container: ContainerStyle,
+    pub pane_button: Toggleable<Interactive<IconButton>>,
+    pub pane_button_container: ContainerStyle,
+    pub active_pane: TabStyles,
+    pub inactive_pane: TabStyles,
+    pub dragged_tab: Tab,
+    pub height: f32,
+    pub nav_button: Interactive<IconButton>,
+}
+
+impl TabBar {
+    pub fn tab_style(&self, pane_active: bool, tab_active: bool) -> &Tab {
+        let tabs = if pane_active {
+            &self.active_pane
+        } else {
+            &self.inactive_pane
+        };
+
+        if tab_active {
+            &tabs.active_tab
+        } else {
+            &tabs.inactive_tab
+        }
+    }
+}
+
+#[derive(Clone, Deserialize, Default)]
+pub struct TabStyles {
+    pub active_tab: Tab,
+    pub inactive_tab: Tab,
+}
+
+#[derive(Clone, Deserialize, Default)]
+pub struct AvatarRibbon {
+    #[serde(flatten)]
+    pub container: ContainerStyle,
+    pub width: f32,
+    pub height: f32,
+}
+
+#[derive(Clone, Deserialize, Default)]
+pub struct OfflineIcon {
+    #[serde(flatten)]
+    pub container: ContainerStyle,
+    pub width: f32,
+    pub color: Color,
+}
+
+#[derive(Clone, Debug, Deserialize, Default)]
+pub struct LabelStyle {
+    pub text: TextStyle,
+    pub highlight_text: Option<TextStyle>,
+}
+
+#[derive(Clone, Deserialize, Default)]
+pub struct Tab {
+    pub height: f32,
+    #[serde(flatten)]
+    pub container: ContainerStyle,
+    #[serde(flatten)]
+    pub label: LabelStyle,
+    pub description: ContainedText,
+    pub spacing: f32,
+    pub close_icon_width: f32,
+    pub type_icon_width: f32,
+    pub icon_close: Color,
+    pub icon_close_active: Color,
+    pub icon_dirty: Color,
+    pub icon_conflict: Color,
+    pub git: GitProjectStatus,
+}
+
+#[derive(Clone, Deserialize, Default)]
+pub struct Toolbar {
+    #[serde(flatten)]
+    pub container: ContainerStyle,
+    pub height: f32,
+    pub item_spacing: f32,
+    pub toggleable_tool: Toggleable<Interactive<IconButton>>,
+    pub toggleable_text_tool: Toggleable<Interactive<ContainedText>>,
+    pub breadcrumb_height: f32,
+    pub breadcrumbs: Interactive<ContainedText>,
+}
+
+#[derive(Clone, Deserialize, Default)]
+pub struct Notifications {
+    #[serde(flatten)]
+    pub container: ContainerStyle,
+    pub width: f32,
+}
+
+#[derive(Clone, Deserialize, Default)]
+pub struct Search {
+    #[serde(flatten)]
+    pub container: ContainerStyle,
+    pub editor: FindEditor,
+    pub invalid_editor: ContainerStyle,
+    pub option_button_group: ContainerStyle,
+    pub include_exclude_editor: FindEditor,
+    pub invalid_include_exclude_editor: ContainerStyle,
+    pub include_exclude_inputs: ContainedText,
+    pub option_button_component: ToggleIconButtonStyle,
+    pub match_background: Color,
+    pub match_index: ContainedText,
+    pub major_results_status: TextStyle,
+    pub minor_results_status: TextStyle,
+    pub editor_icon: IconStyle,
+    pub mode_button: Toggleable<Interactive<ContainedText>>,
+    pub nav_button: Toggleable<Interactive<ContainedLabel>>,
+    pub search_bar_row_height: f32,
+    pub search_row_spacing: f32,
+    pub option_button_height: f32,
+    pub modes_container: ContainerStyle,
+    pub replace_icon: IconStyle,
+    // Used for filters and replace
+    pub option_button: Toggleable<Interactive<IconButton>>,
+    pub action_button: IconButtonStyle,
+}
+
+#[derive(Clone, Deserialize, Default)]
+pub struct FindEditor {
+    #[serde(flatten)]
+    pub input: FieldEditor,
+    pub min_width: f32,
+    pub max_width: f32,
+}
+
+#[derive(Deserialize, Default)]
+pub struct StatusBar {
+    #[serde(flatten)]
+    pub container: ContainerStyle,
+    pub height: f32,
+    pub item_spacing: f32,
+    pub cursor_position: TextStyle,
+    pub vim_mode_indicator: ContainedText,
+    pub active_language: Interactive<ContainedText>,
+    pub auto_update_progress_message: TextStyle,
+    pub auto_update_done_message: TextStyle,
+    pub lsp_status: Interactive<StatusBarLspStatus>,
+    pub panel_buttons: StatusBarPanelButtons,
+    pub diagnostic_summary: Interactive<StatusBarDiagnosticSummary>,
+    pub diagnostic_message: Interactive<ContainedText>,
+}
+
+#[derive(Deserialize, Default)]
+pub struct StatusBarPanelButtons {
+    pub group_left: ContainerStyle,
+    pub group_bottom: ContainerStyle,
+    pub group_right: ContainerStyle,
+    pub button: Toggleable<Interactive<PanelButton>>,
+}
+
+#[derive(Deserialize, Default)]
+pub struct StatusBarDiagnosticSummary {
+    pub container_ok: ContainerStyle,
+    pub container_warning: ContainerStyle,
+    pub container_error: ContainerStyle,
+    pub text: TextStyle,
+    pub icon_color_ok: Color,
+    pub icon_color_warning: Color,
+    pub icon_color_error: Color,
+    pub height: f32,
+    pub icon_width: f32,
+    pub icon_spacing: f32,
+    pub summary_spacing: f32,
+}
+
+#[derive(Deserialize, Default)]
+pub struct StatusBarLspStatus {
+    #[serde(flatten)]
+    pub container: ContainerStyle,
+    pub height: f32,
+    pub icon_spacing: f32,
+    pub icon_color: Color,
+    pub icon_width: f32,
+    pub message: TextStyle,
+}
+
+#[derive(Deserialize, Default)]
+pub struct Dock {
+    pub left: ContainerStyle,
+    pub bottom: ContainerStyle,
+    pub right: ContainerStyle,
+}
+
+#[derive(Clone, Deserialize, Default)]
+pub struct PanelButton {
+    #[serde(flatten)]
+    pub container: ContainerStyle,
+    pub icon_color: Color,
+    pub icon_size: f32,
+    pub label: ContainedText,
+}
+
+#[derive(Deserialize, Default)]
+pub struct ProjectPanel {
+    #[serde(flatten)]
+    pub container: ContainerStyle,
+    pub entry: Toggleable<Interactive<ProjectPanelEntry>>,
+    pub dragged_entry: ProjectPanelEntry,
+    pub ignored_entry: Toggleable<Interactive<ProjectPanelEntry>>,
+    pub cut_entry: Toggleable<Interactive<ProjectPanelEntry>>,
+    pub filename_editor: FieldEditor,
+    pub indent_width: f32,
+    pub open_project_button: Interactive<ContainedText>,
+}
+
+#[derive(Clone, Debug, Deserialize, Default)]
+pub struct ProjectPanelEntry {
+    pub height: f32,
+    #[serde(flatten)]
+    pub container: ContainerStyle,
+    pub text: TextStyle,
+    pub icon_size: f32,
+    pub icon_color: Color,
+    pub chevron_color: Color,
+    pub chevron_size: f32,
+    pub icon_spacing: f32,
+    pub status: EntryStatus,
+}
+
+#[derive(Clone, Debug, Deserialize, Default)]
+pub struct EntryStatus {
+    pub git: GitProjectStatus,
+}
+
+#[derive(Clone, Debug, Deserialize, Default)]
+pub struct GitProjectStatus {
+    pub modified: Color,
+    pub inserted: Color,
+    pub conflict: Color,
+}
+
+#[derive(Clone, Debug, Deserialize, Default)]
+pub struct ContextMenu {
+    #[serde(flatten)]
+    pub container: ContainerStyle,
+    pub item: Toggleable<Interactive<ContextMenuItem>>,
+    pub keystroke_margin: f32,
+    pub separator: ContainerStyle,
+}
+
+#[derive(Clone, Debug, Deserialize, Default)]
+pub struct ContextMenuItem {
+    #[serde(flatten)]
+    pub container: ContainerStyle,
+    pub label: TextStyle,
+    pub keystroke: ContainedText,
+    pub icon_width: f32,
+    pub icon_spacing: f32,
+}
+
+#[derive(Debug, Deserialize, Default)]
+pub struct CommandPalette {
+    pub key: Toggleable<ContainedLabel>,
+    pub keystroke_spacing: f32,
+}
+
+#[derive(Deserialize, Default)]
+pub struct InviteLink {
+    #[serde(flatten)]
+    pub container: ContainerStyle,
+    #[serde(flatten)]
+    pub label: LabelStyle,
+    pub icon: Icon,
+}
+
+#[derive(Deserialize, Clone, Copy, Default)]
+pub struct Icon {
+    #[serde(flatten)]
+    pub container: ContainerStyle,
+    pub color: Color,
+    pub width: f32,
+}
+
+#[derive(Deserialize, Clone, Copy, Default)]
+pub struct IconButton {
+    #[serde(flatten)]
+    pub container: ContainerStyle,
+    pub color: Color,
+    pub icon_width: f32,
+    pub button_width: f32,
+}
+
+#[derive(Deserialize, Default)]
+pub struct ChatPanel {
+    #[serde(flatten)]
+    pub container: ContainerStyle,
+    pub list: ContainerStyle,
+    pub channel_select: ChannelSelect,
+    pub input_editor: FieldEditor,
+    pub avatar: AvatarStyle,
+    pub avatar_container: ContainerStyle,
+    pub rich_text: RichTextStyle,
+    pub message_sender: ContainedText,
+    pub message_timestamp: ContainedText,
+    pub message: Interactive<ContainerStyle>,
+    pub continuation_message: Interactive<ContainerStyle>,
+    pub pending_message: Interactive<ContainerStyle>,
+    pub last_message_bottom_spacing: f32,
+    pub sign_in_prompt: Interactive<TextStyle>,
+    pub icon_button: Interactive<IconButton>,
+}
+
+#[derive(Clone, Deserialize, Default)]
+pub struct RichTextStyle {
+    pub text: TextStyle,
+    pub mention_highlight: HighlightStyle,
+    pub mention_background: Option<Color>,
+    pub self_mention_highlight: HighlightStyle,
+    pub self_mention_background: Option<Color>,
+    pub code_background: Option<Color>,
+}
+
+#[derive(Deserialize, Default)]
+pub struct NotificationPanel {
+    #[serde(flatten)]
+    pub container: ContainerStyle,
+    pub title: ContainedText,
+    pub title_icon: SvgStyle,
+    pub title_height: f32,
+    pub list: ContainerStyle,
+    pub avatar: AvatarStyle,
+    pub avatar_container: ContainerStyle,
+    pub sign_in_prompt: Interactive<TextStyle>,
+    pub icon_button: Interactive<IconButton>,
+    pub unread_text: ContainedText,
+    pub read_text: ContainedText,
+    pub timestamp: ContainedText,
+    pub button: Interactive<ContainedText>,
+}
+
+#[derive(Deserialize, Default)]
+pub struct ChannelSelect {
+    #[serde(flatten)]
+    pub container: ContainerStyle,
+    pub header: ChannelName,
+    pub item: ChannelName,
+    pub active_item: ChannelName,
+    pub hovered_item: ChannelName,
+    pub menu: ContainerStyle,
+}
+
+#[derive(Deserialize, Default)]
+pub struct ChannelName {
+    #[serde(flatten)]
+    pub container: ContainerStyle,
+    pub hash: ContainedText,
+    pub name: TextStyle,
+}
+
+#[derive(Clone, Deserialize, Default)]
+pub struct Picker {
+    #[serde(flatten)]
+    pub container: ContainerStyle,
+    pub empty_container: ContainerStyle,
+    pub input_editor: FieldEditor,
+    pub empty_input_editor: FieldEditor,
+    pub no_matches: ContainedLabel,
+    pub item: Toggleable<Interactive<ContainedLabel>>,
+    pub header: ContainedLabel,
+    pub footer: Interactive<ContainedLabel>,
+}
+
+#[derive(Clone, Debug, Deserialize, Default)]
+pub struct ContainedText {
+    #[serde(flatten)]
+    pub container: ContainerStyle,
+    #[serde(flatten)]
+    pub text: TextStyle,
+}
+
+#[derive(Clone, Debug, Deserialize, Default)]
+pub struct ContainedLabel {
+    #[serde(flatten)]
+    pub container: ContainerStyle,
+    #[serde(flatten)]
+    pub label: LabelStyle,
+}
+
+#[derive(Clone, Deserialize, Default)]
+pub struct ProjectDiagnostics {
+    #[serde(flatten)]
+    pub container: ContainerStyle,
+    pub empty_message: TextStyle,
+    pub tab_icon_width: f32,
+    pub tab_icon_spacing: f32,
+    pub tab_summary_spacing: f32,
+}
+
+#[derive(Deserialize, Default)]
+pub struct ContactNotification {
+    pub header_avatar: ImageStyle,
+    pub header_message: ContainedText,
+    pub header_height: f32,
+    pub body_message: ContainedText,
+    pub button: Interactive<ContainedText>,
+    pub dismiss_button: Interactive<IconButton>,
+}
+
+#[derive(Deserialize, Default)]
+pub struct UpdateNotification {
+    pub message: ContainedText,
+    pub action_message: Interactive<ContainedText>,
+    pub dismiss_button: Interactive<IconButton>,
+}
+
+#[derive(Deserialize, Default)]
+pub struct MessageNotification {
+    pub message: ContainedText,
+    pub action_message: Interactive<ContainedText>,
+    pub dismiss_button: Interactive<IconButton>,
+}
+
+#[derive(Deserialize, Default)]
+pub struct ProjectSharedNotification {
+    pub window_height: f32,
+    pub window_width: f32,
+    #[serde(default)]
+    pub background: Color,
+    pub owner_container: ContainerStyle,
+    pub owner_avatar: ImageStyle,
+    pub owner_metadata: ContainerStyle,
+    pub owner_username: ContainedText,
+    pub message: ContainedText,
+    pub worktree_roots: ContainedText,
+    pub button_width: f32,
+    pub open_button: ContainedText,
+    pub dismiss_button: ContainedText,
+}
+
+#[derive(Deserialize, Default)]
+pub struct IncomingCallNotification {
+    pub window_height: f32,
+    pub window_width: f32,
+    #[serde(default)]
+    pub background: Color,
+    pub caller_container: ContainerStyle,
+    pub caller_avatar: ImageStyle,
+    pub caller_metadata: ContainerStyle,
+    pub caller_username: ContainedText,
+    pub caller_message: ContainedText,
+    pub worktree_roots: ContainedText,
+    pub button_width: f32,
+    pub accept_button: ContainedText,
+    pub decline_button: ContainedText,
+}
+
+#[derive(Clone, Deserialize, Default)]
+pub struct Editor {
+    pub text_color: Color,
+    #[serde(default)]
+    pub background: Color,
+    pub selection: SelectionStyle,
+    pub gutter_background: Color,
+    pub gutter_padding_factor: f32,
+    pub active_line_background: Color,
+    pub highlighted_line_background: Color,
+    pub rename_fade: f32,
+    pub document_highlight_read_background: Color,
+    pub document_highlight_write_background: Color,
+    pub diff: DiffStyle,
+    pub wrap_guide: Color,
+    pub active_wrap_guide: Color,
+    pub line_number: Color,
+    pub line_number_active: Color,
+    pub guest_selections: Vec<SelectionStyle>,
+    pub absent_selection: SelectionStyle,
+    pub syntax: Arc<SyntaxTheme>,
+    pub hint: HighlightStyle,
+    pub suggestion: HighlightStyle,
+    pub diagnostic_path_header: DiagnosticPathHeader,
+    pub diagnostic_header: DiagnosticHeader,
+    pub error_diagnostic: DiagnosticStyle,
+    pub invalid_error_diagnostic: DiagnosticStyle,
+    pub warning_diagnostic: DiagnosticStyle,
+    pub invalid_warning_diagnostic: DiagnosticStyle,
+    pub information_diagnostic: DiagnosticStyle,
+    pub invalid_information_diagnostic: DiagnosticStyle,
+    pub hint_diagnostic: DiagnosticStyle,
+    pub invalid_hint_diagnostic: DiagnosticStyle,
+    pub autocomplete: AutocompleteStyle,
+    pub code_actions: CodeActions,
+    pub folds: Folds,
+    pub unnecessary_code_fade: f32,
+    pub hover_popover: HoverPopover,
+    pub link_definition: HighlightStyle,
+    pub composition_mark: HighlightStyle,
+    pub jump_icon: Interactive<IconButton>,
+    pub scrollbar: Scrollbar,
+    pub whitespace: Color,
+}
+
+#[derive(Clone, Deserialize, Default)]
+pub struct Scrollbar {
+    pub track: ContainerStyle,
+    pub thumb: ContainerStyle,
+    pub width: f32,
+    pub min_height_factor: f32,
+    pub git: BufferGitDiffColors,
+    pub selections: Color,
+}
+
+#[derive(Clone, Deserialize, Default)]
+pub struct BufferGitDiffColors {
+    pub inserted: Color,
+    pub modified: Color,
+    pub deleted: Color,
+}
+
+#[derive(Clone, Deserialize, Default)]
+pub struct DiagnosticPathHeader {
+    #[serde(flatten)]
+    pub container: ContainerStyle,
+    pub filename: ContainedText,
+    pub path: ContainedText,
+    pub text_scale_factor: f32,
+}
+
+#[derive(Clone, Deserialize, Default)]
+pub struct DiagnosticHeader {
+    #[serde(flatten)]
+    pub container: ContainerStyle,
+    pub source: ContainedLabel,
+    pub message: ContainedLabel,
+    pub code: ContainedText,
+    pub text_scale_factor: f32,
+    pub icon_width_factor: f32,
+}
+
+#[derive(Clone, Deserialize, Default)]
+pub struct DiagnosticStyle {
+    pub message: LabelStyle,
+    #[serde(default)]
+    pub header: ContainerStyle,
+    pub text_scale_factor: f32,
+}
+
+#[derive(Clone, Deserialize, Default)]
+pub struct AutocompleteStyle {
+    #[serde(flatten)]
+    pub container: ContainerStyle,
+    pub item: ContainerStyle,
+    pub selected_item: ContainerStyle,
+    pub hovered_item: ContainerStyle,
+    pub match_highlight: HighlightStyle,
+    pub completion_min_width: f32,
+    pub completion_max_width: f32,
+    pub inline_docs_container: ContainerStyle,
+    pub inline_docs_color: Color,
+    pub inline_docs_size_percent: f32,
+    pub alongside_docs_max_width: f32,
+    pub alongside_docs_container: ContainerStyle,
+}
+
+#[derive(Clone, Copy, Default, Deserialize)]
+pub struct SelectionStyle {
+    pub cursor: Color,
+    pub selection: Color,
+}
+
+#[derive(Clone, Deserialize, Default)]
+pub struct FieldEditor {
+    #[serde(flatten)]
+    pub container: ContainerStyle,
+    pub text: TextStyle,
+    #[serde(default)]
+    pub placeholder_text: Option<TextStyle>,
+    pub selection: SelectionStyle,
+}
+
+#[derive(Clone, Deserialize, Default)]
+pub struct InteractiveColor {
+    pub color: Color,
+}
+
+#[derive(Clone, Deserialize, Default)]
+pub struct CodeActions {
+    #[serde(default)]
+    pub indicator: Toggleable<Interactive<InteractiveColor>>,
+    pub vertical_scale: f32,
+}
+
+#[derive(Clone, Deserialize, Default)]
+pub struct Folds {
+    pub indicator: Toggleable<Interactive<InteractiveColor>>,
+    pub ellipses: FoldEllipses,
+    pub fold_background: Color,
+    pub icon_margin_scale: f32,
+    pub folded_icon: String,
+    pub foldable_icon: String,
+}
+
+#[derive(Clone, Deserialize, Default)]
+pub struct FoldEllipses {
+    pub text_color: Color,
+    pub background: Interactive<InteractiveColor>,
+    pub corner_radius_factor: f32,
+}
+
+#[derive(Clone, Deserialize, Default)]
+pub struct DiffStyle {
+    pub inserted: Color,
+    pub modified: Color,
+    pub deleted: Color,
+    pub removed_width_em: f32,
+    pub width_em: f32,
+    pub corner_radius: f32,
+}
+
+#[derive(Debug, Default, Clone, Copy)]
+pub struct Interactive<T> {
+    pub default: T,
+    pub hovered: Option<T>,
+    pub clicked: Option<T>,
+    pub disabled: Option<T>,
+}
+
+impl<T> Deref for Interactive<T> {
+    type Target = T;
+
+    fn deref(&self) -> &Self::Target {
+        &self.default
+    }
+}
+
+impl Interactive<()> {
+    pub fn new_blank() -> Self {
+        Self {
+            default: (),
+            hovered: None,
+            clicked: None,
+            disabled: None,
+        }
+    }
+}
+
+#[derive(Clone, Copy, Debug, Default, Deserialize)]
+pub struct Toggleable<T> {
+    active: T,
+    inactive: T,
+}
+
+impl<T> Deref for Toggleable<T> {
+    type Target = T;
+
+    fn deref(&self) -> &Self::Target {
+        &self.inactive
+    }
+}
+
+impl Toggleable<()> {
+    pub fn new_blank() -> Self {
+        Self {
+            active: (),
+            inactive: (),
+        }
+    }
+}
+
+impl<T> Toggleable<T> {
+    pub fn new(active: T, inactive: T) -> Self {
+        Self { active, inactive }
+    }
+    pub fn in_state(&self, active: bool) -> &T {
+        if active {
+            &self.active
+        } else {
+            &self.inactive
+        }
+    }
+    pub fn active_state(&self) -> &T {
+        self.in_state(true)
+    }
+
+    pub fn inactive_state(&self) -> &T {
+        self.in_state(false)
+    }
+}
+
+impl<T> Interactive<T> {
+    pub fn disabled_style(&self) -> &T {
+        self.disabled.as_ref().unwrap_or(&self.default)
+    }
+}
+
+impl<T> Toggleable<Interactive<T>> {
+    pub fn default_style(&self) -> &T {
+        &self.inactive.default
+    }
+}
+
+impl<'de, T: DeserializeOwned> Deserialize<'de> for Interactive<T> {
+    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+    where
+        D: serde::Deserializer<'de>,
+    {
+        #[derive(Deserialize)]
+        struct Helper {
+            default: Value,
+            hovered: Option<Value>,
+            clicked: Option<Value>,
+            disabled: Option<Value>,
+        }
+
+        let json = Helper::deserialize(deserializer)?;
+
+        let deserialize_state = |state_json: Option<Value>| -> Result<Option<T>, D::Error> {
+            if let Some(mut state_json) = state_json {
+                if let Value::Object(state_json) = &mut state_json {
+                    if let Value::Object(default) = &json.default {
+                        for (key, value) in default {
+                            if !state_json.contains_key(key) {
+                                state_json.insert(key.clone(), value.clone());
+                            }
+                        }
+                    }
+                }
+                Ok(Some(
+                    serde_json::from_value::<T>(state_json).map_err(serde::de::Error::custom)?,
+                ))
+            } else {
+                Ok(None)
+            }
+        };
+
+        let hovered = deserialize_state(json.hovered)?;
+        let clicked = deserialize_state(json.clicked)?;
+        let disabled = deserialize_state(json.disabled)?;
+        let default = serde_json::from_value(json.default).map_err(serde::de::Error::custom)?;
+
+        Ok(Interactive {
+            default,
+            hovered,
+            clicked,
+            disabled,
+        })
+    }
+}
+
+impl Editor {
+    pub fn selection_style_for_room_participant(&self, participant_index: u32) -> SelectionStyle {
+        if self.guest_selections.is_empty() {
+            return SelectionStyle::default();
+        }
+        let style_ix = participant_index as usize % self.guest_selections.len();
+        self.guest_selections[style_ix]
+    }
+}
+
+#[derive(Default)]
+pub struct SyntaxTheme {
+    pub highlights: Vec<(String, HighlightStyle)>,
+}
+
+impl SyntaxTheme {
+    pub fn new(highlights: Vec<(String, HighlightStyle)>) -> Self {
+        Self { highlights }
+    }
+}
+
+impl<'de> Deserialize<'de> for SyntaxTheme {
+    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+    where
+        D: serde::Deserializer<'de>,
+    {
+        let syntax_data: HashMap<String, HighlightStyle> = Deserialize::deserialize(deserializer)?;
+
+        let mut result = Self::new(Vec::new());
+        for (key, style) in syntax_data {
+            match result
+                .highlights
+                .binary_search_by(|(needle, _)| needle.cmp(&key))
+            {
+                Ok(i) | Err(i) => {
+                    result.highlights.insert(i, (key, style));
+                }
+            }
+        }
+
+        Ok(result)
+    }
+}
+
+#[derive(Clone, Deserialize, Default)]
+pub struct HoverPopover {
+    pub container: ContainerStyle,
+    pub info_container: ContainerStyle,
+    pub warning_container: ContainerStyle,
+    pub error_container: ContainerStyle,
+    pub block_style: ContainerStyle,
+    pub prose: TextStyle,
+    pub diagnostic_source_highlight: HighlightStyle,
+    pub highlight: Color,
+}
+
+#[derive(Clone, Deserialize, Default)]
+pub struct TerminalStyle {
+    pub black: Color,
+    pub red: Color,
+    pub green: Color,
+    pub yellow: Color,
+    pub blue: Color,
+    pub magenta: Color,
+    pub cyan: Color,
+    pub white: Color,
+    pub bright_black: Color,
+    pub bright_red: Color,
+    pub bright_green: Color,
+    pub bright_yellow: Color,
+    pub bright_blue: Color,
+    pub bright_magenta: Color,
+    pub bright_cyan: Color,
+    pub bright_white: Color,
+    pub foreground: Color,
+    pub background: Color,
+    pub modal_background: Color,
+    pub cursor: Color,
+    pub dim_black: Color,
+    pub dim_red: Color,
+    pub dim_green: Color,
+    pub dim_yellow: Color,
+    pub dim_blue: Color,
+    pub dim_magenta: Color,
+    pub dim_cyan: Color,
+    pub dim_white: Color,
+    pub bright_foreground: Color,
+    pub dim_foreground: Color,
+}
+
+#[derive(Clone, Deserialize, Default)]
+pub struct AssistantStyle {
+    pub container: ContainerStyle,
+    pub hamburger_button: Interactive<IconStyle>,
+    pub split_button: Interactive<IconStyle>,
+    pub assist_button: Interactive<IconStyle>,
+    pub quote_button: Interactive<IconStyle>,
+    pub zoom_in_button: Interactive<IconStyle>,
+    pub zoom_out_button: Interactive<IconStyle>,
+    pub plus_button: Interactive<IconStyle>,
+    pub title: ContainedText,
+    pub message_header: ContainerStyle,
+    pub sent_at: ContainedText,
+    pub user_sender: Interactive<ContainedText>,
+    pub assistant_sender: Interactive<ContainedText>,
+    pub system_sender: Interactive<ContainedText>,
+    pub model: Interactive<ContainedText>,
+    pub remaining_tokens: ContainedText,
+    pub low_remaining_tokens: ContainedText,
+    pub no_remaining_tokens: ContainedText,
+    pub error_icon: Icon,
+    pub api_key_editor: FieldEditor,
+    pub api_key_prompt: ContainedText,
+    pub saved_conversation: SavedConversation,
+    pub inline: InlineAssistantStyle,
+}
+
+#[derive(Clone, Deserialize, Default)]
+pub struct ButtonStyle<C> {
+    #[serde(flatten)]
+    pub container: ContainerStyle,
+    // TODO: These are incorrect for the intended usage of the buttons.
+    // The size should be constant, but putting them here duplicates them
+    // across the states the buttons can be in
+    pub button_width: Option<f32>,
+    pub button_height: Option<f32>,
+    #[serde(flatten)]
+    contents: C,
+}
+
+pub type IconButtonStyle = Interactive<ButtonStyle<SvgStyle>>;
+pub type ToggleIconButtonStyle = Toggleable<IconButtonStyle>;
+
+#[derive(Clone, Deserialize, Default)]
+pub struct InlineAssistantStyle {
+    #[serde(flatten)]
+    pub container: ContainerStyle,
+    pub editor: FieldEditor,
+    pub disabled_editor: FieldEditor,
+    pub pending_edit_background: Color,
+    pub include_conversation: ToggleIconButtonStyle,
+    pub retrieve_context: ToggleIconButtonStyle,
+    pub context_status: ContextStatusStyle,
+}
+
+#[derive(Clone, Deserialize, Default)]
+pub struct ContextStatusStyle {
+    pub error_icon: Icon,
+    pub in_progress_icon: Icon,
+    pub complete_icon: Icon,
+}
+
+#[derive(Clone, Deserialize, Default)]
+pub struct Contained<T> {
+    container: ContainerStyle,
+    contained: T,
+}
+
+#[derive(Clone, Deserialize, Default)]
+pub struct SavedConversation {
+    pub container: Interactive<ContainerStyle>,
+    pub saved_at: ContainedText,
+    pub title: ContainedText,
+}
+
+#[derive(Clone, Deserialize, Default)]
+pub struct FeedbackStyle {
+    pub submit_button: Interactive<ContainedText>,
+    pub button_margin: f32,
+    pub info_text_default: ContainedText,
+    pub link_text_default: ContainedText,
+    pub link_text_hover: ContainedText,
+}
+
+#[derive(Clone, Deserialize, Default)]
+pub struct CheckboxStyle {
+    pub icon: SvgStyle,
+    pub label: ContainedText,
+    pub default: ContainerStyle,
+    pub checked: ContainerStyle,
+    pub hovered: ContainerStyle,
+    pub hovered_and_checked: ContainerStyle,
+}
+
+#[derive(Clone, Deserialize, Default)]
+pub struct WelcomeStyle {
+    pub page_width: f32,
+    pub logo: SvgStyle,
+    pub logo_subheading: ContainedText,
+    pub usage_note: ContainedText,
+    pub checkbox: CheckboxStyle,
+    pub checkbox_container: ContainerStyle,
+    pub button: Interactive<ContainedText>,
+    pub button_group: ContainerStyle,
+    pub heading_group: ContainerStyle,
+    pub checkbox_group: ContainerStyle,
+}
+
+#[derive(Clone, Deserialize, Default)]
+pub struct ColorScheme {
+    pub name: String,
+    pub is_light: bool,
+    pub lowest: Layer,
+    pub middle: Layer,
+    pub highest: Layer,
+}
+
+#[derive(Clone, Deserialize, Default)]
+pub struct Player {
+    pub cursor: Color,
+    pub selection: Color,
+}
+
+#[derive(Clone, Deserialize, Default)]
+pub struct RampSet {
+    pub neutral: Vec<Color>,
+    pub red: Vec<Color>,
+    pub orange: Vec<Color>,
+    pub yellow: Vec<Color>,
+    pub green: Vec<Color>,
+    pub cyan: Vec<Color>,
+    pub blue: Vec<Color>,
+    pub violet: Vec<Color>,
+    pub magenta: Vec<Color>,
+}
+
+#[derive(Clone, Deserialize, Default)]
+pub struct Layer {
+    pub base: StyleSet,
+    pub variant: StyleSet,
+    pub on: StyleSet,
+    pub accent: StyleSet,
+    pub positive: StyleSet,
+    pub warning: StyleSet,
+    pub negative: StyleSet,
+}
+
+#[derive(Clone, Deserialize, Default)]
+pub struct StyleSet {
+    pub default: Style,
+    pub active: Style,
+    pub disabled: Style,
+    pub hovered: Style,
+    pub pressed: Style,
+    pub inverted: Style,
+}
+
+#[derive(Clone, Deserialize, Default)]
+pub struct Style {
+    pub background: Color,
+    pub border: Color,
+    pub foreground: Color,
+}