{"record":{"id":"1da1134c0117d12f","repo":"GraphiteEditor/Graphite","slug":"invalid-modifier-keys","errorCode":null,"errorMessage":"Invalid modifier keys","messagePattern":"Invalid modifier keys","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"frontend/wrapper/src/editor_commands.rs","lineNumber":304,"sourceCode":"\t\t\tlocalized_commit_date,\n\t\t\tlocalized_commit_year,\n\t\t}\n\t\t.into()\n\t}\n\n\tfn request_licenses_third_party_dialog_with_license_text(license_text: String) -> Message {\n\t\tDialogMessage::RequestLicensesThirdPartyDialogWithLicenseText { license_text }.into()\n\t}\n\n\t/// Send new viewport info to the backend\n\tfn update_viewport(x: f64, y: f64, width: f64, height: f64, scale: f64) -> Message {\n\t\tViewportMessage::Update { x, y, width, height, scale }.into()\n\t}\n\n\t/// Mouse movement within the screenspace bounds of the viewport\n\tfn on_mouse_move(x: f64, y: f64, mouse_keys: u8, modifiers: u8) -> Message {\n\t\tlet editor_mouse_state = EditorMouseState::from_keys_and_editor_position(mouse_keys, (x, y).into());\n\t\tlet modifier_keys = ModifierKeys::from_bits(modifiers).expect(\"Invalid modifier keys\");\n\t\tInputPreprocessorMessage::PointerMove { editor_mouse_state, modifier_keys }.into()\n\t}\n\n\t/// Mouse scrolling within the screenspace bounds of the viewport\n\tfn 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 {\n\t\tlet mut editor_mouse_state = EditorMouseState::from_keys_and_editor_position(mouse_keys, (x, y).into());\n\t\teditor_mouse_state.scroll_delta = ScrollDelta::new(wheel_delta_x, wheel_delta_y, wheel_delta_z);\n\t\tlet modifier_keys = ModifierKeys::from_bits(modifiers).expect(\"Invalid modifier keys\");\n\t\tInputPreprocessorMessage::WheelScroll { editor_mouse_state, modifier_keys }.into()\n\t}\n\n\t/// A mouse button depressed within screenspace the bounds of the viewport\n\tfn on_mouse_down(x: f64, y: f64, mouse_keys: u8, modifiers: u8) -> Message {\n\t\tlet editor_mouse_state = EditorMouseState::from_keys_and_editor_position(mouse_keys, (x, y).into());\n\t\tlet modifier_keys = ModifierKeys::from_bits(modifiers).expect(\"Invalid modifier keys\");\n\t\tInputPreprocessorMessage::PointerDown { editor_mouse_state, modifier_keys }.into()\n\t}\n","sourceCodeStart":286,"sourceCodeEnd":322,"githubUrl":"https://github.com/GraphiteEditor/Graphite/blob/c507b356453361e31638b8bff8f6d46b6da2961e/frontend/wrapper/src/editor_commands.rs#L286-L322","documentation":"`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.","triggerScenarios":"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.","commonSituations":"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()`.","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."],"exampleFix":"// before\nlet modifier_keys = ModifierKeys::from_bits(modifiers).expect(\"Invalid modifier keys\");\nInputPreprocessorMessage::PointerMove { editor_mouse_state, modifier_keys }.into()\n\n// after\nlet modifier_keys = ModifierKeys::from_bits_truncate(modifiers); // drops unknown bits instead of panicking\nInputPreprocessorMessage::PointerMove { editor_mouse_state, modifier_keys }.into()","handlingStrategy":"type-guard","validationCode":"// TS: clamp the mask to the four defined bits before every wrapper call\nconst MODIFIERS_MASK = 0b1111; // SHIFT | ALT | CONTROL | META_OR_COMMAND\nconst safe = makeKeyboardModifiersBitfield(e) & MODIFIERS_MASK;\neditor.onMouseMove(e.clientX, e.clientY, e.buttons, safe);","typeGuard":"function isValidModifierMask(v: unknown): v is number {\n  return typeof v === 'number' && Number.isInteger(v) && v >= 0 && v <= 0b1111;\n}","tryCatchPattern":null,"preventionTips":["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."],"tags":["bitflags","input","mouse","wasm-bindgen","panic","pointer-move"],"backgroundTag":"invalid-bitmask-flags","analyzedSha":"c507b356453361e31638b8bff8f6d46b6da2961e","analyzedAt":"2026-08-16T21:57:18.596Z","schemaVersion":2},"datasetVersion":"2026-08-16T23:17:17.608Z"}