cjpais/Handy · error

failed to create version item

Error message

failed to create version item

What it means

Panic when MenuItem::with_id fails to create the disabled 'version' information item for the tray menu. muda item creation is fallible; for this item (no accelerator, disabled) failure means the menu subsystem could not allocate the item — duplicated id, or tray rebuild racing app teardown so the app handle cannot service the request.

Solutions

  1. Keep 'version' unique within each menu build and centralize tray ids in one module
  2. Debounce or skip tray rebuilds once shutdown has started
  3. Convert expect to logged degradation (build the menu without the version line) if the item is non-essential

Example fix

// before
let version_i = MenuItem::with_id(app, "version", &version_label, false, None::<&str>)
    .expect("failed to create version item");

// after — include ids in diagnostics when tray creation fails
let version_i = MenuItem::with_id(app, "version", &version_label, false, None::<&str>)
    .unwrap_or_else(|e| panic!("failed to create version item (id='version'): {e}"));
Defensive patterns

Strategy: fallback

Try / catch

let version_i = match MenuItem::with_id(app, "version", &version_label, false, None::<&str>) {
    Ok(item) => Some(item),
    Err(e) => {
        log::warn!("version tray item skipped: {e}");
        None
    }
};

Prevention

When it happens

Trigger: Tray creation/refresh where MenuItem::with_id(app, "version", version_label, false, None) errors — id collision within the same menu, or rebuild during shutdown.

Common situations: Theme-change tray rebuilds racing quit; forks reusing the 'version' id for another item; running on a system where the tray/status-item host is going away mid-build.

Related errors


AI-assisted analysis of cjpais/Handy@fbd4e15fa1 (2026-08-17). Data as JSON: /api/errors/8cd45bb1bec188dc. Report an issue: GitHub.

Appendix: source

Thrown at src-tauri/src/tray.rs:214

            // Linux never sets the warning flag (Secure Input is macOS-only),
            // but fall back to the normal icon just in case.
            AppTheme::Colored => "resources/handy.png",
        };
    }
    match (theme, state) {
        // Dark theme uses light icons
        (AppTheme::Dark, TrayIconState::Idle) => "resources/tray_idle.png",
        (AppTheme::Dark, TrayIconState::Recording) => "resources/tray_recording.png",
        (AppTheme::Dark, TrayIconState::Transcribing) => "resources/tray_transcribing.png",
        // Light theme uses dark icons
        (AppTheme::Light, TrayIconState::Idle) => "resources/tray_idle_dark.png",
        (AppTheme::Light, TrayIconState::Recording) => "resources/tray_recording_dark.png",
        (AppTheme::Light, TrayIconState::Transcribing) => "resources/tray_transcribing_dark.png",
        // Colored theme uses pink icons (for Linux)
        (AppTheme::Colored, TrayIconState::Idle) => "resources/handy.png",
        (AppTheme::Colored, TrayIconState::Recording) => "resources/recording.png",
        (AppTheme::Colored, TrayIconState::Transcribing) => "resources/transcribing.png",
    }
}

/// Sets the recording state shown by the tray (icon + Cancel/model menu).
pub fn set_tray_state(app: &AppHandle, state: TrayIconState) {
    sync_tray_with(app, |inner| inner.icon_state = state);
}

/// Re-syncs the tray after something other than the recording state changed
/// (theme, Secure Input warning). The recording state itself is preserved.
pub fn refresh_tray_icon(app: &AppHandle) {
    sync_tray(app);
}

/// Re-syncs the tray after something the menu depends on changed (model
/// list/selection/loaded state, language, settings).
pub fn update_tray_menu(app: &AppHandle) {
    sync_tray(app);
}

View on GitHub (pinned to fbd4e15fa1)