From 0eb72ba95517e51cc33f1bfe53954e564cc5adc9 Mon Sep 17 00:00:00 2001 From: Ilia Denisov Date: Sun, 12 Jul 2026 18:09:11 +0200 Subject: [PATCH] feat(ui): hide Telegram/VK link buttons on the native build MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The native (Capacitor) sign-in surface is guest + email only: VK ID web-login is a full-page redirect to id.vk.com that cannot return into the WebView, and the Telegram Login Widget is unreliable there. Profile now gates telegramLinkable/vkLinkable on !nativeShell (clientChannel android/ ios), hiding both LINK buttons on native; email and account management β€” including an existing link's redirect-free UNLINK β€” stay. Native tg/vk login (native SDKs / deep-link OAuth) is a separate later stage. Covered by e2e/native.spec.ts (the reconcile test opens Profile and asserts no Link Telegram / Link VK buttons, email present). --- ANDROID_PLAN.md | 44 +++++++++++++++++++++-------------- ui/e2e/native.spec.ts | 11 +++++++++ ui/src/screens/Profile.svelte | 10 ++++++-- 3 files changed, 46 insertions(+), 19 deletions(-) diff --git a/ANDROID_PLAN.md b/ANDROID_PLAN.md index a0bacd1..a179806 100644 --- a/ANDROID_PLAN.md +++ b/ANDROID_PLAN.md @@ -91,7 +91,10 @@ Kept current as parts land so a fresh session resumes without re-deriving. Verif `clientver`/config/`connectsrv` tests green, `svelte-check` 0, `vitest` 591, e2e 232 (incl. `update.spec.ts`), client build clean. Silent reconciliation seam deferred to D (owner); in-app store-update SDK noted Out of scope. -- **D. Offline-first β€” 🚧 IN PROGRESS (D.1–D.4 done; D.6 + the D-close verification remain).** +- **D. Offline-first β€” βœ… CODE-COMPLETE & e2e-verified (D.1–D.6 done, 2026-07-12).** Remaining: the on-device + emulator **airplane-mode smoke** (owner / stage G) and the **deferred local-game-visibility decision** (below). + D.5 (both offline modes render + play for the local guest) is verified by `e2e/native.spec.ts` (vs_ai move + + hotseat start). - **D.1 + D.2 (foundations) β€” βœ… DONE & committed `bcd5a1d` (2026-07-12).** `ui/scripts/bundle-dicts.mjs` (release DAWGs β†’ `dist/dict/@.dawg`, keyed on `VITE_DICT_VERSION`, `OUT_DIR` override for the e2e); the `dict/loader.ts` **bundled tier** (between IndexedDB and network, **native-gated** β€” @@ -110,7 +113,11 @@ Kept current as parts land so a fresh session resumes without re-deriving. Verif the dawgs into `dist-e2e/dict/`. `svelte-check` 0, `vitest` 591, web + native builds clean; e2e runs in CI. See Β§D.3/Β§D.4 for the as-built corrections (shouldBootOffline **not** changed; `allowOffline` + checkReachable-token findings; the `__native.reconcile` e2e hook). - - **D.6 (Profile tg/vk hide on native) β€” β–’ TODO** (owner deferred to a follow-up this session). + - **D.6 (Profile tg/vk hide on native) β€” βœ… DONE & verified (2026-07-12).** `Profile.svelte` gates + `telegramLinkable`/`vkLinkable` on `!nativeShell` (`clientChannel()` android/ios), so the Telegram + VK + LINK buttons are hidden on the native build; email + account management (incl. any existing link's UNLINK, + a redirect-free gateway call) stay. Covered by the `e2e/native.spec.ts` reconcile test (Profile β†’ no Link + Telegram / Link VK buttons, email present). - **DEFERRED DECISION (owner-agreed 2026-07-12) β€” native local-game visibility.** The online lobby lists only server games (`Lobby.svelte:42/54`), so a native guest's **device-local vs_ai/hotseat games hide from the lobby once reconciled online** (the pre-existing offline↔online split). For native this IS the primary @@ -420,16 +427,18 @@ purchase actions are absent; `pnpm check` + `pnpm test:unit` pass. **Done when:** Go `clientver` + gate tests pass; `pnpm check`/`test:unit`/`test:e2e` pass; a local gateway with `GATEWAY_MIN_CLIENT_VERSION=v99.0.0` shows the overlay, unset β‡’ unchanged. -### D. Offline-first (bundled dicts + local guest + cold boot + reconciliation) β€” 🚧 IN PROGRESS +### D. Offline-first (bundled dicts + local guest + cold boot + reconciliation) β€” βœ… CODE-COMPLETE Both offline modes already exist and are gated on `offlineMode.active`: local vs_ai and 2-4-player hotseat (pass-and-play with a host PIN referee) β€” see `ui/src/lib/localgame/source.hotseat.test.ts` and `ui/src/screens/NewGame.svelte:177-431`. This phase makes them reachable on a **cold first launch with no network**. -**Status:** the additive foundations (D.1, D.2) are **done & committed (`bcd5a1d`)** and inert on the -web; the boot rewrite (D.3), reconciliation + the silent seam (D.4), the Profile soft-sign-in (D.6) and -the tests remain. **Verify every line ref below against current code** (they are as of 2026-07-12). +**Status:** D.1–D.6 are **done & verified** (foundations committed `bcd5a1d`; boot + reconciliation + +Profile committed on `feature/android-native`, 2026-07-12), with `e2e/native.spec.ts` green locally +(chromium + webkit) and in CI. Remaining for D: the on-device emulator airplane-mode smoke (owner / +stage G) and the deferred local-game-visibility decision (Progress Β§D / Β§G). **Verify every line ref +below against current code.** **Decisions locked this session (owner-approved) β€” bake these before implementing:** - **The blocking Login is bypassed on native only.** Web / PWA / Telegram / VK keep the current @@ -514,17 +523,18 @@ the tests remain. **Verify every line ref below against current code** (they are offline flows on `guest || offlineMode.active`; the local guest makes `offlineMode.active` true, so both "quick" (vs_ai) and "with friends" (hotseat) show. The vs_ai human seat already uses the local-guest id (D.2). -6. **Soft registration = reuse the `Profile` screen β€” β–’ TODO.** - - Profile is already reachable for an **online** guest (guests keep the Profile tab β€” - `SettingsHub.svelte:27` only hides friends/wallet for guests; `:33` hides profile/friends/wallet in - **offline** mode). So once the native guest is online and reconciled (D.4 clears the auto-offline), - Profile shows and email sign-in works. Offline you cannot register anyway (every method needs the - server), so the offline-hidden behaviour is correct β€” **no change is needed to reach Profile**, provided - D.4 clears the auto-offline when online. - - **Hide tg/vk on native:** `Profile.svelte` β€” the Telegram link button (`~line 485`, gated on - `telegramLinkable = loginWidgetAvailable()`, `:62`) and the VK link button (`~line 499`). Add a - `clientChannel()` android/ios check to hide both on the native build; keep email + account management. - (See the decision block above for why.) +6. **Soft registration = reuse the `Profile` screen β€” βœ… DONE & verified (2026-07-12).** As built: + - **Reaching Profile needed no change:** guests keep the Profile tab (`SettingsHub.svelte:27` only hides + friends/wallet for guests; `:33` hides profile/friends/wallet in **offline** mode), so once the native + guest is online and reconciled (D.4 clears the auto-offline) Profile shows and email sign-in works. + Offline you cannot register anyway (every method needs the server), so the offline-hidden behaviour is + correct. + - **Hid tg/vk on native:** `Profile.svelte` gates `telegramLinkable = loginWidgetAvailable() && !nativeShell` + and `vkLinkable = vkWebLinkAvailable() && !nativeShell` (`nativeShell = clientChannel()` android/ios), so + both LINK buttons are hidden on native while email + account management (and an existing link's UNLINK β€” a + redirect-free `link.unlink` call) stay. Verified in `e2e/native.spec.ts` (the reconcile test opens Profile + and asserts no Link Telegram / Link VK, email present; `loginWidgetAvailable`/`vkWebLinkAvailable` are true + under the mock, so the buttons show on web β€” the native count-0 proves the gate). **Tests β€” βœ… DONE for D.3/D.4 (2026-07-12):** - **Unit (vitest, node):** no NEW unit test was added for D.3/D.4 β€” the boot decision, reconciliation and diff --git a/ui/e2e/native.spec.ts b/ui/e2e/native.spec.ts index c532c9e..06ffd00 100644 --- a/ui/e2e/native.spec.ts +++ b/ui/e2e/native.spec.ts @@ -73,6 +73,17 @@ test.describe('native offline-first', () => { await expect(page.locator('header.nav.offline')).toHaveCount(0); await expect(page.getByText('Ann', { exact: false }).first()).toBeVisible({ timeout: 15000 }); await expect(page.locator('button.tab').nth(1)).toBeEnabled(); + + // The native sign-in surface is guest + email only: on Profile the Telegram + VK LINK buttons are + // hidden (their login widget / full-page redirect cannot return into the WebView), while email account + // management stays. loginWidgetAvailable/vkWebLinkAvailable are true under the mock, so on web these + // buttons DO show (social.spec links Telegram there) β€” a count of 0 here proves the native gate, not a + // merely-absent button. + await page.getByRole('button', { name: /Settings/ }).click(); + await page.getByRole('button', { name: 'Profile', exact: true }).click(); + await expect(page.getByRole('button', { name: /Link Telegram/i })).toHaveCount(0); + await expect(page.getByRole('button', { name: /Link VK/i })).toHaveCount(0); + await expect(page.getByText('you@example.com')).toBeVisible(); }); test('starts a 2-player hotseat game as an offline guest', async ({ page }) => { diff --git a/ui/src/screens/Profile.svelte b/ui/src/screens/Profile.svelte index f4a9bd0..bb7644e 100644 --- a/ui/src/screens/Profile.svelte +++ b/ui/src/screens/Profile.svelte @@ -16,6 +16,7 @@ import { gateway } from '../lib/gateway'; import { insideTelegram, loginWidgetAvailable, requestTelegramLogin } from '../lib/telegram'; import { insideVK } from '../lib/vk'; + import { clientChannel } from '../lib/channel'; import { kickDictPreload } from '../lib/offline.svelte'; import { startVKLink, vkWebLinkAvailable } from '../lib/vkid'; import { t } from '../lib/i18n/index.svelte'; @@ -59,8 +60,13 @@ let deleting = $state(null); let deleteCode = $state(''); let deletePhrase = $state(''); - const telegramLinkable = loginWidgetAvailable(); - const vkLinkable = vkWebLinkAvailable(); + // The native build hides the Telegram + VK LINK buttons: VK ID web-login is a full-page redirect to + // id.vk.com that cannot return into the Capacitor WebView, and the Telegram Login Widget is unreliable + // there β€” so guest + email is the native sign-in surface (native tg/vk login is a later stage). An + // existing link's UNLINK button stays (a pure gateway call, no redirect), as does all account management. + const nativeShell = clientChannel() === 'android' || clientChannel() === 'ios'; + const telegramLinkable = loginWidgetAvailable() && !nativeShell; + const vkLinkable = vkWebLinkAvailable() && !nativeShell; // Inside a host Mini App the current platform's own identity must not be managed from the // profile β€” unlinking the very platform you are signed in through is meaningless β€” so that // provider's linked-account row is hidden here (the web / native builds still show both).