feat(export): platform-native delivery — TG popup+downloadFile, VK viewer, mobile share sheet
CI / changes (pull_request) Successful in 1s
CI / unit (pull_request) Successful in 10s
CI / integration (pull_request) Successful in 14s
CI / ui (pull_request) Failing after 1m7s
CI / conformance (pull_request) Successful in 9s
CI / gate (pull_request) Failing after 0s
CI / deploy (pull_request) Has been skipped

Per the on-device review, the last hop is now the most native affordance
per platform, all fed by the same signed URL:

- Telegram: the native showPopup chooser returns (safe here — the whole
  TG chain is bridge calls, which need no user activation) and both
  formats go to the native downloadFile dialog on iOS and Android alike.
- VK: the PNG opens in VK's native photo viewer (VKWebAppShowImages) —
  on screen at once, saved/shared from the viewer's own controls; the
  GCG keeps VKWebAppDownloadFile; either falls back to a plain anchor
  download (the desktop iframe). The gateway now serves /dl/* via
  http.ServeContent (Content-Length + Range/206) — another swing at the
  VK Android DownloadManager hang; if it persists, the next step is the
  clipboard fallback for the VK-Android GCG.
- Mobile browsers: the OS share sheet with the fetched file (the proven
  fetch-then-share pattern) — nothing lands in Downloads first; desktop
  keeps the anchor download.

Legacy Telegram (< 8.0, no downloadFile) keeps the app modal + GCG
clipboard copy and hides the image option.
This commit is contained in:
Ilia Denisov
2026-07-02 22:19:46 +02:00
parent c3c27bba5e
commit c3c26bbbc0
11 changed files with 189 additions and 60 deletions
+14 -12
View File
@@ -322,9 +322,9 @@ Finished games are archived in a dictionary-independent form and exportable in
**two formats behind one 📤 button** — the GCG file and a rendered **PNG image** of
the final position; the export is offered **only once a game is finished**, and
never for an honest-AI practice game (a live game's export would leak the move
journal; an AI game is throwaway). The format chooser is always the app's **own
modal** — deliberately not Telegram's native popup, whose callback runs without
user activation and silently breaks the share/clipboard delivery it leads to.
journal; an AI game is throwaway). The format chooser is Telegram's **native popup**
inside Telegram (safe there: the whole Telegram chain is bridge calls, which need no
user activation) and the app's own modal elsewhere (VK has no native chooser).
The **image** is rendered on the server (the internal render sidecar runs the same
drawing module the web client tests; always the light theme): the final board with
@@ -340,15 +340,17 @@ running totals alone do not include it). The footer stamps the site host and the
finish date in the device locale. The scoresheet typography is fixed; a long game
stretches the board (never below its minimum) so the image carries no dead space.
Delivery is **one route for both formats on every platform**: the app requests a
signed, short-lived download link and hands it to the platform's native download —
Telegram's download dialog, VK's `VKWebAppDownloadFile`, or an ordinary browser
file download elsewhere (including the desktop VK iframe). The link needs no login
to fetch (the platforms' download calls carry none), is valid for minutes, and
serves the artifact as a named file attachment. The single exception is a legacy
Telegram client without the download dialog (pre-Bot API 8.0): there the GCG falls
back to the old clipboard copy (with the confirming toast) and the image option is
not offered. Statistics (durable accounts only):
Delivery is **one signed, short-lived link for both formats on every platform**,
handed to the most native affordance: Telegram's download dialog (both TG
platforms; sharing onwards from Telegram's own file preview); on VK the image opens
in **VK's native photo viewer** (on screen at once — saving/sharing are the
viewer's own controls) and the GCG goes through VK's download; a **mobile browser
gets the OS share sheet** with the file (nothing lands in Downloads first); a
desktop browser downloads the file. The link needs no login to fetch (the
platforms' downloaders carry none) and is valid for minutes. The single exception
is a legacy Telegram client without the download dialog (pre-Bot API 8.0): there
the GCG falls back to the old clipboard copy (with the confirming toast) and the
image option is not offered. Statistics (durable accounts only):
wins, losses, draws, max points in a game, and max points for a single move (the
best play, which already includes every word it formed plus the all-tiles bonus). It
also shows the player's **move count** (their plays — passes and exchanges do not