linebender/druid · error
commands should be dispatched via dispatch_cmd
Error message
commands should be dispatched via dispatch_cmd
What it means
druid's AppHandler::do_window_event is the path for ordinary window events only. Command and targeted-command events must travel through `dispatch_cmd`, which routes them to the correct target window/delegate; receiving one here means a command was injected into the wrong dispatch path, so the handler panics to surface the invariant violation.
Solutions
- Route commands through `AppHandler::dispatch_cmd` (or higher-level `WidgetExt::submit_command` / `EventCtx::submit_command`) instead of feeding them into `do_window_event`
- In custom dispatch code, match on the event kind and forward Command variants to dispatch_cmd, passing only other events to do_window_event
- If you only hold a generic Event, inspect it: commands should never be re-dispatched manually; use the command's WindowId target with dispatch_cmd
Example fix
// before handler.do_window_event(window_id, Event::Command(cmd)); // after handler.dispatch_cmd(window_id, cmd); // or ctx.submit_command(cmd)
Defensive patterns
Strategy: validation
Validate before calling
// Route by event kind before touching do_window_event
match &event {
Event::Command(_) | Event::Internal(InternalEvent::TargetedCommand(_)) => handler.dispatch_cmd(window_id, event.unwrap_cmd()),
_ => handler.do_window_event(window_id, event),
} Prevention
- Always use ctx.submit_command / dispatch_cmd for commands
- Never forward raw Event values into do_window_event in custom loops
- In tests, use the same dispatch entry points the framework uses
When it happens
Trigger: Delivering `Event::Command` or `Event::Internal(InternalEvent::TargetedCommand(..))` directly into `do_window_event` — typically by calling the window-handler plumbing yourself, custom event loops, or code that bypasses `AppHandler::dispatch_cmd`.
Common situations: Custom main-loop or test harnesses pushing events straight to the handler; monkey-patched or forked druid code that forwards all window events uniformly; re-injecting a captured Event object back into the handler.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- TabsPolicy::Build called on a policy that does not support…
- command must carry a ContextMenu .
- Expected selector " " but the command was " ".
- {}
- There is no globally active Application
AI-assisted analysis of linebender/druid@0f8b1195e4 (2026-09-10).
Data as JSON: /api/errors/186f2ef2a16c8d08.
Report an issue: GitHub.
Appendix: source
Thrown at druid/src/win_handler.rs:419
let event = Event::Command(cmd.clone());
if w.event(&mut self.command_queue, event, &mut self.data, &self.env)
.is_handled()
{
return Handled::Yes;
}
}
}
Target::Auto => {
tracing::error!("{:?} reached window handler with `Target::Auto`", cmd);
}
}
Handled::No
}
fn do_window_event(&mut self, source_id: WindowId, event: Event) -> Handled {
match event {
Event::Command(..) | Event::Internal(InternalEvent::TargetedCommand(..)) => {
panic!("commands should be dispatched via dispatch_cmd");
}
_ => (),
}
// if the event was swallowed by the delegate we consider it handled?
let event = match self.delegate_event(source_id, event) {
Some(event) => event,
None => return Handled::Yes,
};
if let Some(win) = self.windows.get_mut(source_id) {
win.event(&mut self.command_queue, event, &mut self.data, &self.env)
} else {
Handled::No
}
}
fn show_context_menu(&mut self, window_id: WindowId, cmd: &Command) {View on GitHub (pinned to 0f8b1195e4)