{"record":{"id":"db95bd91965caf4a","repo":"a-b-street/abstreet","slug":"not-saving","errorCode":null,"errorMessage":"Not saving {}","messagePattern":"Not saving (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"abstio/src/io_web.rs","lineNumber":137,"sourceCode":"    // Only save for data/player, for now\n    if !path.starts_with(&path_player(\"\")) {\n        warn!(\"Not saving {}\", path);\n        return;\n    }\n\n    let window = web_sys::window().unwrap();\n    let storage = window.local_storage().unwrap().unwrap();\n    storage.set_item(&path, &abstutil::to_json(obj)).unwrap();\n}\n\npub fn write_binary<T: Serialize>(path: String, obj: &T) {\n    write_raw(path, &abstutil::to_binary(obj)).unwrap();\n}\n\npub fn write_raw(path: String, bytes: &[u8]) -> Result<()> {\n    // Only save for data/player, for now\n    if !path.starts_with(&path_player(\"\")) {\n        bail!(\"Not saving {}\", path);\n    }\n\n    let window = web_sys::window().unwrap();\n    let storage = window.local_storage().unwrap().unwrap();\n    // Local storage only supports strings, so base64 encoding needed\n    use base64::Engine;\n    let encoded = base64::engine::general_purpose::STANDARD.encode(bytes);\n    storage.set_item(&path, &encoded).map_err(|err| {\n        anyhow!(err.as_string().unwrap_or_else(|| format!(\n            \"set_item for {path} failed for encoded length {}\",\n            encoded.len()\n        )))\n    })?;\n    Ok(())\n}\n\npub fn delete_file<I: AsRef<str>>(path: I) {\n    let path = path.as_ref();","sourceCodeStart":119,"sourceCodeEnd":155,"githubUrl":"https://github.com/a-b-street/abstreet/blob/0964f29315820c91b171b585eb51e300164e9197/abstio/src/io_web.rs#L119-L155","documentation":"abstio::write_raw on web only persists paths under the player data directory (localStorage); writing anywhere else is refused with \"Not saving {path}\". This is intentional: web builds cannot write to the real filesystem, so arbitrary paths are rejected rather than silently dropped.","triggerScenarios":"Calling write_raw / write_binary with a path outside data/player/ on the web target — e.g. saving maps, settings, or recordings to data/system or other native-only locations.","commonSituations":"Porting native code that writes arbitrary output files to web; attempting to export large files expecting a real download; assuming symmetric read/write support on web.","solutions":["On web, write only to paths under the player data directory.","For user-facing exports, use the download-style API (write_file) which triggers a browser download instead.","Gate saving code paths with cfg(not(target_arch = \"wasm32\")) or check the target before attempting the write."],"exampleFix":"// before\nabstio::write_binary(\"../data/player/settings.bin\", &settings);\n// after\n#[cfg(target_arch = \"wasm32\")]\nabstio::write_file(\"settings.bin\", abstutil::to_binary(&settings));\n#[cfg(not(target_arch = \"wasm32\"))]\nabstio::write_binary(\"../data/player/settings.bin\", &settings);","handlingStrategy":"validation","validationCode":"fn can_persist_on_web(path: &str) -> bool { path.starts_with(\"../data/player/\") }","typeGuard":null,"tryCatchPattern":"if abstio::write_raw(path, bytes).is_err() {\n    log::warn!(\"web build cannot save {} - skipping\", path);\n}","preventionTips":["Only write under data/player/ in web builds.","Use write_file for downloads; write_raw only for player persistence.","Compile-time gate file writes with cfg(not(target_arch = \"wasm32\"))."],"tags":["web","wasm","file-write","unsupported-operation"],"backgroundTag":"unsupported-operation","analyzedSha":"0964f29315820c91b171b585eb51e300164e9197","analyzedAt":"2026-09-13T18:02:03.421Z","contentChangedAt":"2026-09-13T18:02:03.421Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}