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
- Verify the tab id exists (Tabs::tabs list) before dispatching the action, or rebuild state after tab removal.
- When handling close/middle-click events, check that the tab still exists and drop the action otherwise.
- 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
- Regenerate tab ids only from the Tabs widget itself, never from cached copies.
- Clear stale tab state whenever the panel is rebuilt.
- Ignore close actions for tabs that no longer exist.
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
- Initial panel outcome is
- This build of A/B Street stores player data in…
- Can't find the data/ directory
- CityName::new( , ) has a country code that isn't two letters
- Couldn't read_json( )
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)