41a642ef974dd6ca6c78c20ce38c3c05ecb9e7fe
61 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
41a642ef97 |
R4: push enrichment — events carry a state delta, kill the last poll
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 37s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m8s
Enrich the in-app live stream into a delta channel so the UI renders a move from the event without a follow-up game.state, and make the matchmaking poll a stream-down fallback. - pkg/fbs: trailing fields on opponent_moved (move+game+bag_len), your_turn (move_count), match_found (state), game_over (game), notify (account/invitation/state), MoveResult (rack+bag_len); regenerate Go + TS. - backend: notify owns the FB encoding (encode.go + payload.go input structs); game/lobby/social map their domain types in. emitMove builds the move delta; game.Service.InitialState feeds match_found/game_started the recipient's initial StateView; friends/invitations notify carry their account/invitation. The move-commit response (submit_play/pass/exchange/resign) returns the actor's refilled rack + bag size. - gateway: MoveResult transcode carries rack+bag_len. - ui: pure lib/gamedelta.ts reducer advances the per-game cache keyed on move_count (idempotent + gap-safe); app.svelte seeds the cache on match_found/game_started; Game.svelte applies the delta (commit/pass/exchange/resign drop their load()); NewGame polls only while app.streamAlive is false. - docs: ARCHITECTURE §10, FUNCTIONAL(+ru), backend/gateway/ui READMEs; PRERELEASE R4 marked done + Refinements. |
||
|
|
26aa154547 |
R1: schema & naming reset — squash migrations, rename variants
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Successful in 9s
CI / integration (pull_request) Successful in 11s
CI / ui (pull_request) Successful in 37s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m8s
Squash the 12 goose migrations into one 00001_baseline.sql (there is no prod data; verified schema-identical to the chain via a pg_dump diff + the green integration suite) and rename the game-variant labels english/russian_scrabble/erudit -> scrabble_en/scrabble_ru/erudit_ru across the backend, the FlatBuffers wire values and the UI. dawg filenames and the Go enum identifiers are unchanged; the i18n display keys are kept. Adds PRERELEASE.md (the R1-R7 pre-release tracker), linked from CLAUDE.md. Contour DB wipe and the scrabble-dictionary tidy are follow-ups. |
||
|
|
d87c0fb10b |
Stage 17: cap display-name special characters at 5 (ui + backend)
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Successful in 8s
CI / integration (pull_request) Successful in 14s
CI / ui (pull_request) Successful in 35s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m3s
display_name validation gains a rule: at most 5 special characters — the '.' / '_' punctuation (spaces, which separate words, don't count) — so a still-well-formed name can't be mostly punctuation. Mirrored in the Go ValidateDisplayName and the UI validDisplayName; both unit-tested (5 ok, 6 rejected, 'J. R. R. Tolkien' ok). Docs: FUNCTIONAL (+ _ru). |
||
|
|
84ecc85f51 |
Stage 17 #2 fix: connection failures show only the spinner, never a toast
A dropped/reset/timed-out connection can surface as a Connect code other than Unavailable (Canceled/DeadlineExceeded/Unknown/…) which fell through to the generic 'internal' -> a red 'something went wrong' toast appeared alongside the Connecting spinner. Now toGatewayError (moved to the pure retry.ts, unit-tested) collapses every transport-level code to 'unavailable' so it is retried + flips offline; and handleError suppresses the toast for any connection code AND whenever the app is mid-reconnect (!connection.online), covering the race where a unary error lands before the stream reports the drop. Genuine server-internal / domain errors still toast while online. |
||
|
|
efa1d0bd22 |
Stage 17 #2: extend the offline soft-disable to all server-action buttons
CI / changes (pull_request) Successful in 1s
CI / unit (pull_request) Has been skipped
CI / integration (pull_request) Has been skipped
CI / ui (pull_request) Successful in 35s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m6s
Following the in-game bar, the Connecting indicator now also visually disables the
other proactive (server-sending) controls while offline: chat send + nudge, profile
save / link email|telegram / merge-confirm, friends (redeem, get-code, accept/decline,
unfriend, block, unblock), New Game (auto-match variant + send-invitation) and the
lobby hide ❌. Purely local controls (board/rack/reset, menus, navigation, settings,
copy-code) stay live. Each reads the global connection.online signal; full e2e + check
green.
|
||
|
|
ef61b778fc |
Stage 17 #2: Connecting indicator + auto-retry, instead of red toasts
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 36s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 55s
Connectivity failures become state, not a toast on every attempt. A global online signal (lib/connection.svelte.ts) flips on a transport unavailable / rate_limited and on the live stream's drop, driving a pure-CSS header spinner + 'Connecting…' in place of the title and softly disabling the in-game server actions (commit / exchange / pass / hint; local board/rack/reset stay live). - transport: exec auto-retries with capped exponential backoff — every op on a rate-limit (rejected before processing, safe), reads only on unavailable (a mutation is never blindly re-sent, to avoid double-applying one whose response was lost; its button is disabled while offline so the player re-issues on reconnect). A reachability watcher (profile.get probe) and any successful traffic clear the signal. - the old red error.unavailable toast is gone (handleError suppresses connection codes; the indicator replaces it). A server-data screen still opens with the spinner and fills on reconnect (global indicator + read auto-retry), so navigation is never dead. - pure retry policy unit-tested (retry.ts); a mock-only window.__conn hook drives a Chromium+WebKit e2e (indicator shows offline, the action disables, both clear on reconnect). Full suite + build green. - docs: ARCHITECTURE transport note, FUNCTIONAL (+ _ru), PLAN tracker (incl. #1 — the bot already drains all updates, no change). Also records #1 as investigated/no-change in PLAN. Other server-action buttons (chat send, profile save, …) still degrade to a safe no-op offline; visual disable is easy to extend. |
||
|
|
f166ff30fe |
Stage 17 #4: enrich the out-of-app your-turn push + add game-over
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Successful in 8s
CI / integration (pull_request) Successful in 12s
CI / ui (pull_request) Successful in 34s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m20s
The Telegram 'your turn' notification now names the opponent and recaps their last
move (voiced as the opponent: «{name}: my move — «WORD». Score 120:95» for a scoring
play; a short 'swapped / passed, your turn' otherwise), and a new game-over
notification reports the result + final score when a game ends by any path (closing
play, all-pass, resign, timeout). Scores are recipient-first (the reader's score
leads), 2-4 players (120:95:80).
- schema: YourTurnEvent gains opponent_name/last_action/last_word/score_line
(appended, backward-compatible); new GameOverEvent{result, score_line}. Go + UI
bindings regenerated (flatc 23.5.26 + pnpm codegen).
- backend: notify.YourTurn enriched + notify.GameOver; emitMove resolves the mover's
name and emits per-recipient (your_turn to the next mover, game_over to every seat),
with recipient-first score lines built in one place.
- gateway: game_over joins the out-of-app whitelist (routing.go).
- connector: render builds the enriched your_turn + game_over text per language (en/ru).
- tests: notify round-trip (enriched + game_over), emit (enriched fields + game_over to
all seats / per-seat result), connector render (en/ru), routing; integration replay
(play → your_turn with real name; resign → game_over) green.
- docs: ARCHITECTURE push catalog + out-of-app set, FUNCTIONAL (+ _ru), PLAN tracker.
|
||
|
|
13361c098c |
Stage 17 #5: make the active-row chevron open the game (not a no-op)
CI / changes (pull_request) Successful in 1s
CI / unit (pull_request) Successful in 8s
CI / integration (pull_request) Successful in 11s
CI / ui (pull_request) Successful in 36s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m5s
Owner review: the '>' on an active game row should be a real tap target that opens the game, like the rest of the row — not inert. The chevron now navigates (kept out of the tab order / a11y tree since the row's main button already does the same), and active-row swipes no longer suppress the tap. Adds an e2e for the chevron navigation. |
||
|
|
4999478ded |
Stage 17 #5: hide finished games from your own lobby list
CI / changes (pull_request) Successful in 3s
CI / unit (pull_request) Successful in 9s
CI / integration (pull_request) Successful in 11s
CI / ui (pull_request) Successful in 35s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 2m16s
A player can remove a finished game from their own 'my games' list. The action is per-account, finished-only and irreversible (the game stays for the other players; there is no un-hide). - backend: migration 00012 game_hidden(account_id, game_id); store HideGame + hiddenGameIDs + ListGamesForAccount filtering; service HideGame (seat + finished checks, reusing ErrNotAPlayer / ErrGameActive); POST /api/v1/user/games/:id/hide. - gateway: game.hide edge op (reuses GameActionRequest -> Ack) + backendclient.HideGame. - ui: finished rows reveal a delete via swipe-left (touch) or a kebab tap (desktop), active rows get an inert chevron for icon alignment; optimistic removal + lobby-cache sync; mock + transport + client wiring; lobby.hideGame label (en/ru). - tests: integration (active->ErrGameActive, outsider->ErrNotAPlayer, per-account, idempotent), gateway transcode round-trip, mock e2e (kebab -> delete); hardened a pre-existing chat-screen .back transition flake surfaced by the new test's timing. - docs: ARCHITECTURE persistence list, FUNCTIONAL (+ _ru) lobby story, PLAN tracker. |
||
|
|
a84e9d8cb7 |
Fix screen-slide direction: by route depth, so back from chat/check slides back
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Successful in 8s
CI / integration (pull_request) Successful in 12s
CI / ui (pull_request) Successful in 35s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m7s
The 'lobby is back' rule slid the chat/check back-to-the-game forward. Direction is now computed from route depth (lobby < game < chat/check): shallower = back, deeper = forward. |
||
|
|
70110effd9 |
Chat + word-check as their own screens; in-game unread badge (review item 7)
CI / changes (pull_request) Successful in 1s
CI / unit (pull_request) Successful in 8s
CI / integration (pull_request) Successful in 14s
CI / ui (pull_request) Successful in 34s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m6s
- Chat and word-check are now routed screens (/game/:id/chat, /game/:id/check) with a header back to the game and no tab-bar, replacing their modals. The soft keyboard just resizes the visible viewport (tracked into --vvh, which the Screen height uses since iOS does not shrink dvh for the keyboard) with the input pinned to the bottom: no modal relayout, no page jump. Supersedes the earlier bottom-sheet Modal attempt. - A new chat message raises an unread badge on the in-game hamburger + the Chat menu row (per game, cleared on opening the chat), mirroring the lobby badge. - TG native back + the header back chevron return chat/check to their game. - Exposes --tg-safe-top (device notch) for the finalised TG-fullscreen header. Tests: e2e for chat/check opening as their own screens + back. Docs: PLAN, FUNCTIONAL(+ru). |
||
|
|
295e45486d |
TG-fullscreen header: add height via padding (min-height wasn't binding), +12px breathing room
CI / changes (pull_request) Successful in 1s
CI / unit (pull_request) Successful in 8s
CI / integration (pull_request) Successful in 12s
CI / ui (pull_request) Successful in 33s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m10s
|
||
|
|
a132edd40a |
TG-fullscreen header: +6px band height so native controls aren't flush (owner tweak)
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Successful in 8s
CI / integration (pull_request) Successful in 12s
CI / ui (pull_request) Successful in 33s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 2m14s
|
||
|
|
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.) |
||
|
|
645df52c0b |
Round-6 follow-up: UX polish + client-IP fix
CI / changes (pull_request) Successful in 2s
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
- Client IP: the compose caddy trusts X-Forwarded-For from private-range upstreams (trusted_proxies private_ranges), so the real client IP survives the host-caddy hop (it was logging the docker caddy hop 172.18.0.x for chat moderation and bucketing the gateway per-IP rate limiter on it). Correct and spoof-safe in both contours (prod has no host caddy); peerIP unit-tested. - Ad banner gated off behind a compile-time SHOW_AD_BANNER=false (the if-branch, the AdBanner import and banner.ts are tree-shaken out of the prod bundle). - Landing: the Telegram entry is just the 64px logo (clickable, no button/text). - TG-fullscreen header: title + menu centred as a pair (hamburger right of the title), pinned to the bottom of the TG nav band. - Edge-swipe back (Screen): a left-edge rightward drag navigates to back (touch/pen only, armed from <=24px; skipped inside Telegram). - Chat soft-keyboard: a bottom-sheet Modal lifted above the keyboard by a visualViewport-driven transform (compositor-only, no page/sheet relayout). iOS-specific, needs on-device tuning; native resize=none awaits Capacitor. - Tests: e2e for the in-game '✓ in friends' item and a board→board tile relocation; codec units for last_activity_unix + OutgoingRequestList. Deferred to the next PR (agreed): #4 enrich the your-turn/game-end push; #5 hide finished games from the lobby. |
||
|
|
6b6baf5710 |
Stage 17 round 6 (#16/#17, PR C): lobby sort + server-derived in-game friend state
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 1m19s
Lobby: group the my-games list into your-turn / opponent-turn / finished (empty sections hidden), ordered by last activity (your-turn oldest-first, the other two newest-first), as a compact line-separated list. gameDTO and FB GameView gain last_activity_unix (turn start while active, finish time once finished); a pure lib/lobbysort.ts holds the grouping/ordering. Friends: the in-game 'add to friends' item is now server-derived via a new GET /user/friends/outgoing (+ friends.outgoing op), returning addressees with a pending OR declined request (both read as 'request sent'), so it is correct across reloads; it shows a disabled '✓ in friends' once accepted. It live-updates when the opponent answers: RespondFriendRequest now publishes friend_added (accept) / friend_declined (new notify sub-kind, decline) to the original requester, whose open game re-derives its friend state. Tests: lobbysort unit test; gateway outgoing + last_activity transcode tests; backend integration ListOutgoingRequests + respond-publishes-to-requester; e2e updated for the new lobby section labels + a non-friend active opponent. Docs: ARCHITECTURE notify catalog, FUNCTIONAL(+ru) lobby/friends, PLAN. |
||
|
|
b720907db2 |
Review fixes #2: bigger flag star, TG header below nav, board-tile relocation
CI / changes (pull_request) Successful in 1s
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 1m7s
Addressing the review on #23: - Flag star scaled up ~25% (the hammer&sickle emblem unchanged, kept clear of it). - TG fullscreen header: drop the WHOLE header below the content-safe-area top inset (the hamburger stays to the right of the title), instead of pinning the hamburger to the physical top edge. - DnD: a placed (pending) tile can now be relocated by dragging it to another board cell (board->board); it lifts off its source cell while dragged; and it can be grabbed even on the zoomed board (touch-action:none on the pending cell, so the drag wins over the board pan). The manual-selection blue frame now clears on recall. |
||
|
|
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. |
||
|
|
3fd279cf8c |
Landing v2: icon switchers, ephemeral theme, channel link, drop browser CTA
CI / changes (pull_request) Successful in 1s
CI / unit (pull_request) Successful in 7s
CI / integration (pull_request) Successful in 10s
CI / ui (pull_request) Successful in 32s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m6s
Owner review-pass rework of the landing page: - Rename the per-language Telegram link build var VITE_TELEGRAM_LINK_EN/_RU -> VITE_TELEGRAM_GAME_CHANNEL_NAME_EN/_RU (it carries a channel username; the landing builds https://t.me/<name> -- the same channels the connector posts to via TELEGRAM_GAME_CHANNEL_ID_*). - Language switcher -> a globe icon dropdown (flags + names), saved + synced to the app prefs. - Theme switcher -> a sun/moon icon toggle, ephemeral (follows the system scheme, no auto, never persisted) -- galaxy-game style. - Drop the "Play in browser" CTA (no standalone-web onboarding yet). Docs: FUNCTIONAL(+ru), PLAN, deploy + ui READMEs. |
||
|
|
e16076c89e |
Stage 17 round 6 (#16-20): landing page, /app/ move, cache + stream fixes
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Successful in 9s
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
Close out Stage 17 round 6: - Landing page at / — one Vite build with two entries (index.html = game SPA, landing.html = a lightweight landing reusing the theme/i18n/ aboutContent leaf modules, not the app store). - Move the web game SPA to /app/; the Telegram Mini App stays at /telegram/ (gateway webui.Handler(stripPrefix, indexName): landing at /, SPA at /app/ + /telegram/). Per-language "Play in Telegram" link via new VITE_TELEGRAM_LINK_EN/_RU build vars (button hides when unset). - Cache headers: hash-named /assets/* immutable, HTML shells no-cache (the go:embed zero modtime emitted no validators, so the client re-downloaded the whole bundle every launch). - Live-stream 15s abort fix: an immediate heartbeat on open + a 10s default interval (the first tick at 15s raced the edge idle timeout -> reconnect storm). PLAN/ARCHITECTURE(§13)/FUNCTIONAL(+ru)/gateway+ui+deploy READMEs updated; round 6 closed. Tests: gateway webui/connectsrv units, ui landing unit + e2e, full e2e (60) green. |
||
|
|
f5c2404123 |
Stage 17 round 6 (#4/#5/#6): draft persistence wire + gateway + UI
CI / changes (pull_request) Successful in 1s
CI / unit (pull_request) Successful in 9s
CI / integration (pull_request) Successful in 11s
CI / ui (pull_request) Successful in 32s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m7s
Complete the client-side draft feature on top of the shipped backend
foundation (the game_drafts store/service):
- FB: DraftRequest{game_id,json} + DraftView{json} (a draft get reuses
GameActionRequest); regenerated committed Go + TS bindings.
- Backend REST: GET/PUT /games/:id/draft, a draftDTO
(rack_order/board_tiles) mapped to game.Draft.
- Gateway: draft.get/draft.save transcode forwarding the composition
JSON verbatim (json.RawMessage both ways -- no double-encode).
- UI: debounced save of the rack order + board tiles and restore on
load (lib/draft.ts), plus #5 -- tiles may be arranged on the
opponent's turn (placement relaxed; the preview and Make-move stay
your-turn-only, so an off-turn draft is position-only).
Tests: backend handler validation, gateway pass-through round-trip, UI
draft/codec units, and a draft-restore e2e.
|
||
|
|
2b0b1c0035 |
Stage 17 round 6 (#3): drag-reorder rack tiles with a visual gap
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Successful in 8s
CI / integration (pull_request) Successful in 14s
CI / ui (pull_request) Successful in 30s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m7s
Dragging a rack tile and dropping it back on the rack reorders it: the dragged tile is lifted out (the drag ghost stands in) and the tiles at/after the pointer's drop slot slide right to open a gap there, so the drop position is visible. On drop the rack and its stable ids are permuted (reorderIndices, unit-tested). Reorder applies only with no pending tiles, so it stays a clean permutation; dropping on a board cell still places as before. Server persistence of the order follows (#4). |
||
|
|
35666e1705 |
Stage 17 round 6 fixes: pin the nudge button right; schematic USSR flag emblem
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 30s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m7s
- Chat: always render the (possibly empty) flex:1 caption before the nudge button, so the nudge stays pinned right whether or not the cooldown text shows (it drifted left when available). - USSR flag: redraw the hammer & sickle as a thin schematic sketch — an elongated semicircle sickle with a handle, crossed by a T-shaped hammer (per the original's structure), instead of the bold over-filled emblem; the star is a touch smaller. |
||
|
|
d3657fdf5c |
Stage 17 round 6 (#11/#12): quick-game variant plaques with rules, flag, and move-limit
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 30s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 55s
Each auto-match variant is now a lobby-style plaque: the display name with a flag on the right (🇺🇸 / 🇷🇺; Erudit uses a bundled minimalist USSR flag SVG) and a one-line rules summary below — bag size, the ё rule, and bonus differences, sourced from the engine rulesets (Scrabble 100 · Скрэббл 104, ё a letter · Эрудит 131, ё=е, no centre ×2, +15). The move-time limit (24h auto-match clock) is shown under the buttons. e2e locks it. (Multiple-words-per-move is the same for every variant, so it is described in About/landing rather than repeated on each button.) |
||
|
|
74683f294f |
Stage 17 round 6 (#13/About): About screen content + app version from git describe
CI / changes (pull_request) Successful in 1s
CI / unit (pull_request) Successful in 8s
CI / integration (pull_request) Successful in 11s
CI / ui (pull_request) Successful in 29s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 56s
- About screen: prominent localized title (Scrabble / Эрудит (Скрэббл)), a rules link (en/ru Wikipedia), and the Random-game / Game-with-friends sections; copy lives in a shared aboutContent module (the landing will reuse it). The random-game move limit inlines the 24h auto-match clock. - App version: Vite define __APP_VERSION__ from VITE_APP_VERSION (default 'dev'), wired as a Docker build-arg sourced from `git describe --tags --always` in the deploy step — no manual version bumps. The fallback keeps a plain/local build working. |
||
|
|
cdf616d6c4 |
Stage 17 round 6 (#7): reset the nudge cooldown once the player acts
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 30s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m4s
The hourly nudge cooldown now clears as soon as the sender has moved or posted a chat since their last nudge — engagement lifts the 'don't spam' limit. Backend: Nudge checks game.LastMoveAt + the sender's last non-nudge chat against the last nudge time (GameReader gains LastMoveAt). UI: nudgeOnCooldown mirrors it — a chat reset is read from the message list, a move is tracked client-side (lastActedAt on commit/pass/exchange; the backend stays authoritative across a reload). Integration test covers the reset. |
||
|
|
2cb2b57cdb |
Stage 17 round 6 (#10 backend): enforce chat only on your turn
CI / changes (pull_request) Successful in 1s
CI / unit (pull_request) Successful in 8s
CI / integration (pull_request) Successful in 11s
CI / ui (pull_request) Successful in 30s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m3s
PostMessage now rejects a chat sent on a finished game or when it is not the sender's turn (ErrChatNotYourTurn -> 409 chat_not_your_turn), matching the UI where the message field is hidden off-turn and only the nudge shows. Existing chat tests post on the to-move seat and are unaffected; adds an off-turn-rejection integration test + the dto mapping case + the UI error message. |
||
|
|
512ad4dfb9 |
Stage 17 round 6 (cluster 1): profile, tap flash, variant naming, chat/nudge by turn
CI / changes (pull_request) Successful in 1s
CI / unit (pull_request) Successful in 9s
CI / integration (pull_request) Successful in 11s
CI / ui (pull_request) Successful in 30s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m3s
- Profile: drop the hint-balance line. - Board: no mobile tap flash on a cell tap (-webkit-tap-highlight-color: transparent), matching the web click; the only intentional cell animation stays the last-word flash. - Variant names keyed by the game's alphabet, not the UI language: english -> Scrabble always, russian_scrabble -> Скрэббл always (unlocalized, never collide), erudit localized. - Chat/nudge are mutually exclusive by turn: the message field + Send show on your turn, the nudge replaces them on the opponent's turn; while the nudge cooldown is active the button is disabled with a grey 'awaiting reply' caption to its left. |
||
|
|
29d1193a0a |
Stage 17 round 5 — board interaction & UI polish
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 29s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m17s
- Even zoom: interpolate the board scroll toward a pre-clamped target as the real width grows/shrinks, so it magnifies A->B in one motion instead of lurching and snapping back near the edges/centre. Recentre only on a zoom toggle, never on a focus change — so a 2nd+ placed tile and a hovered dragged tile no longer jump the board. - Drag: highlight the aimed-at empty cell as a drop target; hover-hold auto-zoom now fires only for the first (zoom-in) hold. - Pinch zoom: two-finger spread/close toggles zoom toward the pinch midpoint (preventDefault only for two touches, so one-finger scroll stays native); a second finger aborts a drag. - Shuffle hop capped at 0.3s and disabled under reduce-motion. - Make-move is a borderless icon button, disabled while the pending word is known illegal. - Variant display names: english & russian_scrabble -> Scrabble/Скрэббл, erudit -> Erudite/Эрудит; the in-game title shows the variant name (was always 'Scrabble'). |
||
|
|
10412fee8e |
Stage 17 round 5 — backend/correctness bug fixes
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Successful in 9s
CI / integration (pull_request) Failing after 12s
CI / ui (pull_request) Successful in 30s
CI / gate (pull_request) Failing after 1s
CI / deploy (pull_request) Has been skipped
- Resign on the opponent's turn: engine ResignSeat(seat) resigns a specific seat (not just toMove); game.Resign bypasses the turn check and forfeits the actor's seat. - Quick-match cancel was a UI no-op (only stopped polling): add the full path (REST /lobby/cancel -> gateway lobby.cancel -> client) and clear the matchmaker's pending result on Cancel, so a cancelled search is dequeued (no 'already queued', no later robot-substituted game). NewGame dequeues on cancel and on abandon. - Lobby win/loss: result.ts ranked by score, so a 0-0 resignation read as a win. The winner now takes rank 1 and the viewer is placed from rank 2 — matching the game-detail screen. - Friend request to a robot: robots no longer block requests; the request stays pending and expires (friendRequestTTL), mirroring a human who ignores it. - Nudge cooldown: ErrNudgeTooSoon now maps to a distinct nudge_too_soon code with a correct message; the chat nudge button disables during the hourly cooldown; the nudge note reads 'Waiting for your move!' (button keeps the Nudge action label). Tests: engine/service off-turn resign, matchmaker cancel-clears-result, friend-to-robot inttest, result.ts 0-0 resignation, nudge_too_soon mapping. |
||
|
|
71b054227a |
Stage 17 (#12): lines-off board variant (gapless checkerboard), Settings toggle
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Successful in 8s
CI / integration (pull_request) Successful in 13s
CI / ui (pull_request) Successful in 30s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 55s
Add a 'Grid lines' preference (default off): when off the board drops the 1px grid gaps for a gapless checkerboard (plain cells alternate shades; tiles get rounded corners and a soft right-side shadow so adjacent gapless tiles still read apart), saving ~14px of width. When on, the classic lined grid returns. Persisted with the other board-style prefs; wired through Board's new lines prop. e2e locks the default and the toggle. |
||
|
|
d0c1306d9b |
Stage 17 (#9): animated shuffle — tiles hop along a low parabola to new slots
CI / changes (pull_request) Successful in 1s
CI / unit (pull_request) Successful in 8s
CI / integration (pull_request) Successful in 11s
CI / ui (pull_request) Successful in 28s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 53s
Give each rack slot a stable id permuted with the letters on shuffle, so the keyed rack reorders (rather than relabelling in place) and Svelte's animate directive fires. hop flies each tile along a parabola (apogee ~half a tile height) with a duration that scales with the horizontal distance (arc length): the longest 1<->7 swap takes ~0.5s, shorter swaps land sooner. Ordinary reflow (place/recall) stays instant via a guard. e2e locks that a shuffle preserves the rack's tile multiset. |
||
|
|
1bbf0bc654 |
Stage 17 (#3,#5,#10): hover-hold drag zoom, always-editable profile, drag-back + double-tap recall
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 27s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 56s
- Board drag now auto-zooms toward a cell after holding the tile over it ~1s (#3). - Profile is inline-editable: drop the Edit/Cancel toggle, form is always shown for durable accounts; hint balance stays read-only; re-populate after link/merge (#5). - A pending tile recalls by double-tap (same cell) or by dragging it back onto the rack (unzoomed board); a single tap no longer recalls (#10). - e2e: lock double-tap recall + single-tap no-op; drop the removed Edit-profile click. |
||
|
|
b15fd30c4f |
Stage 17 (contour round 4a): quick fixes
CI / changes (pull_request) Successful in 1s
CI / unit (pull_request) Successful in 8s
CI / integration (pull_request) Successful in 12s
CI / ui (pull_request) Successful in 27s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m6s
- #4 bag label: '{n} in the bag' / 'Bag is empty' (was 'Bag {n}') - #6 allow a single trailing dot in display names (backend + UI regex + tests) - #1 double-tap zooms toward the tapped cell, not the top-left - #8 shuffle fires a short multi-pulse haptic - #11 highlighted/flashing tiles darken their bottom edge too (shadow joins the flash) - #13 toast slides up from the bottom and fades out - #7 hide the logout button (kept wired behind `hidden`) - #16 admin game seats: left-align numeric columns, clarify the 'Hints used' header |
||
|
|
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 |
||
|
|
645a503532 |
Stage 17 (#4): in-memory lobby cache — render instantly on the back-slide, refresh in background
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Successful in 9s
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 1m4s
|
||
|
|
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 |
||
|
|
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 |
||
|
|
635f2fd9fc |
Stage 17: backend defect fixes (nudge code, TG name, robot names/timing, multi-device push, move-duration metric + admin analytics)
- #3 nudge-on-own-turn: distinct result code nudge_own_turn + i18n (was reused 'not_your_turn') - #2 sanitize connector registration name to the editable format; Player/Игрок-XXXXX fallback - #5 variant-aware robot name pools (composed full/colloquial first + surname forms; ru gets <=20% latin) - #4 move-number-aware robot move timing (early 1-5min -> late 10-90min, skew k=4) - #7 emit move event to the actor too (multi-device sync); opponent_moved stays in-app only - #1 live game_move_duration{variant,phase} histogram + admin console per-user min/avg/max columns and an inline-SVG move-time-by-move-number chart (offline from the journal) - ProvisionRobot bypasses editor name validation (system names like 'Peter J.') |
||
|
|
e9f836db87 |
Stage 15: dual Telegram bots & language-gated variants
Tests · Go / test (push) Successful in 9s
Tests · Integration / integration (push) Successful in 10s
Tests · UI / test (push) Successful in 20s
Tests · Go / test (pull_request) Successful in 8s
Tests · Integration / integration (pull_request) Successful in 11s
Tests · UI / test (pull_request) Successful in 19s
Service-agnostic refinement of the owner's idea: the sign-in service returns a set of supported game languages with the user identity, and the lobby gates the New Game variant choice by it (en -> English; ru -> Russian + Эрудит). - Connector hosts two bots in one container (one per service language, each its own token + game channel; the same telegram_id spans both). ValidateInitData tries each token and returns the validating bot's service_language + supported_languages. Per-language config (TELEGRAM_BOT_TOKEN_EN/_RU, channels). - supported_languages rides the Session (fbs, session-scoped, not persisted); the UI offers only the matching variants on New Game — gating only the START of a new game (auto-match + friend invite), not accept/open/play; backend does not enforce. - service_language persisted (accounts.service_language, migration 00010, written every login, last-login-wins) and routes the user-facing Notify push back through the right bot (push-target coalesces with preferred_language). - Admin SendToUser/SendToGameChannel gain an operator-chosen language selector in the console (unrelated to ValidateInitData). - Non-Telegram logins carry the gateway default set (GATEWAY_DEFAULT_SUPPORTED_LANGUAGES, all variants). Wire (committed regen): ValidateInitDataResponse +service_language +supported_languages; Session +supported_languages; SendToUser/SendToGameChannel +language. Docs (ARCHITECTURE/FUNCTIONAL/_ru/READMEs) + PLAN updated; stage marked done. |
||
|
|
90eaf4964b |
Stage 13: alphabet on the wire (UI alphabet-agnostic, TODO-4)
Tests · Go / test (push) Successful in 10s
Tests · Integration / integration (push) Successful in 12s
Tests · UI / test (push) Successful in 19s
Tests · Go / test (pull_request) Successful in 9s
Tests · Integration / integration (pull_request) Successful in 12s
Tests · UI / test (pull_request) Successful in 19s
Live play now exchanges per-variant alphabet indices instead of concrete letters (rack out; submit-play, evaluate, exchange, word-check in). The client caches each variant's (index, letter, value) table behind StateRequest.include_alphabet and renders the rack and blank chooser from it, dropping the hardcoded value/alphabet tables. History, the durable journal and GCG stay decoded concrete characters (ARCHITECTURE §9.1, unchanged). - pkg/fbs: new AlphabetEntry + PlayTile; StateView.rack -> [ubyte] + alphabet; StateRequest.include_alphabet; SubmitPlay/Eval tiles -> [PlayTile]; Exchange tiles + CheckWord word -> [ubyte] (committed Go + TS regenerated). - engine: AlphabetTable + a cached per-variant codec (LetterForIndex/EncodeRack/ DecodeTiles/DecodeWord) + BlankIndex sentinel; Go parity test. - backend server edge maps index<->letter (new thin game.Service.GameVariant); game.Service domain methods, engine.Game and the robot keep one letter-based play path. The gateway forwards indices verbatim (no alphabet table). - ui: lib/alphabet.ts in-memory cache; codec encodes/decodes indices; premiums.ts is geometry-only; the mock seeds a fixture table; the UI normalises display to upper case (codec + cache), leaving placement/board/checkword unchanged. Parity moved to the Go engine.AlphabetTable test; premiums.ts loses its value tables. Discharges TODO-4. |
||
|
|
01485d8fc6 | Stage 11: account linking & merge (email + Telegram Login Widget) (#12) | ||
|
|
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. |
||
|
|
695508042a |
Stage 8: regression tests for the review-round refinements
Tests · Go / test (push) Successful in 6s
Tests · Integration / integration (push) Successful in 11s
Tests · UI / test (push) Successful in 18s
Tests · Go / test (pull_request) Successful in 6s
Tests · Integration / integration (pull_request) Successful in 12s
Tests · UI / test (pull_request) Successful in 18s
Lock the polish behaviours so a future edit surfaces as a failing test: - backend: UpdateProfile now rejects a bad name layout, an away window over 12h, and a malformed offset timezone (confirming it wires the Stage 8 validators); a new integration test accepts and resolves a "+03:00" offset timezone. - e2e (mock): the lobby notification badge count, the play-with-friends required game type + invitation send, the in-game add-to-friends flipping to a disabled "request sent", the profile-edit invalid-name Save guard, and the chat send/nudge icon buttons. |
||
|
|
b7d469a06e |
Stage 8 polish: keyboard-aware modals, consistent select pickers, required game type
Tests · UI / test (push) Successful in 17s
Third owner-review pass (iPhone): - Modals (and the chat) size their backdrop to window.visualViewport, so they stay fully above the software keyboard (dvh alone left the sheet partly behind it). - On the owner's call, every profile / new-game picker is a native <select> for consistent cross-platform behaviour: the away window returns to hour + 10-minute selects (which also avoids the iOS time-wheel "clear" button), alongside the offset timezone and the game-type / move-time / hints selects. Native time/wheel inputs render differently per OS and cannot be forced to match. - New-game "play with friends" has no preselected game type — an explicit, required pick (empty placeholder); Send invitation stays disabled until both a type and a friend are chosen. A smart default (from play history / language) is TODO-6. |
||
|
|
1d795e0acf |
Stage 8 polish: iPhone refinements (keyboard, native pickers, compact invite)
Tests · UI / test (push) Successful in 17s
Second owner-review pass (iPhone simulator): - Chat (and the modal) are sized in dvh so they shrink above the software keyboard, keeping the start of the conversation on screen instead of pushed off the top. - The profile away window returns to a native <input type="time" step="600"> (the iOS wheel with 10-minute steps) instead of separate dropdowns; the timezone stays a native offset <select>. - A finished game reserves the rack's height (min-height) so the footer no longer collapses when the final rack is empty — no layout jump versus an active game. - New-game "play with friends" is made compact: a searchable, bounded-scroll friend list, the game-type / move-time / hints controls as native selects in one row (labels above), and Send invitation pinned at the bottom — it scales to many friends. |
||
|
|
acbb2d8254 |
Stage 8 polish: profile validation, finished-game UI, badge + Safari fixes
Owner-review follow-up on the Stage 8 branch: - Friend code is copyable (📋 + toast). The lobby notification badge is fixed — it had inherited the hamburger-bar style — into a proper round count dot. - Safari: min-width:0 on flex text inputs (friend code, profile, chat) so they shrink instead of pushing the adjacent button off-screen. - Profile editing is validated on both the UI and the backend: display-name format (letters joined by single space/./_ separators, no leading/trailing/adjacent separators, <=32 runes), a UTC-offset timezone picker (account.ResolveZone parses ±HH:MM or a legacy IANA name), a 10-minute away grid capped at 12h (wrap-aware), and email format; Save is disabled and invalid fields red-bordered until valid. Language stays in Settings. - In a game, an "add to friends" menu item flips to a disabled "request sent"; chat send/nudge became ⬆️/🛎️ icon buttons. - A finished game drops its last-word highlight, hides Check word / Drop game, disables zoom, and draws an inert (greyed) footer instead of hiding it. Tests: account validators (name/away/zone), UI profileValidation, e2e for the finished-game footer/menu and the copy control. Docs (PLAN, ARCHITECTURE, FUNCTIONAL +ru, UI_DESIGN) updated for the display-name rule, UTC-offset timezone and the 12h away window. |
||
|
|
d733ce3119 |
Stage 8: UI social/account/history surfaces
Wire the deferred Stage 7 surfaces end-to-end (UI -> gateway transcode -> backend REST -> existing domain services): friends (incl. one-time friend codes), per-user blocks, friend-game invitations, profile editing + email binding, the statistics screen, and the in-game history + GCG export. Friends gain two add paths (interview decision, a deliberate plan change): one-time 6-digit codes (friend_codes table, 12h TTL, single-use, rate-limited redeem); and play-gated requests (shared game required) where an explicit decline is permanent, an ignored request lapses after 30 days, and a code bypasses a decline. Migration 00006 widens friendships_status_chk and adds friend_codes. Lobby notification badge is poll + push: a new generic `notify` event drives it live; the client polls on open/focus. Language stays a single Settings control that writes through to the durable account's preferred_language. GCG export is finished-only (game.ErrGameActive) and shares/downloads the .gcg file. Tests: backend unit + inttest (friend gate/decline/code, ListInvitations, GetStats, GCG gate), gateway transcode round-trips + notify constructor, UI vitest (codecs, win-rate, share choice) + Playwright social specs. Docs: PLAN (Stage 8 done + refinements + TODO-5), ARCHITECTURE, FUNCTIONAL(+ru), UI_DESIGN, TESTING, module READMEs. |
||
|
|
f8f7d39364 |
Stage 7: regression tests for the polished UI (logic + behaviour)
Lock the polish branch's behaviour so a future UI edit surfaces as a failing
assertion to re-agree or fix.
Unit (vitest, node env):
- placement: recallIndex, cellOccupied/isBlankSlot, non-linear direction, the
single-tile submit default, and placementFromHint blank-fallback / rack-exhausted.
- banner: the marquee scroll-cycle repeat-then-advance, stop(), root-relative and
multiple links.
- client.GatewayError. Extract the check-word constraints out of Game.svelte into a
pure lib/checkword.ts (sanitize + canCheck) and cover them.
E2E (playwright mock, Chromium + WebKit):
- commit via the 🏁 control, history slide-down + close, the exchange dialog,
check-word input sanitising + verdict, resign-to-finished, and the Settings
board-label mode changing the on-board labels.
|
||
|
|
4c475f2b0e |
Stage 7: run the e2e suite in WebKit too (Safari-engine coverage)
Add a webkit project to the Playwright config so the hermetic mock-mode specs run in both Chromium and WebKit, and install both browsers in CI. WebKit's Debian build runs headless without extra host system libraries (verified locally: smoke + zoom pass in webkit); the workflow comment records the one-time host install-deps fallback if a runner ever lacks a library. Desktop WebKit does not reproduce iOS Safari's text auto-inflation, so the app.css text-size-adjust guard stays outside e2e coverage. |