{"record":{"id":"93e8aada2890bf89","repo":"GraphiteEditor/Graphite","slug":"failed-to-serialize-frontendmessage","errorCode":null,"errorMessage":"Failed to serialize FrontendMessage","messagePattern":"Failed to serialize FrontendMessage","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"frontend/wrapper/src/editor_wrapper.rs","lineNumber":135,"sourceCode":"\t}\n\n\t#[cfg(feature = \"editor\")]\n\tpub(crate) fn send_frontend_message_to_js(&self, message: FrontendMessage) {\n\t\tif let FrontendMessage::UpdateImageData { ref image_data } = message {\n\t\t\tlet new_hash = calculate_hash(image_data);\n\t\t\tlet prev_hash = IMAGE_DATA_HASH.load(Ordering::Relaxed);\n\n\t\t\tif new_hash != prev_hash {\n\t\t\t\trender_image_data_to_canvases(image_data.iter());\n\t\t\t\tIMAGE_DATA_HASH.store(new_hash, Ordering::Relaxed);\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\tlet message_type = message.to_discriminant().local_name();\n\n\t\tlet serializer = serde_wasm_bindgen::Serializer::new().serialize_large_number_types_as_bigints(true);\n\t\tlet message_data = message.serialize(&serializer).expect(\"Failed to serialize FrontendMessage\");\n\n\t\tlet js_return_value = self.frontend_message_handler_callback.call2(&JsValue::null(), &JsValue::from(message_type), &message_data);\n\n\t\tif let Err(error) = js_return_value {\n\t\t\terror!(\"While handling FrontendMessage {:?}, JavaScript threw an error:\\n{:?}\", message.to_discriminant().local_name(), error,)\n\t\t}\n\t}\n\n\tpub(crate) fn forward_serialized_frontend_message_to_js(&self, name: &str, data: crate::wasm_value::WasmValue) {\n\t\tlet js_return_value = self.frontend_message_handler_callback.call2(&JsValue::null(), &JsValue::from(name), &data.into());\n\n\t\tif let Err(error) = js_return_value {\n\t\t\terror!(\"While handling FrontendMessage {name:?}, JavaScript threw an error:\\n{error:?}\")\n\t\t}\n\t}\n\n\t#[cfg(all(feature = \"native\", target_family = \"wasm\"))]\n\tpub(crate) fn send(&self, command: EditorCommand) {","sourceCodeStart":117,"sourceCodeEnd":153,"githubUrl":"https://github.com/GraphiteEditor/Graphite/blob/c507b356453361e31638b8bff8f6d46b6da2961e/frontend/wrapper/src/editor_wrapper.rs#L117-L153","documentation":"Every `FrontendMessage` the Rust editor emits is converted to a JS value with `serde_wasm_bindgen` (with `serialize_large_number_types_as_bigints(true)`, so u64/i64 become JS `BigInt`) and handed to the JS callback registered at wrapper creation. The `.expect` fires when serialization returns `Err`, which happens for values that have no JS representation — classically non-string map keys (e.g. a `HashMap` keyed by a struct or tuple), unsupported number types, or a message variant whose payload type gained a field serde cannot map.","triggerScenarios":"Adding a `FrontendMessage` variant containing a map with non-string keys (struct/enum/tuple keys), a `usize`/`u128` field on a serializer path not covered by the bigints setting, or any `Serialize` impl that emits an unsupported construct; the first time that message is emitted after a user action, the wrapper panics instead of delivering data.","commonSituations":"Contributors adding new frontend message payloads during feature work; refactoring a keyed collection into a message (e.g. `HashMap<NodeId, …>` without converting keys to strings first); upgrading serde/serde_wasm_bindgen versions that change supported representations.","solutions":["Find the offending variant from the panic backtrace or by logging `message.to_discriminant().local_name()` just before serializing.","Change message payloads to use string keys (`HashMap<String, _>` / `BTreeMap<String, _>`) or serialize as a Vec of pairs.","Add a CI/test that serializes a sample of every `FrontendMessage` variant through the same `serde_wasm_bindgen::Serializer` configuration.","Replace the `.expect` with a match that logs the discriminant and skips the message so one bad variant doesn't kill the editor."],"exampleFix":"// before\nlet serializer = serde_wasm_bindgen::Serializer::new().serialize_large_number_types_as_bigints(true);\nlet message_data = message.serialize(&serializer).expect(\"Failed to serialize FrontendMessage\");\n\n// after\nlet serializer = serde_wasm_bindgen::Serializer::new().serialize_large_number_types_as_bigints(true);\nlet Ok(message_data) = message.serialize(&serializer) else {\n  error!(\"Failed to serialize FrontendMessage {:?}\", message.to_discriminant().local_name());\n  return;\n};","handlingStrategy":"fallback","validationCode":"// Rust: CI guard proving every FrontendMessage serializes like the wrapper does\nfn serializer() -> serde_wasm_bindgen::Serializer {\n  serde_wasm_bindgen::Serializer::new().serialize_large_number_types_as_bigints(true)\n}\n#[test]\nfn frontend_messages_serialize() {\n  for message in sample_of_every_frontend_message() {\n    assert!(message.serialize(&serializer()).is_ok(),\n      \"{:?} is not representable as a JS value\", message.to_discriminant());\n  }\n}","typeGuard":null,"tryCatchPattern":"JS cannot catch the wasm panic; after suspect operations check `await editor.hasCrashed()`. In the wrapper itself, match on serialize() and log-and-skip the message so one bad variant degrades instead of killing all frontend updates.","preventionTips":["Use string keys in any map carried by a FrontendMessage.","Test every message variant through serde_wasm_bindgen (not just serde_json) in CI.","Handle BigInt payloads (u64 serialized with serialize_large_number_types_as_bigints) on the TS side."],"tags":["serde-wasm-bindgen","serialization","frontend-message","panic","wasm"],"backgroundTag":"serde-wasm-bindgen-serialization-error","analyzedSha":"c507b356453361e31638b8bff8f6d46b6da2961e","analyzedAt":"2026-08-16T21:57:18.596Z","schemaVersion":2},"datasetVersion":"2026-08-16T23:17:17.608Z"}