{"record":{"id":"877f52a04122167b","repo":"cjpais/Handy","slug":"failed-to-create-separator","errorCode":null,"errorMessage":"failed to create separator","messagePattern":"failed to create separator","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src-tauri/src/tray.rs","lineNumber":242,"sourceCode":"pub fn refresh_tray_icon(app: &AppHandle) {\n    sync_tray(app);\n}\n\n/// Re-syncs the tray after something the menu depends on changed (model\n/// list/selection/loaded state, language, settings).\npub fn update_tray_menu(app: &AppHandle) {\n    sync_tray(app);\n}\n\n/// Records the current desired tray state and schedules one apply on the main\n/// thread (or lets an already-pending apply pick it up). Never blocks on the\n/// main thread.\n///\n/// The snapshot (settings, model list, loaded state) is computed on the\n/// *calling* thread on purpose: the main-thread applier must not take manager\n/// locks that a worker may hold across slow work (see #1716).\npub fn sync_tray(app: &AppHandle) {\n    sync_tray_with(app, |_| {});\n}\n\nfn sync_tray_with(app: &AppHandle, update: impl FnOnce(&mut TrayInner)) {\n    let Some(state) = app.try_state::<TrayState>() else {\n        return;\n    };\n\n    // Record intent and claim a sequence number in one critical section, so\n    // sequence order == the order in which state changes were requested.\n    let (seq, icon_state) = {\n        let mut inner = state.lock();\n        update(&mut inner);\n        inner.next_seq += 1;\n        (inner.next_seq, inner.icon_state)\n    };\n\n    // Tray not built yet (early secure-input monitor callbacks). The intent\n    // is kept and picked up by the first sync after the tray exists.","sourceCodeStart":224,"sourceCodeEnd":260,"githubUrl":"https://github.com/cjpais/Handy/blob/fbd4e15fa14a721c66c57006ae110428b9e255b3/src-tauri/src/tray.rs#L224-L260","documentation":"The separator closure wraps PredefinedMenuItem::separator(app) with .expect(\"failed to create separator\"). Separators are the cheapest muda items but go through the same native menu manager; because update_tray_menu creates ~5-8 of them per rebuild, a failing backend often surfaces here first. The panic aborts whatever thread rebuilds the tray, leaving the previous menu in place or crashing the app outright.","triggerScenarios":"Any rebuild of the tray (state change to Recording/Transcribing/Idle, language switch, secure-input warning toggle) on a system where the native/DBus menu backend cannot register items; the closure runs multiple times per menu build, so it multiplies the chance of hitting the broken backend.","commonSituations":"Linux desktops without a system-tray applet or StatusNotifierWatcher; X11 sessions where the tray DBus name vanished mid-session; running in environments where the app was started before the session bus was ready.","solutions":["Ensure a StatusNotifierWatcher exists on the DBus session before the app starts (tray extension, waybar, plasma-systemtray)","Replace the .expect in the separator closure with Result propagation so the caller can abort the rebuild gracefully","Cache one separator creation failure and disable further tray rebuilds for the session instead of panicking repeatedly","Delay the initial update_tray_menu call until the tray icon is confirmed visible"],"exampleFix":"// before\nlet separator = || PredefinedMenuItem::separator(app).expect(\"failed to create separator\");\n\n// after\nlet separator = || PredefinedMenuItem::separator(app).map_err(|e| {\n    error!(\"Failed to create separator: {e}\");\n    e\n});\n// then propagate with ? in a update_tray_menu that returns Result<()>","handlingStrategy":"fallback","validationCode":null,"typeGuard":null,"tryCatchPattern":"let separator = || PredefinedMenuItem::separator(app).map_err(|e| { error!(\"separator creation failed: {e}\"); e });\n// caller: let Some(sep) = separator() else { return; };","preventionTips":["Wrap repeated constructor helpers (the separator closure) so a single failure aborts the build instead of panicking at N call sites","Feature-detect the tray backend once per session and cache the result instead of re-hitting a broken backend on every rebuild","Log the underlying muda error text — it distinguishes registrar/DBus issues from teardown races"],"tags":["tauri","tray","muda","rust","panic","linux"],"backgroundTag":"tauri-tray-menu-creation-failed","analyzedSha":"fbd4e15fa14a721c66c57006ae110428b9e255b3","analyzedAt":"2026-08-17T10:29:55.597Z","contentChangedAt":"2026-08-17T10:29:55.597Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}