{"record":{"id":"da4d146463590174","repo":"gitbutlerapp/gitbutler","slug":"symbols-must-always-be-initialized","errorCode":null,"errorMessage":"symbols must always be initialized","messagePattern":"symbols must always be initialized","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/but/src/theme.rs","lineNumber":392,"sourceCode":"\n/// Helper — builds a bold + colored [`Style`].\nconst fn style_fg_bold(fg: Color) -> Style {\n    Style::new().fg(fg).add_modifier(Modifier::BOLD)\n}\n\nimpl Default for Theme {\n    /// Produces the canonical color palette.\n    fn default() -> Self {\n        Self::default_for(ThemePreset::Dark)\n    }\n}\n\nimpl Theme {\n    /// Get the symbols for this theme.\n    pub fn sym(&self) -> &ThemeSymbols {\n        self.symbols\n            .as_ref()\n            .expect(\"symbols must always be initialized\")\n    }\n\n    /// Produces a specific default color palette.\n    pub fn default_for(preset: ThemePreset) -> Self {\n        let mut t = match preset {\n            ThemePreset::Light => Self::default_light(),\n            ThemePreset::Dark => Self::default_dark(),\n        };\n        t.symbols = Some(ThemeSymbols::new(&t));\n        t\n    }\n\n    /// Load the syntax highlighting theme.\n    pub fn load_syntax_highlighting_theme(&self) -> anyhow::Result<highlighting::Theme> {\n        Ok(ThemeSet::load_from_reader(&mut std::io::Cursor::new(\n            self.syntax_highlighting_theme_raw,\n        ))?)\n    }","sourceCodeStart":374,"sourceCodeEnd":410,"githubUrl":"https://github.com/gitbutlerapp/gitbutler/blob/2497b8007aa4a1922dae9a805b32ffe5b5037785/crates/but/src/theme.rs#L374-L410","documentation":"Theme::sym() returns the derived ThemeSymbols stored as an Option field, because symbols can only be constructed from the fully-built theme (default_for sets t.symbols = Some(ThemeSymbols::new(&t)) last). The field is #[serde(skip_serializing, skip_deserializing)] (theme.rs:356-363), so any Theme produced by deserialization — notably theme::load() reading a custom JSON theme file — has symbols == None, and the first sym() call panics. Every construction path that bypasses default_for (serde, struct literals) hits this.","triggerScenarios":"Loading a custom theme from a JSON file via theme::load() (theme.rs:93-97 returns the deserialized value without filling symbols) and then rendering anything that calls Theme::sym() — TUI symbols/icons on first draw. Also manually constructing a Theme literal in code without the final symbols step.","commonSituations":"A user-supplied theme file configured through but's config; tests/tools building a Theme via serde_json::from_*; partial theme JSON (absent fields keep defaults, but symbols is skipped entirely so it stays None regardless of file contents).","solutions":["Finish deserialized themes: in theme::load(), after serde_json::from_str, set theme.symbols = Some(ThemeSymbols::new(&theme)) (or add a pub fn finish() and call it wherever a Theme is built)","If calling from outside the crate, construct via Theme::default_for(preset) and overlay JSON fields instead of deserializing a full Theme","Make sym() degrade to a static default symbol table instead of expecting","Add a regression test: load a JSON theme, then call sym()"],"exampleFix":"// before (theme::load)\nlet theme: Theme = serde_json::from_str(&contents)?;\nOk(theme)\n\n// after — complete post-deserialization initialization\nlet mut theme: Theme = serde_json::from_str(&contents)?;\ntheme.symbols = Some(ThemeSymbols::new(&theme));\nOk(theme)","handlingStrategy":"try-catch","validationCode":"// probe a custom-theme-loaded Theme before relying on it in a host process\nlet ok = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| theme.sym())).is_ok();\nif !ok { /* fall back to Theme::default_for(ThemePreset::Dark) */ }","typeGuard":null,"tryCatchPattern":"// degrade to the default palette's symbols instead of crashing the TUI\nlet theme = std::panic::catch_unwind(AssertUnwindSafe(|| theme::load(path)))\n    .ok()\n    .map(|mut t| { t.symbols = Some(ThemeSymbols::new(&t)); t }) // when accessible\n    .unwrap_or_else(Theme::default);","preventionTips":["Construct themes via Theme::default_for(preset); it performs the mandatory symbols post-init step","Any Theme that round-tripped through serde (theme::load, from_str) needs symbols re-initialized before sym() is called — pair load() with that step","Add a regression test: load a JSON theme then call sym()","Prefer fixing load() to finish initialization rather than guarding every sym() call site"],"tags":["rust","theme","serde","custom-theme","tui","post-load-init"],"backgroundTag":"post-deserialization-initialization-missing","analyzedSha":"2497b8007aa4a1922dae9a805b32ffe5b5037785","analyzedAt":"2026-08-17T00:30:25.648Z","schemaVersion":2},"datasetVersion":"2026-08-17T04:17:16.089Z"}