Commit Graph

72 Commits

Author SHA1 Message Date
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 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.
2026-06-09 01:48:20 +02:00
Ilia Denisov 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).
2026-06-08 23:23:05 +02:00
Ilia Denisov 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
2026-06-08 22:22:43 +02:00
Ilia Denisov 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
2026-06-08 22:10:44 +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 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.
2026-06-08 21:31:44 +02:00
Ilia Denisov 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.
2026-06-08 18:23:10 +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 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
2026-06-06 14:08:40 +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 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
Ilia Denisov 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.
2026-06-03 23:14:51 +02:00
Ilia Denisov 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.
2026-06-03 22:47:22 +02:00
Ilia Denisov acbb2d8254 Stage 8 polish: profile validation, finished-game UI, badge + Safari fixes
Tests · Go / test (push) Successful in 7s
Tests · Integration / integration (push) Successful in 11s
Tests · UI / test (push) Successful in 17s
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.
2026-06-03 22:12:59 +02:00
Ilia Denisov d733ce3119 Stage 8: UI social/account/history surfaces
Tests · Go / test (push) Successful in 7s
Tests · Integration / integration (push) Successful in 13s
Tests · UI / test (push) Successful in 16s
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.
2026-06-03 19:47:40 +02:00
Ilia Denisov 1e7da5925a Stage 7 UI polish: fix ad-marquee end, drop-time zoom, focus centring, sticky rack selection
Tests · UI / test (push) Successful in 11s
Tests · Go / test (pull_request) Successful in 6s
Tests · Integration / integration (pull_request) Successful in 12s
Tests · UI / test (pull_request) Successful in 11s
- AdBanner: move the side inset onto the scrolling track so the long message
  scrolls to its very end; pin body text-size-adjust:100% so iOS/Safari stops
  inflating the long marquee text.
- Game: do not zoom on drag start (the player may change their mind) — zoom and
  centre happen on drop, in attemptPlace; a stray tap on an occupied cell no
  longer cancels the rack selection (wait for an empty cell).
- Board: centre the focus cell after the zoom width transition finishes (was
  clamping to top-left mid-transition); compute the cell from the rendered
  scrollWidth.
2026-06-03 16:22:01 +02:00
Ilia Denisov 10d48884ac Stage 7 polish (round 3): zoom magnifies labels, popover edge-anchor, flash x2
Tests · UI / test (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 11s
- item 5: move container-type to the zoom-scaled .scaler so cqw labels grow WITH the board (magnifying-glass zoom); new e2e measures the font grows ~1.85x
- item 8: confirm popovers anchor to the trigger's right edge (no longer run off-screen)
- item 9: last-word flash runs 2 cycles then settles to normal (was infinite)
2026-06-03 15:52:28 +02:00
Ilia Denisov 3312130483 Stage 7 polish (round 2b): tab-bar spacing + hint badge on icon corner (items 2,3)
Tests · UI / test (push) Successful in 11s
Tests · Go / test (pull_request) Successful in 6s
Tests · Integration / integration (pull_request) Successful in 11s
Tests · UI / test (pull_request) Successful in 11s
2026-06-03 14:56:19 +02:00
Ilia Denisov 52a0e3160d Stage 7 polish (round 2): layout/zoom/tab-bar/hint/check fixes
Tests · UI / test (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 11s
- nav bar grows ONLY in game (other screens: minimal nav, content fills); tab bar always bottom
- tab bar: tighter icon/label spacing, bigger icons, hint badge on the icon corner
- board zoom reworked to width-based (real native scroll, fixes Safari/Chrome) + constant cqw labels; pinch & swipe-to-history dropped (conflict), double-tap kept, history via menu
- beginner bonus labels shrunk to fit cells
- Draw opens exchange directly (no confirm); confirm popovers restyled like the hamburger dropdown (vertical); removed the floating direction toggle
- pending tiles darker bg (no outline); last-word dark-tile highlight (static / 1s flash)
- check button disabled for <2/>15 chars, already-checked, or 5s cooldown
- global user-select:none (inputs exempt); docs updated; TODO-4 alphabet-on-wire
2026-06-03 14:54:41 +02:00
Ilia Denisov 38be7fea96 Stage 7 polish: app shell + nav + lobby + settings (Parts A/B/C)
- Screen.svelte shell: nav bar grows, ad+content+tabbar pinned bottom (mobile feel)
- AdBanner.svelte + banner.ts rotator (params, mock long/short, linkify); Header CSS chevron + grow; Menu (bigger CSS hamburger); TabBar + HoldConfirm shared components; user-select:none
- Lobby: hide-empty sections, tab order New/Tournaments/Stats, place-based result badges (result.ts)
- Settings: Board style > Labels (beginner/classic/none) + prefs plumbing (boardlabels.ts); i18n keys + ru mirror
2026-06-03 13:20:56 +02:00
Ilia Denisov 453ddc5e94 Stage 7 (wip): UI shell, libs, mock transport, screens (lobby->game), e2e smoke
- plain Svelte 5 + TS + Vite (no SvelteKit); CSS-token design system (Telegram-ready), hash router, IndexedDB session
- pure libs: domain model, premium/value maps ported from solver, board replay, placement state machine, i18n en/ru
- in-memory mock transport + seed data; pnpm start runs lobby->active game->board with no backend
- board: pointer-drag + tap placement, MakeMove (popup / 1s-hold commit), two-state zoom, blank chooser, exchange, hint, word-check, chat
- Playwright smoke (mock) green; svelte-check clean; mock bundle ~37 KB gzip
2026-06-03 00:32:50 +02:00