{"record":{"id":"75cb957ceaa9e127","repo":"a-b-street/abstreet","slug":"two-buttons-in-one-panel-both-use-action","errorCode":null,"errorMessage":"Two buttons in one Panel both use action {}","messagePattern":"Two buttons in one Panel both use action (.+?)","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"widgetry/src/widgets/mod.rs","lineNumber":717,"sourceCode":"                    nodes,\n                    x + dx,\n                    y + dy,\n                    scroll_offset,\n                    ctx,\n                    recompute_layout,\n                    defer_draw,\n                );\n            }\n        } else {\n            self.widget.set_pos(top_left);\n        }\n    }\n\n    fn get_all_click_actions(&self, actions: &mut HashSet<String>) {\n        if let Some(btn) = self.widget.downcast_ref::<Button>() {\n            if btn.is_enabled() {\n                if actions.contains(&btn.action) {\n                    panic!(\"Two buttons in one Panel both use action {}\", btn.action);\n                }\n                actions.insert(btn.action.clone());\n            }\n        } else if let Some(container) = self.widget.downcast_ref::<Container>() {\n            for w in &container.members {\n                w.get_all_click_actions(actions);\n            }\n        }\n    }\n\n    fn currently_hovering(&self) -> Option<&String> {\n        if let Some(btn) = self.widget.downcast_ref::<Button>() {\n            if btn.hovering {\n                return Some(&btn.action);\n            }\n        } else if let Some(checkbox) = self.widget.downcast_ref::<Toggle>() {\n            if checkbox.btn.hovering {\n                return Some(&checkbox.btn.action);","sourceCodeStart":699,"sourceCodeEnd":735,"githubUrl":"https://github.com/a-b-street/abstreet/blob/0964f29315820c91b171b585eb51e300164e9197/widgetry/src/widgets/mod.rs#L699-L735","documentation":"Panel builds a map from click action strings to handlers; if two enabled Buttons in the same Panel share the same action string, event routing would be ambiguous, so the library panics. Action strings must be unique within a Panel.","triggerScenarios":"Creating two Button widgets with the same `.action(...)` string (e.g. reusing \"back\" or a constant) and both being enabled when the Panel lays out and calls get_all_click_actions.","commonSituations":"Copy-pasting button construction without renaming the action; a loop that adds several buttons but reuses one action constant; two containers each adding a similarly-named button.","solutions":["Rename one button's action to a unique string (e.g. prefix with the section name)","If both buttons should do the same thing, keep one and reuse the same widget instead of duplicating","Only one of the buttons may be enabled at a time — the check skips disabled buttons","Generate action strings programmatically to guarantee uniqueness"],"exampleFix":"// before\nbtn1 = Button::text(\"Save\", \"save\");\nbtn2 = Button::text(\"Save\", \"save\");\n// after\nbtn2 = Button::text(\"Save\", \"save_final\");","handlingStrategy":"validation","validationCode":"let mut seen = std::collections::HashSet::new();\nfor action in [btn1.action.clone(), btn2.action.clone()] {\n    assert!(seen.insert(action), \"duplicate action {}\", action);\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Keep action strings in an enum mapped to unique strings","Never duplicate button constructors; reuse one widget when behavior is identical","Remember the check only counts enabled buttons"],"tags":["rust","panel","button","duplicate-key"],"backgroundTag":"conflicting-config-options","analyzedSha":"0964f29315820c91b171b585eb51e300164e9197","analyzedAt":"2026-09-13T18:02:03.421Z","contentChangedAt":"2026-09-13T18:02:03.421Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}