{"record":{"id":"ee5c524bb22c6d89","repo":"slint-ui/slint","slug":"callbacks-were-set-up-earlier","errorCode":null,"errorMessage":"Callbacks were set up earlier","messagePattern":"Callbacks were set up earlier","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"tools/lsp/connector/wasm.rs","lineNumber":279,"sourceCode":"\nimpl editor_preview::LspToPreview for WasmLspToPreview {\n    fn send(&self, message: &LspToPreviewMessage) {\n        let _ = self.server_notifier.send_notification::<LspToPreviewMessage>(message.clone());\n    }\n\n    fn preview_target(&self) -> PreviewTarget {\n        PreviewTarget::EmbeddedWasm\n    }\n}\n\n#[derive(Default)]\nstruct WasmPreviewToLsp {}\n\nimpl editor_preview::PreviewToLsp for WasmPreviewToLsp {\n    fn send(&self, message: &PreviewToLspMessage) -> editor_preview::Result<()> {\n        WASM_CALLBACKS.with_borrow(|callbacks| {\n            let notifier = js_sys::Function::from(\n                (callbacks.as_ref().expect(\"Callbacks were set up earlier\").lsp_notifier).clone(),\n            );\n            let value = serde_wasm_bindgen::to_value(&message)?;\n            notifier\n                .call1(&JsValue::UNDEFINED, &value)\n                .map_err(|_| \"Failed to send message to LSP\".to_string())?;\n            Ok(())\n        })\n    }\n}\n\nfn init_slintpad_specific_ui(api: &crate::preview::ui::Api) {\n    if !WASM_CALLBACKS.with_borrow(|callbacks| {\n        callbacks.as_ref().map_or(false, |cb| cb.invoke_slintpad_callback.is_function())\n    }) {\n        return;\n    }\n\n    api.set_runs_in_slintpad(true);","sourceCodeStart":261,"sourceCodeEnd":297,"githubUrl":"https://github.com/slint-ui/slint/blob/bb937076de3f7919766c1f25e2e969367cf77e9a/tools/lsp/connector/wasm.rs#L261-L297","documentation":"The WASM preview connector stores JS callbacks (lsp_notifier, resource_url_mapper, invoke_slintpad_callback) in a thread-local `WASM_CALLBACKS: RefCell<Option<WasmCallbacks>>` that starts as None and is only populated by `PreviewConnector::create`. When `WasmPreviewToLsp::send` runs, it expects that initialization already happened and panics via `.expect(\"Callbacks were set up earlier\")` if the Option is still None. This is an internal initialization-order invariant: the LSP->preview channel can only be created by `create`, so callbacks must exist by the time messages flow.","triggerScenarios":"Calling `WasmPreviewToLsp::send` (i.e. the preview UI trying to notify the LSP side) before JS invoked `PreviewConnector.create(lspNotifier, resourceUrlMapper, style, invokeSlintpadCallback)`, or after a failed/rejected `create` that nonetheless left a partially constructed preview state that still emits messages.","commonSituations":"Integrating slintpad/slint LSP wasm into a custom JS shell where the connector object is created but `PreviewConnector.create` is not awaited before the UI sends an early message; a JS exception inside `create` rejecting the promise after callbacks were not set; double-loading the wasm module so a second instance's preview state sends through an uninitialized first instance.","solutions":["Ensure the JS host calls and awaits the promise returned by `PreviewConnector.create(...)` before performing any action that triggers preview-to-LSP traffic (opening a file, editing, UI events).","If wiring the connector manually, construct it exactly once per wasm module instance and reuse that instance, rather than creating a second PreviewConnector in the same module.","Check the browser console for a JS error thrown during `create` (e.g. 'PreviewConnector already set up.') that left initialization incomplete, and fix that root cause.","As a defensive patch, replace the expect in send with an early return/error so a missing notifier logs instead of panicking the wasm instance."],"exampleFix":"// JS: before\nconst connector = new slint_lsp.PreviewConnector();\nconnector.create(notify, mapUrl, \"fluent-dark\", cb); // not awaited, sends happen too early\n\n// JS: after\nconst connector = new slint_lsp.PreviewConnector();\nawait connector.create(notify, mapUrl, \"fluent-dark\", cb); // callbacks registered before use\n// now safe to open files / trigger preview messages","handlingStrategy":"type-guard","validationCode":"// JS, before any preview interaction\nif (!connectorInitialized) {\n  throw new Error(\"PreviewConnector.create() has not resolved yet\");\n}","typeGuard":"// JS\nfunction callbacksReady(conn) {\n  return conn && typeof conn.__lspNotifier === \"function\";\n}","tryCatchPattern":"// wrap create and gate traffic\ntry {\n  await connector.create(notify, mapUrl, style, cb);\n  connectorInitialized = true;\n} catch (e) {\n  console.error(\"preview connector init failed\", e);\n}","preventionTips":["Always await the PreviewConnector.create promise before sending or triggering preview messages.","Create the connector exactly once per wasm module instance.","Gate all UI actions and file opens behind the initialized flag.","Watch the console for 'PreviewConnector already set up.' rejections."],"tags":["wasm","initialization-order","lsp","panic"],"backgroundTag":"internal-invariant-violation","analyzedSha":"bb937076de3f7919766c1f25e2e969367cf77e9a","analyzedAt":"2026-09-16T01:37:20.251Z","contentChangedAt":"2026-09-16T01:37:20.251Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}