a-b-street/abstreet · error

invalid tab id

Error message

invalid tab id: {}

What it means

Tabs::handle_action is dispatched a TabAction carrying a tab id; it looks up the tab whose tab_id matches the action. If no tab has that id, it panics with "invalid tab id", because a tab action referencing a nonexistent tab is a programming bug rather than a recoverable condition.

Solutions

  1. Verify the tab id exists (Tabs::tabs list) before dispatching the action, or rebuild state after tab removal.
  2. When handling close/middle-click events, check that the tab still exists and drop the action otherwise.
  3. Regenerate or re-sync any cached tab ids after the panel is rebuilt.

Example fix

// before
ctx.canvas.new_event(WidgetAction::Tabs(TabAction::SwitchTab("stale_id".to_string())));
// after
if tabs.tabs.iter().any(|t| t.tab_id == "stale_id") {
    ctx.canvas.new_event(WidgetAction::Tabs(TabAction::SwitchTab("stale_id".to_string())));
}
Defensive patterns

Strategy: validation

Validate before calling

fn tab_exists(tabs: &Tabs, id: &str) -> bool { tabs.tabs.iter().any(|t| t.tab_id == id) }

Try / catch

// Panic-based; guard before dispatch:
if tab_exists(&tabs, &id) { ctx.canvas.new_event(WidgetAction::Tabs(TabAction::SwitchTab(id))); }

Prevention

When it happens

Trigger: Sending a TabAction (click, close, etc.) for a tab id that is not present in the Tabs widget's tab list — e.g. the tab was already removed, the id came from stale state, or a mismatched id constant was used.

Common situations: Closing a tab twice; holding tab indices/ids across a rebuild of the panel; dynamic tabs whose ids change; typos in hardcoded tab id strings.

Understand the failure class

Background: Record Not Found Errors: "not found", RecordNotFound, and "was not found" — what they mean and how to fix them — this error's family across 28 libraries.

Related errors


AI-assisted analysis of a-b-street/abstreet@0964f29315 (2026-09-13). Data as JSON: /api/errors/a3711b590429aa07. Report an issue: GitHub.

Appendix: source

Thrown at widgetry/src/widgets/tabs.rs:81

            self.build_bar_items(ctx),
            self.pop_active_content()
                .container()
                .tab_body(ctx)
                .named(self.active_content_id()),
        ])
    }

    pub fn handle_action(&mut self, ctx: &EventCtx, action: &str, panel: &mut Panel) -> bool {
        if !action.starts_with(&self.id) {
            return false;
        }

        let tab_idx = self
            .tabs
            .iter()
            .enumerate()
            .find(|(_idx, tab)| tab.tab_id == action)
            .unwrap_or_else(|| panic!("invalid tab id: {}", action))
            .0;
        self.activate_tab(ctx, tab_idx, panel);
        true
    }

    pub fn active_tab_idx(&self) -> usize {
        self.active_tab_idx
    }

    fn active_content_id(&self) -> String {
        format!("{}_active_content", self.id)
    }

    fn bar_items_id(&self) -> String {
        format!("{}_bar_items", self.id)
    }

    fn tab_id(&self, tab_index: usize) -> String {

View on GitHub (pinned to 0964f29315)