diff --git a/docs/UI_DESIGN.md b/docs/UI_DESIGN.md index ef44378..de54f49 100644 --- a/docs/UI_DESIGN.md +++ b/docs/UI_DESIGN.md @@ -31,8 +31,9 @@ Login uses `Screen`. back). The live parent mounts only on commit, which suppresses that one route slide so the hand-off is seamless. The gesture yields to native **vertical scroll** on a vertical move and skips the rack, a draggable pending tile, text inputs and the board **while zoomed-in** - (so it never fights the board pan); it stays disabled inside Telegram, whose native - BackButton owns this. + (so it never fights the board pan). It works **inside Telegram** too — Telegram's only + close/minimise gesture is *vertical* (disabled separately via `disableVerticalSwipes`), so + a horizontal back-swipe has nothing to fight; the native header BackButton complements it. - **No hamburger**: navigation is entirely bottom tab bars (the former `Menu.svelte` dropdown is gone — it fought the Telegram-fullscreen layout, where it had to be re-centred). Destinations beyond the lobby live behind **hub screens** reached from a tab: a ⚙️ diff --git a/ui/e2e/backswipe.spec.ts b/ui/e2e/backswipe.spec.ts index 6e64592..489c580 100644 --- a/ui/e2e/backswipe.spec.ts +++ b/ui/e2e/backswipe.spec.ts @@ -22,6 +22,22 @@ async function openAnnGame(page: Page): Promise { await page.waitForTimeout(400); // let the open slide settle before measuring/aiming } +// A minimal Telegram WebApp stub: non-empty initData makes insideTelegram() true and drives +// the Mini App auto-auth into the lobby (the mock gateway accepts any initData). +function telegramStub() { + return { + Telegram: { + WebApp: { + initData: 'query_id=test&user=%7B%22id%22%3A1%7D&auth_date=1&hash=deadbeef', + initDataUnsafe: {}, + themeParams: {}, + ready() {}, + expand() {}, + }, + }, + }; +} + /** touchDrag dispatches a single-finger touch drag from (x0,y0) to (x1,y1) over the window. */ async function touchDrag(page: Page, x0: number, y0: number, x1: number, y1: number): Promise { await page.evaluate( @@ -88,3 +104,20 @@ test('a vertical drag in the band scrolls instead of navigating', async ({ page await page.waitForTimeout(300); expect(page.url()).toContain('/game/'); }); + +test('the back-swipe also works inside Telegram (its only swipe gesture is vertical)', async ({ page }) => { + await page.addInitScript((stub) => { + Object.assign(window, stub); + }, telegramStub()); + await page.goto('/'); + + // Telegram auto-authenticates into the lobby (no guest click), then open the seeded game. + await page.getByRole('button', { name: /Ann/ }).click(); + await expect(page.locator('[data-cell]').first()).toBeVisible(); + await page.waitForTimeout(400); + + await touchDrag(page, START_X, MID_Y, 300, MID_Y); + + await expect(page).toHaveURL(/#\/$/); + await expect(page.getByRole('button', { name: /Ann/ })).toBeVisible(); +}); diff --git a/ui/src/App.svelte b/ui/src/App.svelte index 1b5ca1d..fc5a395 100644 --- a/ui/src/App.svelte +++ b/ui/src/App.svelte @@ -141,8 +141,6 @@ position: relative; height: 100%; overflow: hidden; - /* Keep a horizontal drag from also triggering the browser's history-swipe underneath. */ - overscroll-behavior-x: contain; } .pane { position: absolute; diff --git a/ui/src/lib/backswipe.svelte.ts b/ui/src/lib/backswipe.svelte.ts index b246571..f183340 100644 --- a/ui/src/lib/backswipe.svelte.ts +++ b/ui/src/lib/backswipe.svelte.ts @@ -9,7 +9,6 @@ import { app } from './app.svelte'; import { createBackSwipe, shouldArm } from './backswipe'; import { navigate } from './router.svelte'; -import { insideTelegram } from './telegram'; /** * backSwipe is the reactive view of the in-flight gesture. App binds `progress` to the @@ -127,7 +126,6 @@ export function initBackSwipe(getParent: () => string | null): () => void { width: window.innerWidth, pointerType: e.pointerType, hasParent: !!parent, - insideTelegram: insideTelegram(), }) ) return; diff --git a/ui/src/lib/backswipe.test.ts b/ui/src/lib/backswipe.test.ts index bc370b9..c82e10f 100644 --- a/ui/src/lib/backswipe.test.ts +++ b/ui/src/lib/backswipe.test.ts @@ -7,7 +7,6 @@ describe('shouldArm', () => { width: 400, pointerType: 'touch', hasParent: true, - insideTelegram: false, }; it('arms a touch press inside the left band of a screen with a parent', () => { @@ -15,11 +14,10 @@ describe('shouldArm', () => { expect(shouldArm({ ...base, clientX: 0.38 * 400 - 1 })).toBe(true); }); - it('does not arm past the band, for a mouse, at a root, or inside Telegram', () => { + it('does not arm past the band, for a mouse, or at a root', () => { expect(shouldArm({ ...base, clientX: 0.38 * 400 + 1 })).toBe(false); expect(shouldArm({ ...base, pointerType: 'mouse' })).toBe(false); expect(shouldArm({ ...base, hasParent: false })).toBe(false); - expect(shouldArm({ ...base, insideTelegram: true })).toBe(false); }); it('honours a custom band fraction', () => { diff --git a/ui/src/lib/backswipe.ts b/ui/src/lib/backswipe.ts index 25d2bd4..da968e5 100644 --- a/ui/src/lib/backswipe.ts +++ b/ui/src/lib/backswipe.ts @@ -30,18 +30,18 @@ export interface ArmInput { pointerType: string; /** Whether the current screen has a parent to return to (false on a navigation root). */ hasParent: boolean; - /** Whether the app is running inside Telegram, where its native Back button owns this. */ - insideTelegram: boolean; /** Fraction of the width forming the left activation band; defaults to 0.38. */ bandFrac?: number; } /** * shouldArm decides whether a pointerdown may begin a back-swipe: a touch or pen press in - * the left band of a screen that has a parent, while not inside Telegram. + * the left band of a screen that has a parent. It is intentionally Telegram-agnostic — + * Telegram's only close/minimise gesture is vertical (handled by disableVerticalSwipes), so + * a horizontal back-swipe has nothing to conflict with there. */ export function shouldArm(i: ArmInput): boolean { - if (!i.hasParent || i.insideTelegram) return false; + if (!i.hasParent) return false; if (i.pointerType === 'mouse') return false; const band = (i.bandFrac ?? 0.38) * i.width; return i.clientX <= band;