Release v1.8.0: promote development → master #173
Reference in New Issue
Block a user
Delete Branch "development"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Release v1.8.0 — promote development → master
Большой накопленный бэклог. Migration-деплой (3 новые миграции → maintenance window + pg_dump; все expand-contract, откат DB-safe).
Что входит
Pre-flight (проверено)
scrabble, established).После мержа
prod-deploy(build→deploy-main→deploy-bot→verify, rolling+auto-rollback, migration maintenance window).ansible-playbook(не в деплое).PROD_GATEWAY_HONEYTOKENуже задан) — подсадить приманку, если нужна ловушка.Score and validate a tentative move on-device instead of a per-arrangement network round trip. The dawg reader and the validate/score/direction slice of the scrabble-solver engine are ported to TypeScript (ui/src/lib/dict), pinned byte-for-byte to the Go engine by a `conformance` CI job (full-dictionary reader parity plus a battery of plays across every variant and both cross-word rules, including the inferred orientation). The server stays authoritative — submit_play re-validates — so the local result is an advisory accelerator only. - backend: Registry.DictBytes + an authed GET /api/v1/user/dict/{variant}/{version} (immutable) streaming the pinned per-game dawg; caddy routes /dict to the gateway. - gateway: a session-gated /dict edge route proxying it; fetchDict on the transport. - client: the dictionary loads on game open (low priority so it never starves the game on a slow link; aborted at a 5s cap or when leaving the game), is cached in IndexedDB (best-effort, self-healing on a rejected blob) and reused across sessions; a warm-up overlay covers a cold load, then the network preview is the fallback; a bad-connection breaker stops warming after repeated misses; the move preview cancels its in-flight request when the tiles change. - parity generators backend/cmd/{dictgen,validategen} + gated Vitest suites, run in CI against the release dictionaries. A hidden debug readout lists the cached dictionaries + breaker state, and its reset clears the cache. - docs: ARCHITECTURE §5, TESTING, UI_DESIGN, FUNCTIONAL (+ru).The small 1️⃣ in the status bar's score-preview slot read as noise; the single-word rule keeps its spelled-out label in the history header and the lobby invitation card.The landing now always opens in Russian (saved 🌐 choice still wins) — browser-language detection made the indexed content nondeterministic (Googlebot renders with en-US). landing.html gains the static Russian SEO head: title/description, canonical pinned to the production origin, Open Graph card (Telegram/VK link previews), twitter:card, JSON-LD, theme-color and the favicon set; the SPA shell turns noindex and its tab title becomes «Эрудит (Скрэббл)». New assets/icons generator (same tile design as the VK loader) produces favicon.svg/ico, apple-touch-icon.png and og-image.png into ui/public/, plus robots.txt.Un-hide the Profile email box for guest accounts (hidden={!p.isGuest}): a guest binds an email to register / sign in, and a returning address opens the existing merge dialog. Provider linking stays hidden — the Telegram control keeps its wiring behind a hidden attribute — until the non-guest linking matrix (PR2). The two linking e2e specs remain skipped (they assume a non-guest login and the visible Telegram control); update their stale comments.Unlink: POST /user/link/unlink (telegram|vk) via account.RemoveIdentity, refusing the last identity; email is never unlinked. New fbs LinkUnlinkRequest + gateway link.unlink op, returning the refreshed profile. Change-email: purposeChange confirm-codes (RequestChangeCode/ConfirmChange) that atomically replace the account's confirmed email (account.replaceEmailIdentity); a new address owned by another account is refused without disclosure, never merged. The one-tap deeplink handles purposeChange too. Reuses the LinkEmail* fbs tables; gateway link.email.change.{request,confirm} ops + backendclient methods; branded ru/en change-email copy.The bare U+2709 rendered as a mono pseudo-glyph; add the U+FE0F variation selector so it shows as ✉️.Step-up: email accounts confirm with a mailed code (purpose=delete, no deeplink — ConfirmByToken refuses a delete token so a stray click can't delete); platform-only accounts type a fixed phrase (anti-impulse). Endpoints /user/delete/{request,confirm}; the confirm orchestration resigns active games, drops all-robot games, tombstones + anonymizes the account (freeing its creds), and revokes its sessions — the tombstone is the point of no return, the rest best-effort. Gateway account.delete.{request,confirm} ops + fbs AccountDeleteConfirm/AccountDeleteRequestResult + branded ru/en delete email. Integration tests cover the step-up (code + no-email) and the orchestration pieces.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.A browser has no signed VK Mini App launch params, so linking VK on the web uses VK ID's raw OAuth 2.1 flow (PKCE, no @vkid/sdk): the SPA redirects to VK's hosted login and returns with an authorization code, which the gateway exchanges server-side (confidential, under the VK "Web" app's protected key) for the trusted vk user id — then the existing link/merge machinery attaches or merges it. - fbs LinkVKRequest{code, device_id, code_verifier}; codec + TS bindings. - backend link.Service ConfirmVK/MergeVK/attachVK (KindVK, mirror Telegram), handleLinkVK[Merge], routes /user/link/vk[/merge], backendclient LinkVK[Merge]. - gateway internal/vkid confidential code exchange (id.vk.com/oauth2/auth); transcode link.vk.confirm/merge (registered only when configured) + config GATEWAY_VK_ID_{APP_ID,CLIENT_SECRET,REDIRECT_URL} + main wiring. - UI lib/vkid (PKCE authorize redirect + callback), Profile "Link VK" control, boot callback handling; a merge re-authorizes for a fresh code (VK codes are single-use). Web-only (a redirect strands a Mini App webview). - Deploy: VITE_VK_APP_ID + VITE_VK_ID_REDIRECT_URL build args + gateway env, ci.yaml/prod-deploy TEST_/PROD_ vars, compose/Dockerfile/.env.example/README. - Tests: vkid exchange unit (string/number user_id, id_token fallback, errors), transcode link.vk, backend ConfirmVK/MergeVK inttest, codec encodeLinkVK. - Docs: ARCHITECTURE §4, FUNCTIONAL(+ru), gateway README.The maintenance overlay never showed on the test contour: only prod-deploy.sh raised the caddy maintenance flag, so a test redeploy was a bare gateway/backend recreate — the SPA saw a transient 502 ("Reconnecting…"), never the 503 + X-Scrabble-Maintenance marker the overlay keys on. So the feature (PR #171) was unverifiable off prod. Raise the flag around the recreate in ci.yaml's deploy job too, mirroring prod. Ordering matters because of the config reseed: ci does `rm -rf $conf` + recreate, so the running caddy sits on the stale pre-reseed bind mount and cannot see a flag written to the new dir until it is recreated. So: raise the flag, force-recreate caddy FIRST (onto the fresh mount, where it carries the flag and 503s), then recreate gateway/backend (the window), then the other config services, then lower it. A trap clears the flag if the step fails, and the next deploy's reseed wipes a stale one — so the contour can't stick in maintenance. The caddy-routed probes run in the next step, after the flag is lowered. Prod was already correct (prod-deploy.sh); this only makes the test contour faithful so the overlay + reload can be seen there. An open SPA must already be on the PR-#171 bundle (which carries the overlay code) — reload once to bootstrap onto it.Nice! <3