From 75018277a0bfd2fd909a28a8895b7815687c1fb1 Mon Sep 17 00:00:00 2001 From: charlieforward9 Date: Thu, 21 May 2026 23:42:18 -0400 Subject: [PATCH 1/2] fix(core): use correct mjolnir requireFailure key for recognizers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The EventManager's RecognizerTupleNormalized expects `requireFailure`, but Deck was passing `requestFailure`. The key was silently dropped, so every requireFailure relationship declared in RECOGNIZERS (pinch waiting for multipan, single-finger pan waiting for multipan, click waiting for dblclick) never took effect. On mobile this caused pinch to fire on any 2-finger touch and beat the multipan recognizer to the gesture: a 2-finger vertical drag would land in pinch's `controllerState.zoom({pos, scale: ~1})` instead of `_onMultiPan → rotate({pos})`, so the camera re-anchored its longitude/latitude (looked like a pan) and picked up tiny bearing changes from inter-finger rotation deltas — and pitch never engaged. Co-Authored-By: Claude Opus 4.7 (1M context) --- modules/core/src/lib/deck.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/core/src/lib/deck.ts b/modules/core/src/lib/deck.ts index ec03d240bd9..4523a087ab3 100644 --- a/modules/core/src/lib/deck.ts +++ b/modules/core/src/lib/deck.ts @@ -1318,14 +1318,14 @@ export default class Deck { touchAction: this.props.touchAction, recognizers: Object.keys(RECOGNIZERS).map((eventName: string) => { // Resolve recognizer settings - const [RecognizerConstructor, defaultOptions, recognizeWith, requestFailure] = + const [RecognizerConstructor, defaultOptions, recognizeWith, requireFailure] = RECOGNIZERS[eventName]; const optionsOverride = this.props.eventRecognizerOptions?.[eventName]; const options = {...defaultOptions, ...optionsOverride, event: eventName}; return { recognizer: new RecognizerConstructor(options), recognizeWith, - requestFailure + requireFailure }; }), events: { From b8f30a09a056a4388d0feb4a0ece45d589c55680 Mon Sep 17 00:00:00 2001 From: charlieforward9 Date: Fri, 22 May 2026 17:28:06 -0400 Subject: [PATCH 2/2] test(core): regression test for mjolnir requireFailure wiring Construct a Deck and verify pinch, pan, and click each have the expected blocking recognizer in their requireFail array. Catches the `requestFailure` typo class of bug without depending on TypeScript flagging it through mjolnir's union type. --- test/modules/core/lib/deck.spec.ts | 34 ++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/test/modules/core/lib/deck.spec.ts b/test/modules/core/lib/deck.spec.ts index e4c0d9a7280..006baa914ac 100644 --- a/test/modules/core/lib/deck.spec.ts +++ b/test/modules/core/lib/deck.spec.ts @@ -116,6 +116,40 @@ test('Deck#constructor', async () => { console.log('Deck constructor did not throw'); }); +test('Deck wires mjolnir requireFailure between recognizers', async () => { + // Regression guard: deck.gl previously emitted `requestFailure` instead of + // `requireFailure`, which mjolnir silently dropped — so pinch/pan/click no + // longer waited for their blocking recognizer to fail. + await new Promise((resolve, reject) => { + const deck = new Deck({ + device, + width: 1, + height: 1, + viewState: {longitude: 0, latitude: 0, zoom: 0}, + layers: [], + controller: true, + onLoad: () => { + try { + const recognizers = (deck as any).eventManager?.manager?.recognizers ?? []; + const requiredFailures = (event: string): string[] => + (recognizers.find(r => r.options.event === event)?.requireFail ?? []).map( + (r: any) => r.options.event + ); + + expect(requiredFailures('pinch'), 'pinch waits for multipan').toContain('multipan'); + expect(requiredFailures('pan'), 'pan waits for multipan').toContain('multipan'); + expect(requiredFailures('click'), 'click waits for dblclick').toContain('dblclick'); + + deck.finalize(); + resolve(); + } catch (error) { + reject(error); + } + } + }); + }); +}); + test('Deck#abort', async () => { const deck = new Deck({ device,