Detailed changes
  
  
    
    @@ -0,0 +1,135 @@
+use serde::Serializer;
+
+/// Serializes an f32 value with 2 decimal places of precision.
+///
+/// This function rounds the value to 2 decimal places and formats it as a string,
+/// then parses it back to f64 before serialization. This ensures clean JSON output
+/// without IEEE 754 floating-point artifacts.
+///
+/// # Arguments
+///
+/// * `value` - The f32 value to serialize
+/// * `serializer` - The serde serializer to use
+///
+/// # Returns
+///
+/// Result of the serialization operation
+///
+/// # Usage
+///
+/// This function can be used with Serde's `serialize_with` attribute:
+/// ```
+/// use serde::Serialize;
+/// use settings::serialize_f32_with_two_decimal_places;
+///
+/// #[derive(Serialize)]
+/// struct ExampleStruct(#[serde(serialize_with = "serialize_f32_with_two_decimal_places")] f32);
+/// ```
+pub fn serialize_f32_with_two_decimal_places<S>(
+    value: &f32,
+    serializer: S,
+) -> Result<S::Ok, S::Error>
+where
+    S: Serializer,
+{
+    let rounded = (value * 100.0).round() / 100.0;
+    let formatted = format!("{:.2}", rounded);
+    let clean_value: f64 = formatted.parse().unwrap_or(rounded as f64);
+    serializer.serialize_f64(clean_value)
+}
+
+/// Serializes an optional f32 value with 2 decimal places of precision.
+///
+/// This function handles `Option<f32>` types, serializing `Some` values with 2 decimal
+/// places of precision and `None` values as null. For `Some` values, it rounds to 2 decimal
+/// places and formats as a string, then parses back to f64 before serialization. This ensures
+/// clean JSON output without IEEE 754 floating-point artifacts.
+///
+/// # Arguments
+///
+/// * `value` - The optional f32 value to serialize
+/// * `serializer` - The serde serializer to use
+///
+/// # Returns
+///
+/// Result of the serialization operation
+///
+/// # Behavior
+///
+/// * `Some(v)` - Serializes the value rounded to 2 decimal places
+/// * `None` - Serializes as JSON null
+///
+/// # Usage
+///
+/// This function can be used with Serde's `serialize_with` attribute:
+/// ```
+/// use serde::Serialize;
+/// use settings::serialize_optional_f32_with_two_decimal_places;
+///
+/// #[derive(Serialize)]
+/// struct ExampleStruct {
+///     #[serde(serialize_with = "serialize_optional_f32_with_two_decimal_places")]
+///     optional_value: Option<f32>,
+/// }
+/// ```
+pub fn serialize_optional_f32_with_two_decimal_places<S>(
+    value: &Option<f32>,
+    serializer: S,
+) -> Result<S::Ok, S::Error>
+where
+    S: Serializer,
+{
+    match value {
+        Some(v) => {
+            let rounded = (v * 100.0).round() / 100.0;
+            let formatted = format!("{:.2}", rounded);
+            let clean_value: f64 = formatted.parse().unwrap_or(rounded as f64);
+            serializer.serialize_some(&clean_value)
+        }
+        None => serializer.serialize_none(),
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use serde::{Deserialize, Serialize};
+
+    #[derive(Serialize, Deserialize)]
+    struct TestOptional {
+        #[serde(serialize_with = "serialize_optional_f32_with_two_decimal_places")]
+        value: Option<f32>,
+    }
+
+    #[derive(Serialize, Deserialize)]
+    struct TestNonOptional {
+        #[serde(serialize_with = "serialize_f32_with_two_decimal_places")]
+        value: f32,
+    }
+
+    #[test]
+    fn test_serialize_optional_f32_with_two_decimal_places() {
+        let cases = [
+            (Some(123.456789), r#"{"value":123.46}"#),
+            (Some(1.2), r#"{"value":1.2}"#),
+            (Some(300.00000), r#"{"value":300.0}"#),
+        ];
+        for (value, expected) in cases {
+            let value = TestOptional { value };
+            assert_eq!(serde_json::to_string(&value).unwrap(), expected);
+        }
+    }
+
+    #[test]
+    fn test_serialize_f32_with_two_decimal_places() {
+        let cases = [
+            (123.456789, r#"{"value":123.46}"#),
+            (1.200, r#"{"value":1.2}"#),
+            (300.00000, r#"{"value":300.0}"#),
+        ];
+        for (value, expected) in cases {
+            let value = TestNonOptional { value };
+            assert_eq!(serde_json::to_string(&value).unwrap(), expected);
+        }
+    }
+}
  
  
  
    
    @@ -2,6 +2,7 @@ mod base_keymap_setting;
 mod editable_setting_control;
 mod keymap_file;
 pub mod merge_from;
+mod serde_helper;
 mod settings_content;
 mod settings_file;
 mod settings_json;
@@ -21,6 +22,7 @@ pub use keymap_file::{
     KeyBindingValidator, KeyBindingValidatorRegistration, KeybindSource, KeybindUpdateOperation,
     KeybindUpdateTarget, KeymapFile, KeymapFileLoadResult,
 };
+pub use serde_helper::*;
 pub use settings_file::*;
 pub use settings_json::*;
 pub use settings_store::{
  
  
  
    
    @@ -515,6 +515,7 @@ pub struct GitPanelSettingsContent {
     /// Default width of the panel in pixels.
     ///
     /// Default: 360
+    #[serde(serialize_with = "crate::serialize_optional_f32_with_two_decimal_places")]
     pub default_width: Option<f32>,
     /// How entry statuses are displayed.
     ///
@@ -586,6 +587,7 @@ pub struct NotificationPanelSettingsContent {
     /// Default width of the panel in pixels.
     ///
     /// Default: 300
+    #[serde(serialize_with = "crate::serialize_optional_f32_with_two_decimal_places")]
     pub default_width: Option<f32>,
 }
 
@@ -603,6 +605,7 @@ pub struct PanelSettingsContent {
     /// Default width of the panel in pixels.
     ///
     /// Default: 240
+    #[serde(serialize_with = "crate::serialize_optional_f32_with_two_decimal_places")]
     pub default_width: Option<f32>,
 }
 
@@ -777,6 +780,7 @@ pub struct OutlinePanelSettingsContent {
     /// Customize default width (in pixels) taken by outline panel
     ///
     /// Default: 240
+    #[serde(serialize_with = "crate::serialize_optional_f32_with_two_decimal_places")]
     pub default_width: Option<f32>,
     /// The position of outline panel
     ///
@@ -797,6 +801,7 @@ pub struct OutlinePanelSettingsContent {
     /// Amount of indentation (in pixels) for nested items.
     ///
     /// Default: 20
+    #[serde(serialize_with = "crate::serialize_optional_f32_with_two_decimal_places")]
     pub indent_size: Option<f32>,
     /// Whether to reveal it in the outline panel automatically,
     /// when a corresponding project entry becomes active.
  
  
  
    
    @@ -26,10 +26,12 @@ pub struct AgentSettingsContent {
     /// Default width in pixels when the agent panel is docked to the left or right.
     ///
     /// Default: 640
+    #[serde(serialize_with = "crate::serialize_optional_f32_with_two_decimal_places")]
     pub default_width: Option<f32>,
     /// Default height in pixels when the agent panel is docked to the bottom.
     ///
     /// Default: 320
+    #[serde(serialize_with = "crate::serialize_optional_f32_with_two_decimal_places")]
     pub default_height: Option<f32>,
     /// The default model to use when creating new chats and for other features when a specific model is not specified.
     pub default_model: Option<LanguageModelSelection>,
@@ -232,6 +234,7 @@ pub enum CompletionMode {
 pub struct LanguageModelParameters {
     pub provider: Option<LanguageModelProviderSetting>,
     pub model: Option<SharedString>,
+    #[serde(serialize_with = "crate::serialize_optional_f32_with_two_decimal_places")]
     pub temperature: Option<f32>,
 }
 
  
  
  
    
    @@ -7,7 +7,9 @@ use serde::{Deserialize, Serialize};
 use serde_with::skip_serializing_none;
 use settings_macros::MergeFrom;
 
-use crate::{DelayMs, DiagnosticSeverityContent, ShowScrollbar};
+use crate::{
+    DelayMs, DiagnosticSeverityContent, ShowScrollbar, serialize_f32_with_two_decimal_places,
+};
 
 #[skip_serializing_none]
 #[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom)]
@@ -70,6 +72,7 @@ pub struct EditorSettingsContent {
     /// The number of lines to keep above/below the cursor when auto-scrolling.
     ///
     /// Default: 3.
+    #[serde(serialize_with = "crate::serialize_optional_f32_with_two_decimal_places")]
     pub vertical_scroll_margin: Option<f32>,
     /// Whether to scroll when clicking near the edge of the visible text area.
     ///
@@ -78,17 +81,20 @@ pub struct EditorSettingsContent {
     /// The number of characters to keep on either side when scrolling with the mouse.
     ///
     /// Default: 5.
+    #[serde(serialize_with = "crate::serialize_optional_f32_with_two_decimal_places")]
     pub horizontal_scroll_margin: Option<f32>,
     /// Scroll sensitivity multiplier. This multiplier is applied
     /// to both the horizontal and vertical delta values while scrolling.
     ///
     /// Default: 1.0
+    #[serde(serialize_with = "crate::serialize_optional_f32_with_two_decimal_places")]
     pub scroll_sensitivity: Option<f32>,
     /// Scroll sensitivity multiplier for fast scrolling. This multiplier is applied
     /// to both the horizontal and vertical delta values while scrolling. Fast scrolling
     /// happens when a user holds the alt or option key while scrolling.
     ///
     /// Default: 4.0
+    #[serde(serialize_with = "crate::serialize_optional_f32_with_two_decimal_places")]
     pub fast_scroll_sensitivity: Option<f32>,
     /// Whether the line numbers on editors gutter are relative or not.
     ///
@@ -796,7 +802,9 @@ pub enum DisplayIn {
     derive_more::FromStr,
 )]
 #[serde(transparent)]
-pub struct MinimumContrast(pub f32);
+pub struct MinimumContrast(
+    #[serde(serialize_with = "crate::serialize_f32_with_two_decimal_places")] pub f32,
+);
 
 impl Display for MinimumContrast {
     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
@@ -827,7 +835,9 @@ impl From<f32> for MinimumContrast {
     derive_more::FromStr,
 )]
 #[serde(transparent)]
-pub struct InactiveOpacity(pub f32);
+pub struct InactiveOpacity(
+    #[serde(serialize_with = "serialize_f32_with_two_decimal_places")] pub f32,
+);
 
 impl Display for InactiveOpacity {
     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
  
  
  
    
    @@ -46,6 +46,7 @@ pub struct AnthropicAvailableModel {
     /// Configuration of Anthropic's caching API.
     pub cache_configuration: Option<LanguageModelCacheConfiguration>,
     pub max_output_tokens: Option<u64>,
+    #[serde(serialize_with = "crate::serialize_optional_f32_with_two_decimal_places")]
     pub default_temperature: Option<f32>,
     #[serde(default)]
     pub extra_beta_headers: Vec<String>,
@@ -71,6 +72,7 @@ pub struct BedrockAvailableModel {
     pub max_tokens: u64,
     pub cache_configuration: Option<LanguageModelCacheConfiguration>,
     pub max_output_tokens: Option<u64>,
+    #[serde(serialize_with = "crate::serialize_optional_f32_with_two_decimal_places")]
     pub default_temperature: Option<f32>,
     pub mode: Option<ModelMode>,
 }
@@ -332,6 +334,7 @@ pub struct ZedDotDevAvailableModel {
     /// Indicates whether this custom model supports caching.
     pub cache_configuration: Option<LanguageModelCacheConfiguration>,
     /// The default temperature to use for this model.
+    #[serde(serialize_with = "crate::serialize_optional_f32_with_two_decimal_places")]
     pub default_temperature: Option<f32>,
     /// Any extra beta headers to provide when using the model.
     #[serde(default)]
  
  
  
    
    @@ -41,6 +41,7 @@ pub struct TerminalSettingsContent {
     ///
     /// If this option is not included,
     /// the terminal will default to matching the buffer's font size.
+    #[serde(serialize_with = "crate::serialize_optional_f32_with_two_decimal_places")]
     pub font_size: Option<f32>,
     /// Sets the terminal's font family.
     ///
@@ -61,6 +62,7 @@ pub struct TerminalSettingsContent {
     pub line_height: Option<TerminalLineHeight>,
     pub font_features: Option<FontFeatures>,
     /// Sets the terminal's font weight in CSS weight units 0-900.
+    #[serde(serialize_with = "crate::serialize_optional_f32_with_two_decimal_places")]
     pub font_weight: Option<f32>,
     /// Default cursor shape for the terminal.
     /// Can be "bar", "block", "underline", or "hollow".
@@ -99,10 +101,12 @@ pub struct TerminalSettingsContent {
     /// Default width when the terminal is docked to the left or right.
     ///
     /// Default: 640
+    #[serde(serialize_with = "crate::serialize_optional_f32_with_two_decimal_places")]
     pub default_width: Option<f32>,
     /// Default height when the terminal is docked to the bottom.
     ///
     /// Default: 320
+    #[serde(serialize_with = "crate::serialize_optional_f32_with_two_decimal_places")]
     pub default_height: Option<f32>,
     /// The maximum number of lines to keep in the scrollback history.
     /// Maximum allowed value is 100_000, all values above that will be treated as 100_000.
@@ -130,6 +134,7 @@ pub struct TerminalSettingsContent {
     /// - 90: Preferred for body text
     ///
     /// Default: 45
+    #[serde(serialize_with = "crate::serialize_optional_f32_with_two_decimal_places")]
     pub minimum_contrast: Option<f32>,
 }
 
@@ -213,7 +218,7 @@ pub enum TerminalLineHeight {
     /// particularly if they use box characters
     Standard,
     /// Use a custom line height.
-    Custom(f32),
+    Custom(#[serde(serialize_with = "crate::serialize_f32_with_two_decimal_places")] f32),
 }
 
 impl TerminalLineHeight {
  
  
  
    
    @@ -9,6 +9,8 @@ use std::{fmt::Display, sync::Arc};
 
 use serde_with::skip_serializing_none;
 
+use crate::serialize_f32_with_two_decimal_places;
+
 /// Settings for rendering text in UI and text buffers.
 
 #[skip_serializing_none]
@@ -16,6 +18,7 @@ use serde_with::skip_serializing_none;
 pub struct ThemeSettingsContent {
     /// The default font size for text in the UI.
     #[serde(default)]
+    #[serde(serialize_with = "crate::serialize_optional_f32_with_two_decimal_places")]
     pub ui_font_size: Option<f32>,
     /// The name of a font to use for rendering in the UI.
     #[serde(default)]
@@ -42,6 +45,7 @@ pub struct ThemeSettingsContent {
     pub buffer_font_fallbacks: Option<Vec<FontFamilyName>>,
     /// The default font size for rendering in text buffers.
     #[serde(default)]
+    #[serde(serialize_with = "crate::serialize_optional_f32_with_two_decimal_places")]
     pub buffer_font_size: Option<f32>,
     /// The weight of the editor font in CSS units from 100 to 900.
     #[serde(default)]
@@ -56,9 +60,11 @@ pub struct ThemeSettingsContent {
     pub buffer_font_features: Option<FontFeatures>,
     /// The font size for agent responses in the agent panel. Falls back to the UI font size if unset.
     #[serde(default)]
+    #[serde(serialize_with = "crate::serialize_optional_f32_with_two_decimal_places")]
     pub agent_ui_font_size: Option<f32>,
     /// The font size for user messages in the agent panel.
     #[serde(default)]
+    #[serde(serialize_with = "crate::serialize_optional_f32_with_two_decimal_places")]
     pub agent_buffer_font_size: Option<f32>,
     /// The name of the Zed theme to use.
     #[serde(default)]
@@ -104,7 +110,7 @@ pub struct ThemeSettingsContent {
     derive_more::FromStr,
 )]
 #[serde(transparent)]
-pub struct CodeFade(pub f32);
+pub struct CodeFade(#[serde(serialize_with = "serialize_f32_with_two_decimal_places")] pub f32);
 
 impl Display for CodeFade {
     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
  
  
  
    
    @@ -8,6 +8,7 @@ use settings_macros::MergeFrom;
 
 use crate::{
     DelayMs, DockPosition, DockSide, InactiveOpacity, ScrollbarSettingsContent, ShowIndentGuides,
+    serialize_optional_f32_with_two_decimal_places,
 };
 
 #[skip_serializing_none]
@@ -58,6 +59,7 @@ pub struct WorkspaceSettingsContent {
     /// Given as a fraction that will be multiplied by the smaller dimension of the workspace.
     ///
     /// Default: `0.2` (20% of the smaller dimension of the workspace)
+    #[serde(serialize_with = "serialize_optional_f32_with_two_decimal_places")]
     pub drop_target_size: Option<f32>,
     /// Whether to close the window when using 'close active item' on a workspace with no tabs
     ///
@@ -251,6 +253,7 @@ pub struct ActivePaneModifiers {
     /// The border is drawn inset.
     ///
     /// Default: `0.0`
+    #[serde(serialize_with = "crate::serialize_optional_f32_with_two_decimal_places")]
     pub border_size: Option<f32>,
     /// Opacity of inactive panels.
     /// When set to 1.0, the inactive panes have the same opacity as the active one.
@@ -468,11 +471,13 @@ pub struct CenteredLayoutSettings {
     /// workspace when the centered layout is used.
     ///
     /// Default: 0.2
+    #[serde(serialize_with = "serialize_optional_f32_with_two_decimal_places")]
     pub left_padding: Option<f32>,
     // The relative width of the right padding of the central pane from the
     // workspace when the centered layout is used.
     ///
     /// Default: 0.2
+    #[serde(serialize_with = "serialize_optional_f32_with_two_decimal_places")]
     pub right_padding: Option<f32>,
 }
 
@@ -521,6 +526,7 @@ pub struct ProjectPanelSettingsContent {
     /// Customize default width (in pixels) taken by project panel
     ///
     /// Default: 240
+    #[serde(serialize_with = "crate::serialize_optional_f32_with_two_decimal_places")]
     pub default_width: Option<f32>,
     /// The position of project panel
     ///
@@ -545,6 +551,7 @@ pub struct ProjectPanelSettingsContent {
     /// Amount of indentation (in pixels) for nested items.
     ///
     /// Default: 20
+    #[serde(serialize_with = "serialize_optional_f32_with_two_decimal_places")]
     pub indent_size: Option<f32>,
     /// Whether to reveal it in the project panel automatically,
     /// when a corresponding project entry becomes active.