Replace the very-narrow 24px edge-swipe trigger with a left band of ~20% of the
viewport width (EDGE_FRACTION) so the back-swipe is easy to reach, keeping the
simple instant-navigate behaviour (the route slide plays the animation). A
hit-test keeps the wider band clear of the rack, a draggable pending tile, a
zoomed-in board and text inputs, so it never hijacks those drags.
Test: e2e (Chromium+WebKit) — the band swipe returns to the lobby; a swipe
starting on the rack does not navigate.
Replace Menu.svelte (hamburger) everywhere with tab-bar navigation:
- Settings hub (SettingsHub) from the lobby ⚙️ tab: Settings/Profile/
Friends/About as in-place tabs, back → lobby; the lobby ⚙️ badge counts
incoming friend requests (invitations keep their own lobby section).
- Comms hub (CommsHub) from the move-history 💬: Chat/Dictionary tabs,
back → game; Dictionary only while the game is active.
- Game menu items relocate into the open history: 🏁 leave / 📤 export in
the header, 🤝 add-friend per opponent card, 💬 comms; unread chat is
badged on the score bar + the 💬.
- TapConfirm (tap → fading ✅ → tap) replaces the Skip/Hint press-and-hold
popovers and drives the add-friend confirm.
- Fix the move-history "jump": the slid board is inert and the stage can't
scroll, so a swipe up genuinely closes the history.
Remove Menu.svelte + HoldConfirm.svelte. Docs: UI_DESIGN, FUNCTIONAL(+ru),
PRERELEASE. UI check/unit/build/bundle/e2e (Chromium+WebKit) all green.
- 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).
- 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.)
- 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.
- 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
- 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