Commit Graph

31 Commits

Author SHA1 Message Date
Ilia Denisov 564abdcf88 chore: fix broken telegram links
CI / changes (pull_request) Successful in 3s
CI / unit (pull_request) Successful in 11s
CI / integration (pull_request) Successful in 21s
CI / ui (pull_request) Successful in 1m14s
CI / conformance (pull_request) Successful in 10s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 2m7s
2026-07-14 09:06:03 +02:00
Ilia Denisov cc34622630 feat(vk): show a launch diagnostic on a direct /vk/ open instead of a guest
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Has been skipped
CI / integration (pull_request) Has been skipped
CI / ui (pull_request) Successful in 1m13s
CI / conformance (pull_request) Successful in 10s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 2m7s
Opening the dedicated /vk/ entry directly in an ordinary browser (no signed VK
launch) fell through to the web flow and silently started a throwaway guest,
which is wrong for a VK-only entry. Mirror the existing /telegram/ launch-error
behaviour: render a compact, shareable, privacy-safe diagnostic screen instead.

- Boot: a new branch `onVKPath() && !insideVK()` sets app.launchError and stops
  the fall-through, the VK counterpart of the /telegram/ diagnostic guard.
- Diagnostic (passive, no VK Bridge round-trip): reads the URL launch-parameter
  NAMES (never the `sign` value — auth material), whether the URL was signed,
  `vk_platform`, the iframe/referrer context, plus the shared client-environment
  lines. VK signs the launch URL at load, so — unlike Telegram's initData — the
  parameters never arrive late; hence the VK screen offers Share only, no Retry.
- Generalise the screen: TelegramLaunchError.svelte -> LaunchError.svelte, which
  renders a neutral pre-formatted report and a per-platform title, with Retry
  gated on a `retry` flag (Telegram true, VK false). app.launchError is now a
  neutral LaunchDiag { platform, report, retry }.
- New lib/launchdiag.ts holds the shared pieces both platforms reuse: the
  LaunchDiag shape, the client-environment lines, and the query field-NAME
  reader (moved out of telegram.ts so VK does not duplicate them).

Tests: pure vkDiagLines units, incl. a guard that the `sign` value never leaks
into the report. i18n: launch.errorTitleVk (en/ru). Docs: FUNCTIONAL (+ru) user
story and ARCHITECTURE entry-path note.
2026-07-13 23:25:26 +02:00
Ilia Denisov 6e03ce0131 feat(payments): Telegram Stars payment rail
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Successful in 22s
CI / integration (pull_request) Successful in 20s
CI / ui (pull_request) Successful in 1m10s
CI / conformance (pull_request) Successful in 10s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m57s
Accept real money via Telegram Stars (XTR) — the third intake rail
alongside Robokassa (direct) and VK Votes.

Only the bot reaches Telegram, so the rail funnels through the reverse
mTLS bot-link:
- the gateway mints the invoice on a CreateInvoice command (the bot
  calls createInvoiceLink, XTR; the link goes to WebApp.openInvoice);
