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

  1. Compute the mask exactly like the shipped frontend: `(shiftKey<<0) | (altKey<<1) | (ctrlKey<<2) | (metaKey<<3)` and never add extra bits.
  2. If you control the wrapper source, use `ModifierKeys::from_bits_truncate(modifiers)` so unknown bits are dropped instead of panicking.
  3. Mask the value before crossing the boundary: `modifiers & 0b1111`.
  4. 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

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


AI-assisted analysis of GraphiteEditor/Graphite@c507b35645 (2026-08-16). Data as JSON: /api/errors/1da1134c0117d12f. Report an issue: GitHub.