From dd905888cbf016a76457b1c17e2bb8d57143645f Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 27 Sep 2026 17:54:21 +0300 Subject: [PATCH] =?UTF-8?q?test(mutation):=20=D1=81=D0=B2=D0=B8=D0=B4?= =?UTF-8?q?=D0=B5=D1=82=D0=B5=D0=BB=D0=B8=20=D0=B6=D0=B5=D1=81=D1=82=D0=BE?= =?UTF-8?q?=D0=B2=20=D0=B8=20=D0=BF=D0=BE=D0=B4=D1=81=D0=BA=D0=B0=D0=B7?= =?UTF-8?q?=D0=BA=D0=B8=20=D0=BB=D0=B5=D1=81=D1=82=D0=BD=D0=B8=D1=86=20?= =?UTF-8?q?=E2=80=94=20=D0=B1=D0=B5=D0=B7=20=D0=B1=D1=80=D0=B0=D1=83=D0=B7?= =?UTF-8?q?=D0=B5=D1=80=D0=B0=20(#676)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #659 закрыл реестр браузерных гвардов ровно на 200 и проверяет число точно: два новых смок-свидетеля #676 подняли его до 202, и юниты на ребейзнутом кандидате покраснели («browser guard inventory is reviewed, capped and exact»). Оба инварианта — проводка Lit-шаблонов, которую без браузера не импортировать, но можно засвидетельствовать по исходнику, как #659 сделал для прежних трёх лестничных мутантов: подавление синтезированного click после жеста (`_suppressClick` на один тик в конце протяжки и жеста узла, `@click=${stop}` на обоих видах узлов) и условие подсказки Просмотра (`if (!active) return;` — тот же признак, что делает лестницу ссылкой). Мутанты переведены в группу `unitGuard('test/stairs-box.test.mjs')`, свидетель — новый тест в `mutation-browser-offload.test.mjs`; поведение в Chromium по-прежнему держит `demo/smoke_stairs.mjs` (AC6, AC8). Issue: #676 User-Visible: no Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd --- scripts/mutation-registry.mjs | 6 ++++++ test/mutation-browser-offload.test.mjs | 24 ++++++++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/scripts/mutation-registry.mjs b/scripts/mutation-registry.mjs index c20fada0..e4b0c29c 100644 --- a/scripts/mutation-registry.mjs +++ b/scripts/mutation-registry.mjs @@ -13149,6 +13149,12 @@ const UNIT_GUARD_GROUPS = [ 'stairs-link-auto-creates-target-object', 'stairs-broken-targets-become-active', ]], + // #676: gesture-click suppression and the tooltip's `active` gate are template + // wiring; the box model beside them is pure and tested in stairs-box.test.mjs. + [unitGuard('test/stairs-box.test.mjs'), [ + 'stairs-gesture-click-reaches-plan-tool', + 'stairs-view-tooltip-ignores-target-state', + ]], [unitGuard('test/device-hit-owner.test.mjs'), [ 'device-hit-scroll-observer-disabled', ]], diff --git a/test/mutation-browser-offload.test.mjs b/test/mutation-browser-offload.test.mjs index 50221128..d8381090 100644 --- a/test/mutation-browser-offload.test.mjs +++ b/test/mutation-browser-offload.test.mjs @@ -150,3 +150,27 @@ test('#659 room gear drag keeps the click suppression window after a real move', contains('src/room-gear-drag.ts', 'this.suppressClickUntil = performance.now() + 700;'); }); + +test('#676 stair gestures swallow the synthesized click and the View tooltip keeps the link condition', () => { + // Gesture → click suppression: the drag end (draft or handle) arms the host's + // `_suppressClick` for one tick, and every frame handle stops its own click. + // `demo/smoke_stairs.mjs` proves the behaviour in Chromium (AC6/AC8); this is + // the Node witness for the wiring that Lit templates cannot expose. + const editor = contains('src/stairs-editor.ts', + ' this.owner._suppressClick = true;\n' + + ' setTimeout(() => { this.owner._suppressClick = false; }, 0);\n', + ' this.draft = null;\n this.swallowNextClick();\n', + ' if (!drag || drag.pid !== event.pointerId) return false;\n this.swallowNextClick();\n', + "@pointerdown=${(event: PointerEvent) => this.pointerDown(event, stair, 'resize', { sx: handle.sx, sy: handle.sy })}\n" + + ' @click=${stop}>', + "@pointerdown=${(event: PointerEvent) => this.pointerDown(event, stair, 'rotate')}\n" + + ' @click=${stop}>', + ); + assert.equal((editor.match(/@click=\$\{stop\}/g) || []).length, 2, 'both handle kinds stop their click'); + // Tooltip only where the stair is a link: the same `active` that gates navigation. + const view = contains('src/stairs-view.ts', + ' const tip = (event: PointerEvent): void => {\n if (!active) return;\n', + "this.owner._showTip(event, this.owner._t('stairs.tooltip_navigate', { title: targetTitle }), '');", + ); + assert.doesNotMatch(view, /const tip = \(event: PointerEvent\): void => \{\n\s*this\.owner\._showTip/); +});