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.
This commit is contained in:
Ilia Denisov
2026-06-10 16:56:03 +02:00
parent a372343797
commit 8881214213
156 changed files with 749 additions and 778 deletions
+2 -2
View File
@@ -94,7 +94,7 @@
return () => cancelAnimationFrame(raf);
});
// Pinch zoom (Stage 17): a two-finger spread zooms in toward the pinch midpoint, a pinch
// Pinch zoom: a two-finger spread zooms in toward the pinch midpoint, a pinch
// close zooms out. preventDefault fires only for two touches, so the one-finger native
// scroll of the zoomed board is left untouched. It maps to the same two-state zoom as
// double-tap, toggling toward the midpoint cell.
@@ -278,7 +278,7 @@
.cell.pending {
background: var(--tile-pending);
/* The placed tile owns the pointer so it can be dragged to relocate it (even on the zoomed
board) instead of the touch starting a board pan (Stage 17). */
board) instead of the touch starting a board pan. */
touch-action: none;
}
/* Lines-off variant: a gapless checkerboard. The 1px grid gaps (and the cell-line they
+2 -2
View File
@@ -15,7 +15,7 @@
messages: ChatMessage[];
myId: string;
busy: boolean;
// Chat and nudge are mutually exclusive by turn (Stage 17): on the player's own turn the
// Chat and nudge are mutually exclusive by turn: on the player's own turn the
// message field + send are shown (and nudging makes no sense — there is no one to
// hurry); on the opponent's turn only the nudge button shows. While the hourly nudge
// cooldown is active the nudge is disabled with an "awaiting reply" caption.
@@ -72,7 +72,7 @@
gap: 10px;
/* Fill the chat screen; the list scrolls and the input pins to the bottom. The screen
fits the visual viewport (--vvh), so an open keyboard simply shrinks it and the input
stays visible — no modal relayout, no page jump (Stage 17). */
stays visible — no modal relayout, no page jump. */
flex: 1;
min-height: 0;
padding: 10px var(--pad);
+1 -1
View File
@@ -7,7 +7,7 @@
import { t } from '../lib/i18n/index.svelte';
import type { ChatMessage, StateView } from '../lib/model';
// The chat is its own screen (Stage 17), so the soft keyboard simply resizes the viewport with
// The chat is its own screen, so the soft keyboard simply resizes the viewport with
// the input pinned to the bottom — no modal relayout jank. It loads the game state (for the
// turn-based chat/nudge toggle) and the message list, and clears the unread badge while open.
let { id }: { id: string } = $props();
+1 -1
View File
@@ -8,7 +8,7 @@
import { canCheckWord, sanitizeCheckWord } from '../lib/checkword';
import type { Variant } from '../lib/model';
// Word-check on its own screen (Stage 17): unlimited dictionary lookups, each with a
// Word-check on its own screen: unlimited dictionary lookups, each with a
// complaint, off the board so the soft keyboard never relayouts the play area.
let { id }: { id: string } = $props();
+14 -14
View File
@@ -112,7 +112,7 @@
}
let draftSaveTimer: ReturnType<typeof setTimeout> | null = null;
// scheduleDraftSave persists the composition (rack order + pending tiles) after a short
// debounce; best-effort, so a failed save never interrupts play (Stage 17).
// debounce; best-effort, so a failed save never interrupts play.
function scheduleDraftSave() {
if (draftSaveTimer) clearTimeout(draftSaveTimer);
draftSaveTimer = setTimeout(() => {
@@ -178,7 +178,7 @@
if (!e) return;
if (e.kind === 'opponent_moved' && e.gameId === id) {
// While composing, reload so a draft overlapping the new move is reconciled; otherwise apply
// the move as a delta with no fetch (R4).
// the move as a delta with no fetch.
if (placement.pending.length > 0) void load();
else applyDelta(applyMoveDelta(cacheSnapshot(), { move: e.move, game: e.game, bagLen: e.bagLen }));
} else if (e.kind === 'your_turn' && e.gameId === id) {
@@ -208,15 +208,15 @@
let hoverKey = '';
let hoverTimer: ReturnType<typeof setTimeout> | null = null;
// The empty board cell the dragged tile is currently aimed at, highlighted as a drop
// target while carrying a tile over the board (Stage 17). Null over an occupied cell.
// target while carrying a tile over the board. Null over an occupied cell.
let dropTarget = $state<{ row: number; col: number } | null>(null);
// Rack reordering (Stage 17): while a rack tile is dragged, reorderDragId is its stable id
// Rack reordering: while a rack tile is dragged, reorderDragId is its stable id
// (so the rack hides it — the ghost stands in) and reorderTo is the drop slot over the rack
// (a gap opens there). Only when no tiles are pending, so the order is a clean permutation.
let reorderDragId = $state<number | null>(null);
let reorderTo = $state<number | null>(null);
// While a placed (pending) board tile is dragged to relocate it, draggingPend is its cell —
// hidden from the board (the ghost stands in) like a lifted rack tile (Stage 17).
// hidden from the board (the ghost stands in) like a lifted rack tile.
let draggingPend = $state<{ row: number; col: number } | null>(null);
let dragPointerId = -1;
@@ -245,7 +245,7 @@
drag = null;
}
function onRackDown(e: PointerEvent, index: number) {
// Tiles may be arranged on the opponent's turn too (Stage 17 #5): only placement is
// Tiles may be arranged on the opponent's turn too: only placement is
// relaxed — the preview and Make-move stay your-turn-only, so an off-turn draft is
// position-only (never scored or submitted).
if (busy || gameOver) return;
@@ -390,7 +390,7 @@
window.removeEventListener('pointerdown', onExtraPointer);
clearHover();
clearReorder();
// Flush a pending draft save so leaving mid-composition still persists it (Stage 17).
// Flush a pending draft save so leaving mid-composition still persists it.
if (draftSaveTimer) {
clearTimeout(draftSaveTimer);
void gateway.draftSave(id, serializeDraft(rackIds, placement.pending)).catch(() => {});
@@ -401,7 +401,7 @@
function onCell(row: number, col: number) {
if (swallowClick) return;
// A pending tile is recalled by a double-tap or by dragging it back to the rack, not
// by a single tap (which recalled too easily — Stage 17).
// by a single tap (which recalled too easily).
if (pendingMap.has(`${row},${col}`)) return;
if (selected != null) {
// A committed tile already sits here: keep the rack selection so a stray tap
@@ -418,7 +418,7 @@
scheduleDraftSave();
}
// relocatePending moves a placed-but-unsubmitted tile from one board cell to another free one
// (a board→board drag), keeping its rack slot and any blank letter (Stage 17).
// (a board→board drag), keeping its rack slot and any blank letter.
function relocatePending(fromRow: number, fromCol: number, toRow: number, toCol: number) {
const pt = placement.pending.find((p) => p.row === fromRow && p.col === fromCol);
if (!pt) return;
@@ -459,7 +459,7 @@
function recompute() {
preview = null;
if (previewTimer) clearTimeout(previewTimer);
// Off-turn the composition is position-only: no score preview or evaluate (Stage 17 #5).
// Off-turn the composition is position-only: no score preview or evaluate.
if (!isMyTurn) return;
const sub = toSubmit(placement, dirOverride);
if (!sub) return;
@@ -473,7 +473,7 @@
}
// applyMoveResult renders the actor's own just-committed move from the response — the move, the
// post-move game and the refilled rack — without a follow-up game.state + game.history (R4).
// post-move game and the refilled rack — without a follow-up game.state + game.history.
function applyMoveResult(r: MoveResult) {
view = { game: r.game, seat: r.move.player, rack: r.rack, bagLen: r.bagLen, hintsRemaining: view?.hintsRemaining ?? 0 };
moves = [...moves, r.move];
@@ -613,7 +613,7 @@
}
// Friend state for the in-game "add to friends" item, derived from the server so it is
// correct across reloads and live-updates when a request is answered (Stage 17):
// correct across reloads and live-updates when a request is answered:
// `friends` are the caller's accepted friends; `requested` are the addressees already
// requested (pending or declined — both block a re-send and read as "request sent").
let friends = $state(new Set<string>());
@@ -985,7 +985,7 @@
min-width: 0;
}
/* A borderless icon button (like the tab bar), not a filled accent button — and disabled
while the pending word is known to be illegal (Stage 17). */
while the pending word is known to be illegal. */
.make {
min-width: 56px;
background: none;
@@ -1045,7 +1045,7 @@
pointer-events: none;
z-index: 60;
}
/* On touch the finger covers the tile, so enlarge the drag ghost ~1.5x (Stage 17). */
/* On touch the finger covers the tile, so enlarge the drag ghost ~1.5x. */
.ghost.touch {
transform: translate(-50%, -50%) scale(1.5);
}
+2 -2
View File
@@ -20,7 +20,7 @@
selected: number | null;
shuffling?: boolean;
// While a rack tile is being dragged to reorder it, draggingId is its id (hidden here —
// the drag ghost stands in) and dropIndex is the slot where a gap opens (Stage 17).
// the drag ghost stands in) and dropIndex is the slot where a gap opens.
draggingId?: number | null;
dropIndex?: number | null;
ondown: (e: PointerEvent, index: number) => void;
@@ -93,7 +93,7 @@
user-select: none;
-webkit-user-select: none;
/* iOS shows a tap/active highlight that can linger on the neighbour sliding into a
dragged tile's slot (Stage 17); suppress it so only our own styles mark a tile. */
dragged tile's slot; suppress it so only our own styles mark a tile. */
-webkit-tap-highlight-color: transparent;
}
.tile.selected {