GraphiteEditor/Graphite · error
Invalid modifier keys
Error message
Invalid modifier keys
What it means
`on_mouse_move` is the wasm-bindgen entry point called from TypeScript (`editor.onMouseMove(x, y, buttons, modifiers)` in frontend/src/utility-functions/input.ts:126). It converts the raw `modifiers: u8` bitmask into the `ModifierKeys` bitflags type, whose only defined bits are SHIFT=1, ALT=2, CONTROL=4, META_OR_COMMAND=8 (input_keyboard.rs:50-56). Bitflags' `from_bits` returns `None` for any value with bits outside that set (i.e. anything > 0b0000_1111), and the `.expect` turns that into a panic that kills the editor session.
Source
Thrown at frontend/wrapper/src/editor_commands.rs:304
localized_commit_date,
localized_commit_year,
}
.into()
}
fn request_licenses_third_party_dialog_with_license_text(license_text: String) -> Message {
DialogMessage::RequestLicensesThirdPartyDialogWithLicenseText { license_text }.into()
}
/// Send new viewport info to the backend
fn update_viewport(x: f64, y: f64, width: f64, height: f64, scale: f64) -> Message {
ViewportMessage::Update { x, y, width, height, scale }.into()
}
/// Mouse movement within the screenspace bounds of the viewport
fn on_mouse_move(x: f64, y: f64, mouse_keys: u8, modifiers: u8) -> Message {
let editor_mouse_state = EditorMouseState::from_keys_and_editor_position(mouse_keys, (x, y).into());
let modifier_keys = ModifierKeys::from_bits(modifiers).expect("Invalid modifier keys");
InputPreprocessorMessage::PointerMove { editor_mouse_state, modifier_keys }.into()
}
/// Mouse scrolling within the screenspace bounds of the viewport
fn on_wheel_scroll(x: f64, y: f64, mouse_keys: u8, wheel_delta_x: f64, wheel_delta_y: f64, wheel_delta_z: f64, modifiers: u8) -> Message {
let mut editor_mouse_state = EditorMouseState::from_keys_and_editor_position(mouse_keys, (x, y).into());
editor_mouse_state.scroll_delta = ScrollDelta::new(wheel_delta_x, wheel_delta_y, wheel_delta_z);
let modifier_keys = ModifierKeys::from_bits(modifiers).expect("Invalid modifier keys");
InputPreprocessorMessage::WheelScroll { editor_mouse_state, modifier_keys }.into()
}
/// A mouse button depressed within screenspace the bounds of the viewport
fn on_mouse_down(x: f64, y: f64, mouse_keys: u8, modifiers: u8) -> Message {
let editor_mouse_state = EditorMouseState::from_keys_and_editor_position(mouse_keys, (x, y).into());
let modifier_keys = ModifierKeys::from_bits(modifiers).expect("Invalid modifier keys");
InputPreprocessorMessage::PointerDown { editor_mouse_state, modifier_keys }.into()
}
View on GitHub (pinned to c507b35645)
Solutions
- Compute the mask exactly like the shipped frontend: `(shiftKey<<0) | (altKey<<1) | (ctrlKey<<2) | (metaKey<<3)` and never add extra bits.
- If you control the wrapper source, use `ModifierKeys::from_bits_truncate(modifiers)` so unknown bits are dropped instead of panicking.
- Mask the value before crossing the boundary: `modifiers & 0b1111`.
- After upgrading either side, regenerate/reinstall bindings so TS and Rust agree on the bit layout.
Example fix
// before
let modifier_keys = ModifierKeys::from_bits(modifiers).expect("Invalid modifier keys");
InputPreprocessorMessage::PointerMove { editor_mouse_state, modifier_keys }.into()
// after
let modifier_keys = ModifierKeys::from_bits_truncate(modifiers); // drops unknown bits instead of panicking
InputPreprocessorMessage::PointerMove { editor_mouse_state, modifier_keys }.into() Defensive patterns
Strategy: type-guard
Validate before calling
// TS: clamp the mask to the four defined bits before every wrapper call const MODIFIERS_MASK = 0b1111; // SHIFT | ALT | CONTROL | META_OR_COMMAND const safe = makeKeyboardModifiersBitfield(e) & MODIFIERS_MASK; editor.onMouseMove(e.clientX, e.clientY, e.buttons, safe);
Type guard
function isValidModifierMask(v: unknown): v is number {
return typeof v === 'number' && Number.isInteger(v) && v >= 0 && v <= 0b1111;
} Prevention
- Always build the mask from the four DOM booleans (shiftKey/altKey/ctrlKey/metaKey) exactly like makeKeyboardModifiersBitfield.
- Mask with & 0b1111 at the FFI boundary so unknown bits can never cross.
- Regenerate TS bindings whenever ModifierKeys changes; deploy JS and wasm together.
When it happens
Trigger: Calling `editor.onMouseMove(x, y, mouseKeys, modifiers)` with a modifiers value that has any bit above 0b1111 set — e.g. a stale or third-party frontend that adds an AltGraph/CapsLock bit at `<< 4`, a hand-written caller computing the mask differently, or a wrapper/TS version skew where the bit layout was renumbered on one side only.
Common situations: Embedding the Graphite wasm wrapper with your own event plumbing instead of the shipped `makeKeyboardModifiersBitfield` (keyboard-entry.ts:1-12, which only sets bits 0-3); upgrading the Rust wrapper without regenerating the TS bindings; automated tests synthesizing modifier masks from `event.getModifierState()`.
Related errors
- Failed to draw arc
- Failed to measure the text dimensions
- Failed to draw the text at the calculated position
- Failed to cast element to HtmlCanvasElement
- Ungrouped folder must have a parent
AI-assisted analysis of GraphiteEditor/Graphite@c507b35645 (2026-08-16).
Data as JSON: /api/errors/1da1134c0117d12f.
Report an issue: GitHub.