cjpais/Handy · error

failed to create settings item

Error message

failed to create settings item

What it means

Panic when MenuItem::with_id fails to create the 'settings' item, which unlike the version item registers a keyboard accelerator ('Cmd+,' on macOS, 'Ctrl+,' elsewhere). Accelerator registration is the extra failure mode here: an accelerator string that muda cannot parse or that the platform menu layer rejects makes item creation fail, in addition to the generic duplicated-id/teardown causes.

Solutions

  1. Verify the accelerator is valid for the platform (keep the cfg(target_os = "macos") split for Cmd/Ctrl)
  2. Ensure 'settings' is unique within the menu build
  3. Retry creation with None as the accelerator (menu still works; keyboard shortcut lost) before giving up
  4. Include the accelerator in the panic message for fast diagnosis

Example fix

// before
let settings_i = MenuItem::with_id(
    app,
    "settings",
    &strings.settings,
    true,
    settings_accelerator,
)
.expect("failed to create settings item");

// after — fall back to no accelerator rather than aborting
let settings_i = MenuItem::with_id(app, "settings", &strings.settings, true, settings_accelerator)
    .or_else(|_| {
        log::warn!("settings item created without accelerator");
        MenuItem::with_id(app, "settings", &strings.settings, true, None::<&str>)
    })
    .expect("failed to create settings item");
Defensive patterns

Strategy: fallback

Validate before calling

// Validate the accelerator string per platform before creating the item
#[cfg(target_os = "macos")]
const SETTINGS_ACCEL: Option<&str> = Some("Cmd+,");
#[cfg(not(target_os = "macos"))]
const SETTINGS_ACCEL: Option<&str> = Some("Ctrl+,");
// keep accelerators compile-time constants per platform instead of runtime-computed strings

Try / catch

let settings_i = MenuItem::with_id(app, "settings", &strings.settings, true, settings_accelerator)
    .or_else(|_| {
        log::warn!("settings item lost its accelerator; recreating without");
        MenuItem::with_id(app, "settings", &strings.settings, true, None::<&str>)
    });

Prevention

When it happens

Trigger: Tray menu build where MenuItem::with_id(app, "settings", strings.settings, true, Some(accelerator)) errors — unparseable/unsupported accelerator for the platform, id collision, or rebuild racing shutdown.

Common situations: Forks changing the accelerator string to one the target platform's menu backend rejects; cross-platform builds where a platform-specific accelerator leaks to the wrong cfg branch; id reuse after menu restructuring.

Related errors


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

Appendix: source

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

        (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);
}

/// Records the current desired tray state and schedules one apply on the main
/// thread (or lets an already-pending apply pick it up). Never blocks on the
/// main thread.
///
/// The snapshot (settings, model list, loaded state) is computed on the
/// *calling* thread on purpose: the main-thread applier must not take manager
/// locks that a worker may hold across slow work (see #1716).

View on GitHub (pinned to fbd4e15fa1)