{"record":{"id":"25e7caa02e21d464","repo":"flxzt/rnote","slug":"engine-snapshot-does-not-contain-stroke-component","errorCode":null,"errorMessage":"engine snapshot does not contain 'stroke_components'.","messagePattern":"engine snapshot does not contain 'stroke_components'\\.","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/rnote-engine/src/fileformats/rnoteformat/maj0min15.rs","lineNumber":25,"sourceCode":"#[derive(Debug, Clone, Serialize, Deserialize)]\npub struct RnoteFileMaj0Min15 {\n    /// A snapshot of the engine.\n    #[serde(rename = \"engine_snapshot\")]\n    pub engine_snapshot: ijson::IValue,\n}\n\nimpl TryFrom<RnoteFileMaj0Min13> for RnoteFileMaj0Min15 {\n    type Error = anyhow::Error;\n\n    fn try_from(mut value: RnoteFileMaj0Min13) -> Result<Self, Self::Error> {\n        let engine_snapshot = value\n            .engine_snapshot\n            .as_object_mut()\n            .ok_or(anyhow!(\"engine snapshot is not a JSON object.\"))?;\n\n        for comp in engine_snapshot\n            .get_mut(\"stroke_components\")\n            .ok_or(anyhow!(\n                \"engine snapshot does not contain 'stroke_components'.\"\n            ))?\n            .as_array_mut()\n            .ok_or(anyhow!(\"stroke components is not a JSON array.\"))?\n            .iter_mut()\n        {\n            let value = comp\n                .as_object_mut()\n                .ok_or(anyhow!(\"stroke component is not a JSON object.\"))?\n                .get_mut(\"value\")\n                .ok_or(anyhow!(\"stroke component does not contain 'value'.\"))?;\n            if value.is_null() {\n                continue;\n            }\n            if let Some(shapestroke) = value\n                .as_object_mut()\n                .ok_or(anyhow!(\"value is not a JSON object.\"))?\n                .get_mut(\"shapestroke\")","sourceCodeStart":7,"sourceCodeEnd":43,"githubUrl":"https://github.com/flxzt/rnote/blob/bbc5354502ba2fc83eec2670b535348825e679a6/crates/rnote-engine/src/fileformats/rnoteformat/maj0min15.rs#L7-L43","documentation":"Thrown during the maj0min13 -> maj0min15 migration when the engine snapshot object exists but has no `stroke_components` key. The migration iterates all stroke components to transform their values, so the key is mandatory in a valid snapshot.","triggerScenarios":"Converting an RnoteFileMaj0Min13 whose engine_snapshot object lacks `stroke_components` — a snapshot written by a different schema version, a manually built object, or a file that was damaged/truncated.","commonSituations":"Opening .rnote files from third-party tools or very early builds that used a different snapshot schema; test fixtures with minimal snapshots; corruption during file transfer.","solutions":["Inspect the snapshot JSON in the .rnote archive and add an empty `\"stroke_components\": []` array if the file otherwise looks valid","Re-save or re-export the document from the rnote version that produced it","In code, insert the key with an empty array when missing instead of failing"],"exampleFix":"// before\nfor comp in engine_snapshot\n    .get_mut(\"stroke_components\")\n    .ok_or(anyhow!(\"engine snapshot does not contain 'stroke_components'.\"))?;\n// after\nlet comps = engine_snapshot\n    .entry(\"stroke_components\")\n    .or_insert_with(|| ijson::IArray::new().into())\n    .as_array_mut()\n    .ok_or(anyhow!(\"stroke components is not a JSON array.\"))?;","handlingStrategy":"validation","validationCode":"if engine_snapshot.get(\"stroke_components\").is_none() {\n    return Err(anyhow!(\"refusing migration: snapshot missing 'stroke_components'\"));\n}","typeGuard":"fn has_stroke_components(snapshot: &ijson::IObject) -> bool {\n    snapshot.get(\"stroke_components\").is_some()\n}","tryCatchPattern":"// handle the missing key explicitly on conversion\nmatch RnoteFileMaj0Min15::try_from(file_maj0min13) {\n    Ok(f) => f,\n    Err(e) if e.to_string().contains(\"stroke_components\") => {\n        // offer recovery: insert empty array and retry, or fail with clear message\n    }\n    Err(e) => return Err(e),\n}","preventionTips":["Validate snapshot schema (stroke_components present) before migrating","Only migrate files produced by the matching rnote version","Insert `\"stroke_components\": []` for empty documents rather than omitting the key"],"tags":["json","migration","file-format","rust"],"backgroundTag":"missing-required-config-field","analyzedSha":"bbc5354502ba2fc83eec2670b535348825e679a6","analyzedAt":"2026-09-08T13:20:33.747Z","contentChangedAt":"2026-09-08T13:20:33.747Z","schemaVersion":2},"datasetVersion":"2026-09-16T09:17:16.951Z"}