diff --git a/crates/hypercolor-core/src/config/mod.rs b/crates/hypercolor-core/src/config/mod.rs index 3840d6294..c696608b6 100644 --- a/crates/hypercolor-core/src/config/mod.rs +++ b/crates/hypercolor-core/src/config/mod.rs @@ -1044,8 +1044,10 @@ impl ConfigManager { /// effect fallback value `clear_groups` to `clear_zones`, then /// upgraded again as v5. Schema v5 is upgraded by dropping an /// `audio.beat_sensitivity` still at the retired 0.6 default so the - /// current default applies. Every other older or newer schema is - /// refused rather than guessed at. + /// current default applies, then upgraded again as v6. Schema v6 is + /// upgraded by dropping a `daemon.target_fps` still at the retired 30 + /// default the same way. Every other older or newer schema is refused + /// rather than guessed at. /// /// # Errors /// @@ -1070,6 +1072,10 @@ impl ConfigManager { } if schema_version == 5 { migrate_v5_to_v6(&mut document)?; + schema_version = 6; + } + if schema_version == 6 { + migrate_v6_to_v7(&mut document)?; } else if schema_version != CURRENT_SCHEMA_VERSION { bail!(schema_mismatch_message(schema_version)); } @@ -1141,6 +1147,27 @@ fn migrate_v5_to_v6(document: &mut toml::Value) -> Result<()> { { audio.remove("beat_sensitivity"); } + root.insert("schema_version".to_owned(), toml::Value::Integer(6)); + Ok(()) +} + +/// The `daemon.target_fps` default that schemas v4 through v6 shipped. +const V6_DEFAULT_TARGET_FPS: i64 = 30; + +/// Schema v7 raised the render target default from 30 to +/// [`hypercolor_types::config::DEFAULT_TARGET_FPS`]. Every save rewrites the +/// whole file, so a v6 file carries 30 whether or not anyone chose it. That +/// exact value is dropped so the current default applies; any other value +/// was picked on purpose and stays. +fn migrate_v6_to_v7(document: &mut toml::Value) -> Result<()> { + let root = document + .as_table_mut() + .ok_or_else(|| anyhow::anyhow!("configuration root must be a TOML table"))?; + if let Some(daemon) = root.get_mut("daemon").and_then(toml::Value::as_table_mut) + && daemon.get("target_fps").and_then(toml::Value::as_integer) == Some(V6_DEFAULT_TARGET_FPS) + { + daemon.remove("target_fps"); + } root.insert( "schema_version".to_owned(), toml::Value::Integer(i64::from(CURRENT_SCHEMA_VERSION)), diff --git a/crates/hypercolor-core/tests/config_persistence_tests.rs b/crates/hypercolor-core/tests/config_persistence_tests.rs index 5bf11e7b8..aaf95d8f0 100644 --- a/crates/hypercolor-core/tests/config_persistence_tests.rs +++ b/crates/hypercolor-core/tests/config_persistence_tests.rs @@ -9,6 +9,7 @@ use std::time::Duration; use hypercolor_core::config::{ConfigManager, ConfigPersistence}; use hypercolor_core::persistence::AtomicFileWriter; +use hypercolor_types::config::DEFAULT_TARGET_FPS; fn set_mode(path: &Path, mode: u32) { fs::set_permissions(path, fs::Permissions::from_mode(mode)) @@ -63,7 +64,7 @@ fn save_into_a_read_only_directory_is_live_and_lagging_until_the_retry_lands() { .expect("the previous file stays readable") .daemon .target_fps, - 30 + DEFAULT_TARGET_FPS ); AtomicFileWriter::new(&path) diff --git a/crates/hypercolor-core/tests/config_sources_tests.rs b/crates/hypercolor-core/tests/config_sources_tests.rs index b96477d24..d3fa92e40 100644 --- a/crates/hypercolor-core/tests/config_sources_tests.rs +++ b/crates/hypercolor-core/tests/config_sources_tests.rs @@ -163,7 +163,7 @@ fn pending_restart_flags_only_restart_classified_changes() { // A live-classified exact key inside the restart-classified // daemon section must NOT flag a restart. - manager.modify(|config| config.daemon.target_fps = 60); + manager.modify(|config| config.daemon.target_fps = 45); assert!( manager.pending_restart().is_empty(), "live render knobs never flag a restart" diff --git a/crates/hypercolor-core/tests/config_tests.rs b/crates/hypercolor-core/tests/config_tests.rs index d4af1ab8c..3aa186343 100644 --- a/crates/hypercolor-core/tests/config_tests.rs +++ b/crates/hypercolor-core/tests/config_tests.rs @@ -5,8 +5,8 @@ use std::sync::{Arc, Mutex, MutexGuard}; use hypercolor_core::config::ConfigManager; use hypercolor_types::config::{ - CURRENT_SCHEMA_VERSION, DEFAULT_BEAT_SENSITIVITY, EffectErrorFallbackPolicy, HypercolorConfig, - InteractionRoutePolicy, + CURRENT_SCHEMA_VERSION, DEFAULT_BEAT_SENSITIVITY, DEFAULT_TARGET_FPS, + EffectErrorFallbackPolicy, HypercolorConfig, InteractionRoutePolicy, }; // ─── TOML Parsing ─────────────────────────────────────────────────────────── @@ -14,7 +14,7 @@ use hypercolor_types::config::{ #[test] fn load_minimal_toml() { let toml = r" - schema_version = 6 + schema_version = 7 "; let tmp = tempfile::NamedTempFile::new().expect("failed to create temp file"); @@ -22,10 +22,10 @@ fn load_minimal_toml() { let config = ConfigManager::load(tmp.path()).expect("minimal TOML should parse without error"); - assert_eq!(config.schema_version, 6); + assert_eq!(config.schema_version, 7); // Sections should fall back to their serde defaults assert_eq!(config.daemon.port, 9420); - assert_eq!(config.daemon.target_fps, 30); + assert_eq!(config.daemon.target_fps, 60); assert!(config.web.enabled); assert_eq!(config.input.daemon_route, InteractionRoutePolicy::Host); assert_eq!(config.input.preview_route, InteractionRoutePolicy::Browser); @@ -37,7 +37,7 @@ fn schema_v4_renames_start_profile_before_deserialization() { ConfigManager::parse_toml("schema_version = 4\n[daemon]\nstart_profile = \"Evening\"\n") .expect("schema v4 should migrate"); - assert_eq!(config.schema_version, 6); + assert_eq!(config.schema_version, 7); assert_eq!(config.daemon.start_scene, "Evening"); } @@ -109,11 +109,126 @@ fn schema_v5_keeps_a_beat_threshold_someone_chose() { } #[test] -fn current_schema_keeps_a_beat_threshold_of_point_six() { - let config = ConfigManager::parse_toml("schema_version = 6\n[audio]\nbeat_sensitivity = 0.6\n") - .expect("current schema should parse"); +fn schema_v6_and_later_keep_a_beat_threshold_of_point_six() { + for version in [6, CURRENT_SCHEMA_VERSION] { + let config = ConfigManager::parse_toml(&format!( + "schema_version = {version}\n[audio]\nbeat_sensitivity = 0.6\n" + )) + .expect("schema v6 and later should parse"); + + assert!( + (config.audio.beat_sensitivity - 0.6).abs() < f32::EPSILON, + "schema {version} moved {}", + config.audio.beat_sensitivity + ); + } +} + +#[test] +fn schema_v6_moves_the_retired_render_target_to_the_current_one() { + for version in [4, 5, 6] { + let config = ConfigManager::parse_toml(&format!( + "schema_version = {version}\n[daemon]\ntarget_fps = 30\n" + )) + .expect("older schemas should migrate"); + + assert_eq!(config.schema_version, CURRENT_SCHEMA_VERSION); + assert_eq!( + config.daemon.target_fps, DEFAULT_TARGET_FPS, + "schema {version} kept the retired render target" + ); + } +} + +/// A v6 save wrote the whole document, so the retired default sits on disk +/// as an explicit `target_fps = 30` that nobody chose. +#[test] +fn schema_v6_moves_the_render_target_exactly_as_a_save_wrote_it() { + let mut saved = HypercolorConfig { + schema_version: 6, + ..HypercolorConfig::default() + }; + saved.daemon.target_fps = 30; + let written = toml::to_string_pretty(&saved).expect("serialize a v6 save"); + assert!(written.contains("target_fps = 30"), "{written}"); + + let config = ConfigManager::parse_toml(&written).expect("a v6 save should migrate"); + + assert_eq!(config.schema_version, CURRENT_SCHEMA_VERSION); + assert_eq!(config.daemon.target_fps, DEFAULT_TARGET_FPS); +} + +#[test] +fn schema_v6_keeps_a_render_target_someone_chose() { + for chosen in [10, 20, 45, 60, 120] { + let config = ConfigManager::parse_toml(&format!( + "schema_version = 6\n[daemon]\ntarget_fps = {chosen}\n" + )) + .expect("schema v6 should migrate"); + + assert_eq!(config.daemon.target_fps, chosen); + } +} + +#[test] +fn current_schema_keeps_a_render_target_of_thirty() { + let config = ConfigManager::parse_toml(&format!( + "schema_version = {CURRENT_SCHEMA_VERSION}\n[daemon]\ntarget_fps = 30\n" + )) + .expect("current schema should parse"); + + assert_eq!(config.daemon.target_fps, 30); +} + +/// Loading never rewrites the file; the migrated value reaches disk with +/// the next save, stamped with the current schema so it is never migrated +/// again. +#[test] +fn a_migrated_render_target_reaches_disk_on_the_next_save() { + let dir = tempfile::tempdir().expect("failed to create temp dir"); + let path = dir.path().join("hypercolor.toml"); + fs::write(&path, "schema_version = 6\n\n[daemon]\ntarget_fps = 30\n") + .expect("failed to write v6 config"); + + let manager = ConfigManager::new(path.clone()).expect("a v6 file should load"); + assert_eq!(manager.get().daemon.target_fps, DEFAULT_TARGET_FPS); + assert!(manager.save().expect("save should succeed").is_durable()); + + let written = fs::read_to_string(&path).expect("saved config should read"); + let on_disk: toml::Value = toml::from_str(&written).expect("saved config should parse"); + assert_eq!( + on_disk + .get("schema_version") + .and_then(toml::Value::as_integer), + Some(i64::from(CURRENT_SCHEMA_VERSION)) + ); + assert_eq!( + on_disk + .get("daemon") + .and_then(|daemon| daemon.get("target_fps")) + .and_then(toml::Value::as_integer), + Some(i64::from(DEFAULT_TARGET_FPS)) + ); +} + +/// A v4 file runs every migration in order: the start-key rename, the +/// beat threshold, and the render target. +#[test] +fn schema_v4_chains_through_every_later_migration() { + let config = ConfigManager::parse_toml(concat!( + "schema_version = 4\n\n", + "[daemon]\n", + "start_profile = \"Evening\"\n", + "target_fps = 30\n\n", + "[audio]\n", + "beat_sensitivity = 0.6\n", + )) + .expect("schema v4 should migrate"); - assert!((config.audio.beat_sensitivity - 0.6).abs() < f32::EPSILON); + assert_eq!(config.schema_version, CURRENT_SCHEMA_VERSION); + assert_eq!(config.daemon.start_scene, "Evening"); + assert_eq!(config.daemon.target_fps, DEFAULT_TARGET_FPS); + assert!((config.audio.beat_sensitivity - DEFAULT_BEAT_SENSITIVITY).abs() < f32::EPSILON); } #[test] @@ -156,10 +271,12 @@ fn outdated_schema_is_refused_and_names_the_file_and_the_fix() { /// The hand-migration lands an old file on the oldest schema the chain /// still upgrades, so later migrations run on it. Jumping straight to the -/// current schema would skip them and keep a retired beat threshold. +/// current schema would skip them and keep a retired beat threshold and +/// render target. #[test] fn following_the_hand_migration_runs_every_later_migration() { - let old = "schema_version = 3\n\n[audio]\nbeat_sensitivity = 0.6\n"; + let old = + "schema_version = 3\n\n[daemon]\ntarget_fps = 30\n\n[audio]\nbeat_sensitivity = 0.6\n"; let error = ConfigManager::parse_toml(old).expect_err("schema 3 must be refused"); let rendered = format!("{error:#}"); assert!(rendered.contains("schema_version = 4"), "{rendered}"); @@ -170,6 +287,7 @@ fn following_the_hand_migration_runs_every_later_migration() { assert_eq!(config.schema_version, CURRENT_SCHEMA_VERSION); assert!((config.audio.beat_sensitivity - DEFAULT_BEAT_SENSITIVITY).abs() < f32::EPSILON); + assert_eq!(config.daemon.target_fps, DEFAULT_TARGET_FPS); assert_eq!(config.input.daemon_route, InteractionRoutePolicy::Merge); assert_eq!(config.input.preview_route, InteractionRoutePolicy::Browser); } @@ -177,7 +295,7 @@ fn following_the_hand_migration_runs_every_later_migration() { #[test] fn newer_schema_is_refused_as_written_by_a_newer_hypercolor() { let tmp = tempfile::NamedTempFile::new().expect("failed to create temp file"); - fs::write(tmp.path(), "schema_version = 7\n").expect("failed to write future config"); + fs::write(tmp.path(), "schema_version = 8\n").expect("failed to write future config"); let error = ConfigManager::load(tmp.path()) .expect_err("a future schema must be refused, never guessed at"); @@ -187,7 +305,7 @@ fn newer_schema_is_refused_as_written_by_a_newer_hypercolor() { rendered.contains(&tmp.path().display().to_string()), "{rendered}" ); - assert!(rendered.contains("schema_version 7"), "{rendered}"); + assert!(rendered.contains("schema_version 8"), "{rendered}"); assert!(rendered.contains("newer hypercolor"), "{rendered}"); // A future file is not an old file: no hand-migration is offered. assert!( @@ -202,7 +320,7 @@ fn current_schema_keeps_explicit_route_fields() { fs::write( tmp.path(), concat!( - "schema_version = 6\n\n", + "schema_version = 7\n\n", "[input]\n", "daemon_route = \"host\"\n", "preview_route = \"merge\"\n", @@ -212,7 +330,7 @@ fn current_schema_keeps_explicit_route_fields() { let config = ConfigManager::load(tmp.path()).expect("current-schema config should load"); - assert_eq!(config.schema_version, 6); + assert_eq!(config.schema_version, 7); assert_eq!(config.input.daemon_route, InteractionRoutePolicy::Host); assert_eq!(config.input.preview_route, InteractionRoutePolicy::Merge); } @@ -339,9 +457,9 @@ fn new_with_missing_file_uses_defaults() { ConfigManager::new(path).expect("ConfigManager should fall back to defaults gracefully"); let config = manager.get(); - assert_eq!(config.schema_version, 6); + assert_eq!(config.schema_version, 7); assert_eq!(config.daemon.port, 9420); - assert_eq!(config.daemon.target_fps, 30); + assert_eq!(config.daemon.target_fps, 60); assert!(config.web.enabled); } @@ -760,7 +878,7 @@ mod change_stream { assert_eq!( drain_config_changes(&mut events), - vec![("daemon.target_fps".to_owned(), Some(json!(30)), json!(45))] + vec![("daemon.target_fps".to_owned(), Some(json!(60)), json!(45))] ); } @@ -870,7 +988,7 @@ mod change_stream { assert_eq!(manager.get().daemon.target_fps, 50); assert_eq!( drain_config_changes(&mut events), - vec![("daemon.target_fps".to_owned(), Some(json!(30)), json!(50))] + vec![("daemon.target_fps".to_owned(), Some(json!(60)), json!(50))] ); } @@ -940,7 +1058,7 @@ mod change_stream { assert_eq!( drain_config_changes(&mut events), - vec![("daemon.target_fps".to_owned(), Some(json!(30)), json!(50))] + vec![("daemon.target_fps".to_owned(), Some(json!(60)), json!(50))] ); } diff --git a/crates/hypercolor-daemon/src/durable_stores.rs b/crates/hypercolor-daemon/src/durable_stores.rs index 6856ac0cc..f4622a74d 100644 --- a/crates/hypercolor-daemon/src/durable_stores.rs +++ b/crates/hypercolor-daemon/src/durable_stores.rs @@ -114,7 +114,7 @@ const ATTACHMENT: u32 = hypercolor_types::attachment::CURRENT_ATTACHMENT_SCHEMA_ /// Every durable store this release reads or writes. pub const DURABLE_STORES: &[DurableStore] = &[ - // Reads schemas 4 through 6; 4 and 5 are upgraded in memory + // Reads schemas 4 through 7; 4, 5, and 6 are upgraded in memory // (hypercolor_core::config::ConfigManager::parse_toml). DurableStore { name: "config", diff --git a/crates/hypercolor-daemon/tests/daemon_integration_tests.rs b/crates/hypercolor-daemon/tests/daemon_integration_tests.rs index 650803847..63295ccca 100644 --- a/crates/hypercolor-daemon/tests/daemon_integration_tests.rs +++ b/crates/hypercolor-daemon/tests/daemon_integration_tests.rs @@ -421,7 +421,7 @@ async fn config_loading_defaults_have_correct_schema() { .boot; assert_eq!(config.schema_version, CURRENT_SCHEMA_VERSION); - assert_eq!(config.daemon.target_fps, 30); + assert_eq!(config.daemon.target_fps, 60); assert_eq!(config.daemon.port, 9420); assert_eq!(config.daemon.listen_address, "127.0.0.1"); assert_eq!(config.daemon.canvas_width, DEFAULT_CANVAS_WIDTH); @@ -645,7 +645,10 @@ async fn daemon_render_loop_uses_configured_fps() { let mut config = default_config(); config.daemon.target_fps = 30; - let toml_str = "schema_version = 5\n[daemon]\ntarget_fps = 30\n"; + // Only a current-schema file keeps 30; an older one carries the + // retired default and loads at the new one. + let toml_str = + format!("schema_version = {CURRENT_SCHEMA_VERSION}\n[daemon]\ntarget_fps = 30\n"); let mut temp = NamedTempFile::new().expect("create temp"); temp.write_all(toml_str.as_bytes()).expect("write"); temp.flush().expect("flush"); diff --git a/crates/hypercolor-daemon/tests/durable_store_tests.rs b/crates/hypercolor-daemon/tests/durable_store_tests.rs index fc05396a6..811b235c7 100644 --- a/crates/hypercolor-daemon/tests/durable_store_tests.rs +++ b/crates/hypercolor-daemon/tests/durable_store_tests.rs @@ -83,7 +83,7 @@ fn enforced_versions_come_from_the_stores_own_constants() { hypercolor_daemon::device_settings::DEVICE_SETTINGS_SCHEMA_VERSION ); for (name, min, max) in [ - ("config", 4, 6), + ("config", 4, 7), ("scenes", 2, 2), ("device-settings", 1, 3), ("device-aliases", 2, 2), diff --git a/crates/hypercolor-daemon/tests/startup_tests.rs b/crates/hypercolor-daemon/tests/startup_tests.rs index acfdef9e2..e37a068d5 100644 --- a/crates/hypercolor-daemon/tests/startup_tests.rs +++ b/crates/hypercolor-daemon/tests/startup_tests.rs @@ -629,7 +629,7 @@ async fn load_config_falls_back_to_defaults_when_no_file() { .expect("default config should load") .boot; assert_eq!(config.schema_version, CURRENT_SCHEMA_VERSION); - assert_eq!(config.daemon.target_fps, 30); + assert_eq!(config.daemon.target_fps, 60); assert_eq!(config.daemon.port, 9420); } @@ -639,7 +639,7 @@ async fn load_config_reads_toml_file() { schema_version = 5 [daemon] -target_fps = 30 +target_fps = 45 port = 8080 listen_address = "0.0.0.0" "#; @@ -655,7 +655,7 @@ listen_address = "0.0.0.0" )) .expect("config should load from file"); - assert_eq!(loaded.boot.daemon.target_fps, 30); + assert_eq!(loaded.boot.daemon.target_fps, 45); assert_eq!(loaded.boot.daemon.port, 8080); assert_eq!(loaded.boot.daemon.listen_address, "0.0.0.0"); assert_eq!(loaded.manager.path(), temp.path()); @@ -785,7 +785,7 @@ fn parse_config_toml_minimal() { // runs, so an old schema comes back migrated forward. assert_eq!(config.schema_version, CURRENT_SCHEMA_VERSION); // All sections should have serde defaults. - assert_eq!(config.daemon.target_fps, 30); + assert_eq!(config.daemon.target_fps, 60); assert!(config.audio.enabled); } @@ -861,11 +861,11 @@ fn parse_config_toml_refuses_an_outdated_schema() { #[test] fn parse_config_toml_refuses_a_newer_schema() { - let error = parse_config_toml("schema_version = 7\n") + let error = parse_config_toml("schema_version = 8\n") .expect_err("a future schema must be refused, not read"); let rendered = format!("{error:#}"); - assert!(rendered.contains("schema_version 7"), "{rendered}"); + assert!(rendered.contains("schema_version 8"), "{rendered}"); assert!(rendered.contains("newer hypercolor"), "{rendered}"); } @@ -904,8 +904,8 @@ fn parse_config_toml_rejects_invalid_toml() { #[test] fn default_config_has_sane_values() { let config = default_config(); - assert_eq!(config.schema_version, 6); - assert_eq!(config.daemon.target_fps, 30); + assert_eq!(config.schema_version, 7); + assert_eq!(config.daemon.target_fps, 60); assert_eq!(config.daemon.port, 9420); assert_eq!(config.daemon.listen_address, "127.0.0.1"); assert_eq!(config.daemon.canvas_width, DEFAULT_CANVAS_WIDTH); diff --git a/crates/hypercolor-types/src/config/daemon.rs b/crates/hypercolor-types/src/config/daemon.rs index 81f3a871f..db4d13271 100644 --- a/crates/hypercolor-types/src/config/daemon.rs +++ b/crates/hypercolor-types/src/config/daemon.rs @@ -4,6 +4,15 @@ use super::defaults; // ─── Daemon ────────────────────────────────────────────────────────────────── +/// Default `[daemon] target_fps`, the top render tier. +/// +/// The value is a ceiling, not a fixed rate: the render loop's FPS +/// controller never upshifts past it, and it downshifts on its own after +/// consecutive budget misses. A lower default would only strand headroom +/// on machines that can hold the full tier, while slower machines are +/// already protected by the downshift. +pub const DEFAULT_TARGET_FPS: u32 = 60; + /// Core daemon settings: networking, render loop, lifecycle. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct DaemonConfig { @@ -16,6 +25,7 @@ pub struct DaemonConfig { #[serde(default = "defaults::bool_true")] pub unix_socket: bool, + /// Highest render tier the FPS controller may run at. #[serde(default = "defaults::target_fps")] pub target_fps: u32, diff --git a/crates/hypercolor-types/src/config/mod.rs b/crates/hypercolor-types/src/config/mod.rs index a06177a51..5a737ace0 100644 --- a/crates/hypercolor-types/src/config/mod.rs +++ b/crates/hypercolor-types/src/config/mod.rs @@ -58,7 +58,7 @@ mod defaults { 9420 } pub fn target_fps() -> u32 { - 30 + super::DEFAULT_TARGET_FPS } pub fn canvas_width() -> u32 { 640 diff --git a/crates/hypercolor-types/src/config/root.rs b/crates/hypercolor-types/src/config/root.rs index de05173b4..be0c71dca 100644 --- a/crates/hypercolor-types/src/config/root.rs +++ b/crates/hypercolor-types/src/config/root.rs @@ -71,7 +71,7 @@ pub struct HypercolorConfig { } /// Current schema version for newly created configurations. -pub const CURRENT_SCHEMA_VERSION: u32 = 6; +pub const CURRENT_SCHEMA_VERSION: u32 = 7; impl HypercolorConfig { /// Decode a config document from an authoritative source, refusing any diff --git a/crates/hypercolor-types/tests/config_tests.rs b/crates/hypercolor-types/tests/config_tests.rs index 2a7dd6ca8..8d7416302 100644 --- a/crates/hypercolor-types/tests/config_tests.rs +++ b/crates/hypercolor-types/tests/config_tests.rs @@ -24,7 +24,7 @@ fn daemon_defaults_match_spec() { assert_eq!(d.listen_address, "127.0.0.1"); assert_eq!(d.port, 9420); assert!(d.unix_socket); - assert_eq!(d.target_fps, 30); + assert_eq!(d.target_fps, 60); assert_eq!(d.canvas_width, 640); assert_eq!(d.canvas_height, 480); assert_eq!(d.max_devices, 32); diff --git a/crates/hypercolor-ui/src/components/settings_sections.rs b/crates/hypercolor-ui/src/components/settings_sections.rs index 6d23613b8..234286687 100644 --- a/crates/hypercolor-ui/src/components/settings_sections.rs +++ b/crates/hypercolor-ui/src/components/settings_sections.rs @@ -1049,7 +1049,7 @@ pub fn RenderingSection(