- the bot gates each pre_checkout_query via a ValidatePreCheckout unary
  (the order must exist, be still creditable and not already paid — the
  reusable-invoice double-pay guard; the decline reason is localised to
  the order account's language);
- a completed successful_payment is queued in a durable pure-Go SQLite
  outbox and forwarded via a ForwardPayment unary, credited once
  (idempotent on telegram_payment_charge_id, honours an expired order),
  re-driven on restart and every 30s.

The rail is wired by TELEGRAM_STARS_OUTBOX_DIR (default /data) but stays
inert until a chip pack carries an XTR price, so seeding a Stars price in
the admin is the go-live.

Tests: backend integration (order->forward->credit once, duplicate,
pre_checkout gate) + bot outbox unit (idempotent, restart re-drive) +
executor createInvoice. Docs: PAYMENTS(+ru) §9, ARCHITECTURE, the
platform/telegram README, PLAN.
2026-07-09 21:35:29 +02:00
Ilia Denisov cabcd94d92 feat(profile): account-deletion flow + terminal deleted screen
Profile gains a Delete-account control (durable accounts) opening a step-up dialog: a
mailed code for an email account, or the typed DELETE phrase for a platform-only one.
On success the app swaps to a terminal AccountDeleted screen ('Учётная запись удалена')
with a Close that closes the host Mini App (telegramClose / vkClose; web = no close).
Wires deleteRequest/deleteConfirm through client/transport/mock/codec; ru/en i18n;
codec wire test + Chromium/WebKit e2e.
2026-07-03 13:18:37 +02:00
developer d5fbaa3034 feat(export): server-rendered artifacts behind one signed download URL (#160)
CI / changes (push) Successful in 1s
CI / unit (push) Successful in 9s
CI / integration (push) Successful in 15s
CI / ui (push) Successful in 1m2s
CI / conformance (push) Successful in 9s
CI / gate (push) Successful in 0s
CI / deploy (push) Successful in 1m42s
The finished-game export (GCG + a new PNG of the final position) is one
signed, short-lived relative URL (game.export_url; HMAC-SHA256, 10-min
TTL, BACKEND_EXPORT_SIGN_KEY) resolved against the client's own origin
and delivered by the best affordance each platform has (five on-device
review rounds):

- TG Android/desktop: native showPopup chooser -> native downloadFile
  dialog (bridge-only chain, activation-safe).
- TG iOS: app-modal chooser -> OS share sheet with the fetched file
  (a popup callback cannot supply the activation the sheet needs).
- VK iOS: VKWebAppDownloadFile for both formats.
- VK Android: the PNG opens in VK's native image viewer, the GCG copies
  to the clipboard (the VK Android downloader hangs on any download,
  Content-Length/Range notwithstanding).
- VK desktop iframe / desktop browsers: plain anchor downloads.
- Mobile browsers: the OS share sheet (fetch-then-share).
- Legacy TG (< Bot API 8.0): app modal + GCG clipboard, no image option.

The PNG is rasterized on demand by the new internal `renderer` sidecar
(node:22-slim + skia-canvas + baked Liberation/Noto Color Emoji fonts)
executing the SAME ui/src/lib/gameimage.ts the ui project unit-tests;
the backend rebuilds the render payload from the journal +
engine.AlphabetTable, and the device date locale, IANA time zone and
localized non-play labels ride the signed URL. Nothing is stored — the
artifact re-derives from the immutable journal on each GET. The gateway
forwards /dl/* (caddy @gateway matcher extended) behind the per-IP
public rate limiter and serves bytes via http.ServeContent.

Deploy: renderer service in compose + prod overlay + rolling order +
prod push list; TEST_/PROD_EXPORT_SIGN_KEY secrets; the sidecar smoke
runs in the ui CI job. Docs: ARCHITECTURE, FUNCTIONAL(+_ru), UI_DESIGN,
TESTING, deploy/README, renderer/README.
2026-07-02 21:58:07 +00:00
Ilia Denisov db17287113 fix(ui): route external links out of the Android VK WebView
The Android VK client's WebView ignores target=_blank and navigates the
Mini App's own window to the target, stranding the player outside the
game with no way back (the dictionary lookup, About/Feedback links, the
ad banner and the bot-link modal fallbacks). vk-bridge 3.x has no method
to open an external URL, so external links are routed through VK's own
leave-VK redirect (vk.com/away.php), which the client intercepts
natively and hands to the system browser.

onExternalLinkClick moves from lib/telegram to a new lib/links that
composes the Telegram and VK routers; iOS and desktop VK open _blank
correctly and are left alone.
2026-07-02 00:09:52 +02:00
Ilia Denisov 2ba7cc3086 feat(telegram): native dialogs for confirms and deep-link notices
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Successful in 10s
CI / integration (pull_request) Successful in 15s
CI / ui (pull_request) Successful in 56s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m19s
Inside the Mini App, route the destructive confirmations (resign, block, unfriend)
through Telegram's native showConfirm, and present the deep-link info modals (stale
invite, welcome-on-redeem) as a native showPopup whose button opens the bot chat.
Outside Telegram, or on a client predating the dialogs, the existing in-app Modal is
used unchanged; offline also keeps the modal so the action retains its disabled state.

Adds showConfirm/showPopup wrappers to telegram.ts and a pure popup-params builder
(nativedialogs.ts) with unit tests; the deep-link modal components choose native vs
in-app via an effect gated on insideTelegram + dialog availability.
2026-06-24 12:36:52 +02:00
Ilia Denisov 8a5a5d6c4d feat(telegram): sync client display prefs via CloudStorage
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Has been skipped
CI / integration (pull_request) Has been skipped
CI / ui (pull_request) Successful in 56s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m20s
Theme, reduce-motion and board labels lived only in local IndexedDB/localStorage,
so they did not follow the user across devices and could be lost when the Telegram
WebView cleared storage.

Mirror these three device-independent prefs to Telegram CloudStorage (Bot API 6.9)
from the single local-persist point (persistPrefs), and reconcile them from
CloudStorage in the background on launch (reconcileCloudPrefs) so a change made on
another device follows the user here. The local store stays the instant-render
cache; the interface language is intentionally excluded (it syncs via the durable
account). Pure encode/decode extracted to cloudprefs.ts with unit tests; the
CloudStorage transport wrappers are added to telegram.ts. No-op outside Telegram or
on a client predating CloudStorage.
2026-06-24 11:39:29 +02:00
Ilia Denisov ea931c6680 feat(telegram): native SettingsButton opens our Settings screen
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Has been skipped
CI / integration (pull_request) Has been skipped
CI / ui (pull_request) Successful in 55s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m16s
Inside the Mini App, reveal Telegram's native Settings button (Bot API 7.0)
and route its taps to the Settings screen — the standard Mini App affordance.
The in-app gear entry stays the primary path (two entry points by design).
No-op outside Telegram or on a client predating the button.
2026-06-24 10:47:05 +02:00
Ilia Denisov b84bd1297e feat(telegram): clear bottom and side safe-area insets
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Has been skipped
CI / integration (pull_request) Has been skipped
CI / ui (pull_request) Successful in 55s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m17s
Only the device safe-area TOP inset was mirrored, so on phones with a home
indicator the rack / bottom bar sat under it, and in landscape the notch
clipped the screen edges.

Mirror the full device safe-area inset (bottom / left / right) into new
--tg-safe-bottom / --tg-safe-left / --tg-safe-right CSS vars (0 outside
Telegram) and pad the shared Screen wrapper by them, so every screen clears the
home indicator and the landscape notch; the top inset stays owned by the header.
Replace telegramSafeAreaTop with telegramSafeAreaInset (the full inset object),
with a unit test.
2026-06-24 10:24:56 +02:00
Ilia Denisov 0fb6004a8b feat(telegram): re-apply theme live on themeChanged
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Has been skipped
CI / integration (pull_request) Has been skipped
CI / ui (pull_request) Successful in 56s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m21s
The Mini App read Telegram's themeParams/colorScheme only once at launch, so
switching Telegram's light/dark theme (or its auto day/night) while the app was
open left the SPA on stale colours until a relaunch.

Subscribe to the themeChanged WebApp event and re-apply the theme live. Extract
the launch-time token + colour-scheme + chrome application into syncTelegramTheme
(reading live themeParams when no launch snapshot is passed) and call it from both
applyTelegramChrome (launch) and the new event handler. Add a telegramThemeParams
live getter (+ unit test). Drop the stale 'immersive fullscreen' note from
applyTelegramChrome — the app deliberately does not request fullscreen.
2026-06-24 09:20:41 +02:00
Ilia Denisov 37070c3cb7 feat(ui): drop Telegram fullscreen; own back chevron everywhere; hidden debug panel
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Has been skipped
CI / integration (pull_request) Has been skipped
CI / ui (pull_request) Successful in 56s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 2m13s
Finalises the Telegram Mini App navigation work after on-device testing (Pixel
10 / Android 17 + iOS, fresh beta clients):

- Remove requestFullscreen entirely. Immersive fullscreen hid Telegram's native
  header (and its BackButton) and the Android system swipe-back minimised the
  app; the owner prefers the windowed full-size (expand) presentation, so the
  app never requests fullscreen on any platform now.
- The app's own back chevron (Header, showBack = !!back) drives back-navigation
  on every platform; the native Telegram BackButton is dropped — it does not
  render in the windowed Mini App (backVisible=false on iOS and Android), so
  relying on it lost back navigation (notably none on iOS).
- Replace the temporary always-on diagnostic overlay with a hidden debug panel
  (components/DebugPanel): ten quick taps on the header title open it; it shows a
  privacy-safe client diagnostic snapshot (app version, locale, online, userId,
  Telegram chrome / viewport / SDK state — no secrets, no IP) and shares it via
  the OS share sheet / clipboard; a tap anywhere except Share dismisses it.
- Drop the now-dead telegramRequestFullscreen / telegramBackButton /
  isTelegramAndroid helpers and the iOS-fullscreen unit test.

Telegram has no native non-modal notification API (only modal showPopup /
showAlert), so in-app toasts stay ours. Docs: UI_DESIGN.md.
2026-06-23 15:05:13 +02:00
Ilia Denisov 6f00c2f41d chore(ui): TEMP disable fullscreen for testing + Android back chevron + BackButton diag
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Has been skipped
CI / integration (pull_request) Has been skipped
CI / ui (pull_request) Failing after 8s
CI / gate (pull_request) Failing after 0s
CI / deploy (pull_request) Has been skipped
WIP for the Android nav investigation (TEMP bits reverted before merge):
- telegramRequestFullscreen: temporarily a no-op (incl. iOS) so the owner can
  confirm the Android "fullscreen look" is Telegram's own Mini App presentation,
  not our requestFullscreen (isFullscreen is already false on Android).
- Header: show the app's own back chevron in Telegram on Android, where the
  native BackButton does not render — a reliable tap-back. [keep]
- Diagnostic overlay moved app-wide (pointer-events:none) and now reports
  BackButton state (req/present/visible) + viewport geometry, to see whether the
  native BackButton can capture the Android system swipe-back.
2026-06-23 14:04:21 +02:00
Ilia Denisov 79766438a2 chore(ui): TEMP lobby diagnostic for the Android fullscreen issue
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Has been skipped
CI / integration (pull_request) Has been skipped
CI / ui (pull_request) Successful in 56s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m19s
Renders Telegram viewport/fullscreen state (isFullscreen, isExpanded, viewport
heights, innerH vs screenH, safe-area insets) in the lobby, inside Telegram
only, to diagnose why the app still opens fullscreen on Android with
requestFullscreen now iOS-only and no persisted state (fresh TG + test account).
REVERT before merge.
2026-06-23 13:36:54 +02:00
Ilia Denisov 6aa5023b24 fix(ui): Android Telegram nav — windowed mode, no close guard
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Has been skipped
CI / integration (pull_request) Has been skipped
CI / ui (pull_request) Successful in 56s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m18s
Three Android Mini App issues, all in the Telegram chrome:

- Entering a game showed no native back button, and the Android system
  swipe-back minimised the app instead of navigating. Root cause: immersive
  fullscreen (requestFullscreen). On Android, fullscreen replaces the native
  header — and its BackButton, which also captures the system back — with a bare
  close/menu pill, so back navigation has no control to land on. Request
  fullscreen on iOS only; Android stays windowed, keeping the native header +
  BackButton (and the system swipe-back that routes to it). iOS is unchanged.

- Closing the game board always prompted "changes that you made may not be
  saved", even on a board just opened and untouched. The close-confirmation was
  armed unconditionally on game mount. Remove it entirely: move drafts auto-save
  (debounced during play + flushed on destroy), so nothing is lost on close.

Drops the now-unused telegramClosingConfirmation + isMobilePlatform helpers and
their SDK interface fields. Docs: UI_DESIGN.md.
2026-06-23 11:48:38 +02:00
Ilia Denisov e3899d4755 feat(ui): record the SDK load outcome in the launch diagnostic
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Has been skipped
CI / integration (pull_request) Has been skipped
CI / ui (pull_request) Successful in 55s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m16s
The diagnostic showed only sdk: yes/no (window.Telegram presence), not why the
SDK was absent. Capture how the dynamic telegram-web-app.js load resolved —
present / loaded / no-webapp / error / timeout — and surface it as
"sdk-load: <outcome>". error/timeout pinpoint a blocked or hanging telegram.org
(the prime suspect for an empty launch); no-webapp a loaded-but-broken script.

loadTelegramSDK records the outcome (telegramSdkOutcome); collectTelegramDiag
carries it into the screen. Unit tests cover each outcome; the blocked-script
e2e now asserts sdk-load: error.
2026-06-23 10:18:02 +02:00
Ilia Denisov ae5090b851 fix(ui): load telegram-web-app.js dynamically with a timeout
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Has been skipped
CI / integration (pull_request) Has been skipped
CI / ui (pull_request) Successful in 55s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m18s
The Telegram SDK was a render-blocking <script src="telegram.org/..."> in the
shared index.html shell, so it ran on every entry (/telegram/, /app/, native).
On a network that blocks telegram.org — common where Telegram itself reaches
users only over a proxy — the script hangs forever, stranding the whole page,
including the launch-diagnostic screen meant to surface exactly this failure.
This is the likely root cause of the Android "won't open" reports (all launch
methods fail identically; iOS on a different network works).

Remove the head <script> and load the SDK dynamically (lib/telegram.ts
loadTelegramSDK) with a 10s timeout, only on a Telegram entry (the /telegram/
path or a tgWebApp launch fragment). The SPA — served from our own reachable
origin — boots first and controls the load: on a block/error it falls through
to the diagnostic screen (reporting sdk: no) instead of hanging, and Retry
re-attempts. /app/ and the native build no longer touch telegram.org.

Pin the SDK to the version the official page recommends (?62) for the newer
client features the app already uses (fullscreen, safe areas, swipe guard).

Tests: loadTelegramSDK unit tests (present / error / timeout); an e2e that
aborts the script fetch and asserts the diagnostic still renders.
2026-06-23 09:59:24 +02:00
Ilia Denisov 0c5d3808d7 feat(ui): diagnostic screen on /telegram/ instead of the landing bounce
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Has been skipped
CI / integration (pull_request) Has been skipped
CI / ui (pull_request) Successful in 55s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m22s
A Mini App launch on /telegram/ without sign-in data (empty initData) used to
location.replace('/') — bouncing the visitor to the marketing landing. That
destroyed all diagnosability and, on some Android clients that reach the entry
with no initData, simply looked like the app refusing to open.

Replace the bounce with a compact, privacy-safe launch-error screen
(screens/TelegramLaunchError.svelte) that renders a one-screenshot diagnostic
snapshot captured at the moment of failure: SDK/WebApp presence, Telegram
platform/version, whether initData is empty, whether the URL fragment carried
tgWebAppData, the initData field NAMES present/missing (never the signed
values, never an IP), and OS/mobile/browser via User-Agent Client Hints plus
the full User-Agent. A Share button delivers it through the OS share sheet
(clipboard copy on desktop, reusing the GCG no-webview-strand guard); Retry
re-checks in place to recover a late initData without a reload (which would
discard the launch fragment).

This is the instrument to root-cause the Android empty-initData failure, which
is not reproducible in Playwright.

Tests: collectTelegramDiag + shareText/pickTextShare unit tests; the /telegram/
e2e now asserts the diagnostic screen, not a redirect. Docs: ARCHITECTURE.md +
UI_DESIGN.md updated.
2026-06-23 09:37:36 +02:00
Ilia Denisov bd0482c376 feat(ui): route all in-app external links through Telegram openLink
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Has been skipped
CI / integration (pull_request) Has been skipped
CI / ui (pull_request) Successful in 49s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 59s
Extend the openLink routing from the dictionary lookup to every external
link shown inside the Mini App, so none triggers the WebView's 'open this
link?' confirmation. A shared onExternalLinkClick handler resolves the anchor
via closest() (so it also works delegated on {@html} content), backed by a
pure routeExternalLinkInTelegram decision: only inside Telegram, only an
external http(s) target=_blank link, excluding same-origin/in-app and t.me
links (t.me keeps openTelegramLink). Applied to the word-check lookup, the
About rules link, the Feedback operator-reply links, and the feature-gated
announcement banner.

Outside Telegram every anchor keeps its native target=_blank.
2026-06-15 18:29:53 +02:00
Ilia Denisov 8e0d7f9e17 feat(ui): external dictionary lookup link on the word-check tool
CI / changes (pull_request) Successful in 3s
CI / unit (pull_request) Has been skipped
CI / integration (pull_request) Has been skipped
CI / ui (pull_request) Successful in 48s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 56s
When a checked word is found, show a 'look it up' text link beside the
complaint button that opens an external reference dictionary in a new tab:
gramota.ru for the Russian variants, scrabblewordfinder.org for English
(word lower-cased and percent-encoded). The link hides for a word that is
not found. Inside Telegram it routes through the Mini App SDK's openLink, so
Telegram opens it directly instead of the WebView's 'open this link?'
confirmation; in a browser the anchor's own target=_blank handles it.

Relabel the complaint button to 'Возражаю' (ru); English stays 'Disagree'.
2026-06-15 18:14:19 +02:00
Ilia Denisov 00129414e5 feat(ui): force Mini App fullscreen on mobile, not via the share link
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Successful in 9s
CI / integration (pull_request) Successful in 13s
CI / ui (pull_request) Successful in 47s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m15s
Replace the shared-link &mode=fullscreen (which would also force fullscreen on
desktop) with an imperative requestFullscreen() on launch, gated to mobile
clients (ios/android/android_x) — mirroring how Telegram's own Mini Apps go
immersive on phones while desktop keeps the bot's full-size window. It triggers
the existing fullscreenChanged -> safe-area resync; a no-op on clients predating
Bot API 8.0.
2026-06-15 17:34:07 +02:00
Ilia Denisov 853730823b feat(ui): refine Telegram invite & close UX
CI / changes (pull_request) Successful in 1s
CI / unit (pull_request) Successful in 9s
CI / integration (pull_request) Successful in 13s
CI / ui (pull_request) Successful in 47s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m2s
- Shared invite links open the Mini App fullscreen (mode=fullscreen), so a
  shared link matches the bot's own fullscreen launch.
- A used or expired invite deep-link now lands the visitor in the lobby with a
  gentle notice pointing at the right bot (@<username>, by service language),
  instead of a red "code invalid/expired" error on the Friends screen.
- The in-game close confirmation is armed only on Telegram mobile clients; on
  desktop (tdesktop/macOS/web) it is skipped, where the "changes may not be
  saved" dialog is just noise (drafts auto-save).
2026-06-15 17:22:09 +02:00
Ilia Denisov 03eb8044ff feat(ui): real friend-invite share with a per-bot link
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Successful in 9s
CI / integration (pull_request) Successful in 15s
CI / ui (pull_request) Successful in 47s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m12s
The friend-code 'share' was an <a> that just opened the bot. Make it a real share:
Telegram's native share-to-chat picker inside the Mini App (openTelegramLink +
t.me/share/url), the system share sheet (navigator.share) on the web, else copy the
link. The shared deep link points at the same bot the player is in — it picks
VITE_TELEGRAM_LINK_EN/_RU by the session's service language, falling back to the
single VITE_TELEGRAM_LINK. Adds the per-bot build args across Dockerfile / compose /
ci.yaml / .env / docs; PLAN TODO-5 updated.
2026-06-15 15:49:36 +02:00
Ilia Denisov 8881214213 R6(a): de-stage code, docs, READMEs; split stage6_test
Mechanical, behaviour-preserving removal of Stage N / TODO-N / phase (RN)
references from comments, doc-comments, service READMEs, the current-state docs
(ARCHITECTURE, FUNCTIONAL+_ru, TESTING, UI_DESIGN), config-file comments, and the
.fbs/.proto schema comments. PLAN.md / PRERELEASE.md / CLAUDE.md keep the stage
history.

- Rename the only stage-named identifiers: registerStage8 -> registerSocialOps,
  registerStage11 -> registerLinkOps (gateway transcode).
- Split stage6_test.go: TestEmailLoginFlow -> email_test.go,
  TestGuestAutoMatchLeavesNoStats (+ provisionGuest) -> account_test.go.
- Regenerated proto bindings (push.pb.go, telegram_grpc.pb.go) from the de-staged
  .proto comments; FB Go/TS bindings unchanged (flatc strips schema comments).

go build/vet/gofmt clean across modules; integration typecheck and pnpm check green.
2026-06-10 16:56:03 +02:00
Ilia Denisov 7e34897d6d Review fixes: swipe (capture-phase, enabled in TG), TG header aligns to the nav band, DnD zoom delay 1s->0.7s
CI / changes (pull_request) Successful in 1s
CI / unit (pull_request) Successful in 8s
CI / integration (pull_request) Successful in 13s
CI / ui (pull_request) Successful in 32s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m8s
- Edge-swipe back now listens at the window in the CAPTURE phase (the board's
  pointer handlers can't swallow it) and is no longer skipped inside Telegram
  (where the owner tests it).
- TG-fullscreen header: expose the device safe-area top (--tg-safe-top) and
  centre the title + menu pair within Telegram's nav band ([safe-top,
  content-top]) below the notch, keeping the band's height — lining up with
  Telegram's own controls.
- DnD auto-zoom-on-hover delay reduced 1000ms -> 700ms.

(Client-IP: diagnosed as the owner's home-router SNAT — the host caddy already
receives 192.168.0.1 with no XFF, so the real IP is lost upstream of our stack;
correct in prod. No code change.)
2026-06-08 22:01:43 +02:00
Ilia Denisov 34385240b9 Game/Telegram review polish: USSR flag, touch drag ghost, TG fullscreen header
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Successful in 8s
CI / integration (pull_request) Successful in 11s
CI / ui (pull_request) Successful in 31s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 55s
Backlog item 2 of ~4 (owner review pass):
- USSR flag emblem redrawn (canonical hammer & sickle, scaled down 1.5x
  below the star).
- Touch drag-and-drop: enlarge the drag ghost 1.5x on touch only (the finger
  hides the tile); suppress the iOS tap-highlight that lingered on a rack tile
  sliding into a dragged tile's slot.
- Telegram fullscreen: its native nav no longer hides our header -- the header
  drops below the content-safe-area top inset and the menu (hamburger) lifts
  into the nav band, centred (--tg-content-top from the SDK inset + a
  tg-fullscreen class; new telegram.ts helper + app wiring).

Tests: UI check/test:unit/build + full e2e (60) green. The iOS tap-highlight
fix and the TG-fullscreen layout want on-device verification on the deploy.
2026-06-08 17:11:10 +02:00
Ilia Denisov f6bffd1f57 Stage 17 (contour round 3): Telegram Mini Apps polish, board scroll, keyboard overlay
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Successful in 8s
CI / integration (pull_request) Successful in 10s
CI / ui (pull_request) Successful in 27s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 54s
- Telegram (lib/telegram.ts): chrome colours (setHeaderColor/setBackgroundColor/setBottomBarColor) match Telegram's header/bg/bottom bar to the app; native BackButton on sub-screens (app chevron hidden in TG); HapticFeedback on tile place/commit/error; enableClosingConfirmation while a game is open; disableVerticalSwipes so swipe-to-minimise doesn't fight tile drag / board scroll
- #9 board-only vertical scroll: Screen 'column' mode lets the board area scroll while score/status/rack/tab bar stay fixed (zoom keeps its own scroll)
- #10 check-word dialog opens in Modal keyboard-overlay mode (top-anchored, keyboard overlays the empty area) — no resize/relayout jank; other modals stay keyboard-aware
- docs: UI_DESIGN Telegram integration + vertical fit/keyboard; PLAN round 2-3 follow-ups
2026-06-06 12:55:46 +02:00
Ilia Denisov c94cd3c3bf Stage 17 (contour round 2): Grafana Live/reload, rate-limit, iOS reconnect, hint/plaque/make-move UX
- Grafana: disable Live (GF_LIVE_MAX_CONNECTIONS=0) so its WebSocket no longer trips caddy Basic-Auth and re-prompts; admin console gains a Grafana nav link
- deploy: force-recreate config-only services so reseeded Grafana dashboards / Caddyfile are actually picked up (the move-duration panel was invisible because the bind-mount went stale)
- rate-limit: raise per-user budget 120/40 -> 300/80; UI skips reloading on the echo of the player's own move (fewer requests, no double-load)
- iOS/Telegram reconnect: suppress the connection banner while backgrounded and for a short grace after resume; reconnect silently; wire visibilitychange + pageshow/pagehide + Telegram activated/deactivated (Bot API 8.0)
- hint button disabled when 0 hints remain; nudge button shows a disabled state on your own turn
- players plaque: invert so the active seat pops (accent chip, raised) and others recede
- make-move UX: a direct  commit button (no hold/popover); the Shuffle tab becomes ↩️ Reset while tiles are pending
2026-06-06 12:33:52 +02:00
Ilia Denisov 1d0bafaabb Stage 17: UI defect fixes (russian variant, Telegram theme/nav/banner, reconnect, hint zoom, plaque, history, transitions, per-game cache)
- #6 align the UI variant id to the backend canonical 'russian_scrabble' (type, variants, Lobby, mock, tests) — fixes the New->Russian 400
- #11/#12 inside Telegram force the colour scheme from WebApp.colorScheme (over OS prefers-color-scheme, fixing the Telegram Desktop breakage) and hide the theme switcher
- #14/#15 nav bar takes Telegram's bg; announcement banner gets a dedicated subtle --ad-bg accent token
- #16 suppress the reconnect banner while backgrounded and silently reconnect the live stream on return to the foreground
- #17 hint zoom scrolls to the placement's bounding box, not the top-left
- #19/#20 players plaque: active seat raised with side shadows, others sunk; tap toggles history
- #21/#23 history: scrollbar-gutter:stable (no word jitter); fixed-height drawer pins the bottom shadow to the board
- #3 (UI) disable nudge on the player's own turn
- #18a directional screen slide transitions (forward in from the right, back reveals the lobby)
- #13 per-game in-memory cache: instant render on re-entry + background refresh
- e2e: openGame waits for the slide transition to settle
2026-06-06 10:23:42 +02:00
developer 01485d8fc6 Stage 11: account linking & merge (email + Telegram Login Widget) (#12)
Tests · Go / test (push) Successful in 7s
Tests · Integration / integration (push) Successful in 11s
Tests · UI / test (push) Successful in 18s
2026-06-04 09:18:17 +00:00
Ilia Denisov cf66ed7e26 Stage 9: Telegram integration (connector side-service, Mini App, out-of-app push)
Tests · Go / test (push) Successful in 7s
Tests · Integration / integration (push) Successful in 12s
Tests · Go / test (pull_request) Successful in 6s
Tests · Integration / integration (pull_request) Successful in 11s
Tests · UI / test (pull_request) Successful in 19s
New platform/telegram connector (own container, bot token only there):
- go-telegram/bot long-poll loop: /start deep-links + Mini App launch button.
- gRPC API pkg/proto/telegram/v1 (Telegram service): ValidateInitData, Notify
  (renders a localized message + deep-link button), SendToUser/SendToGameChannel
  (admin, wired in Stage 10). Generic methods are platform-agnostic (external_id).
- Bot API base override for Telegram's test environment; Dockerfile + compose
  (VPN sidecar, no public ingress); README.

Gateway:
- initData validation relocated from the gateway into the connector; the gateway
  calls ValidateInitData over gRPC (GATEWAY_CONNECTOR_ADDR), drops the bot token,
  and deletes internal/auth.
- Out-of-app push: runPushPump routes events whose recipient has no live in-app
  stream to connector.Notify, gated by /internal/push-target + the in-app-only
  flag (race-free de-dup); HasSubscribers added to the push hub.

Backend:
- Migration 00007 accounts.notifications_in_app_only (default true) + jetgen.
- ProvisionTelegram seeds a new account's language/display name from the launch
  fields; IdentityExternalID reverse lookup; /internal/push-target handler.

UI:
- Telegram Mini App launch: detect initData, apply themeParams, authTelegram,
  route the deep-link start_param (g/i/f); /telegram/ guard redirects outside
  Telegram. Vite relative base + telegram-web-app.js. In-app-only profile toggle;
  share-to-Telegram link for a friend code. Vitest + Playwright coverage.

Wire/docs/CI: fbs Profile/UpdateProfileRequest gain notifications_in_app_only
(Go + TS); go.work uses ./platform/telegram; go-unit.yaml covers it; PLAN,
ARCHITECTURE, FUNCTIONAL (+ru), UI_DESIGN, READMEs updated.
2026-06-04 07:10:21 +02:00