diff --git a/plans/reports/fullstack-developer-260921-0027-quick-match.md b/plans/reports/fullstack-developer-260921-0027-quick-match.md new file mode 100644 index 0000000..094dc02 --- /dev/null +++ b/plans/reports/fullstack-developer-260921-0027-quick-match.md @@ -0,0 +1,94 @@ +# Quick-match implementation report + +- Branch: `worktree-agent-ad9e4fa5c795f9e96` +- Worktree: `/workspace/tiennm99/noitu/.claude/worktrees/agent-ad9e4fa5c795f9e96` +- Status: complete + +## Setup note + +The worktree's branch was at `dd3b463`, five commits behind `dev` and missing +the proto commit (`865222d`) that adds `QuickMatch`/`CancelQuickMatch`. It had +zero commits of its own beyond that point, so `git merge --ff-only dev` +fast-forwarded it cleanly before any implementation work started. + +## Files touched + +Server (commit `4a78b8a`): +- `server/internal/wsapi/hub.go` — `waiting []*session` FIFO, `quickMatch`/ + `cancelQuickMatch` methods, `errAlreadyQueued`, queue cleared on `shutdown`. +- `server/internal/wsapi/room.go` — `createInput.autoStart`, `room.autoStart`, + dequeue calls in `handleCreate`/`handleStartBot`/`handleJoin`, auto-begin + logic in `handleJoin`. +- `server/internal/wsapi/session.go` — `QuickMatch`/`CancelQuickMatch` dispatch + cases, `cancelQuickMatch` on connection teardown. +- `server/internal/wsapi/codec.go` — `quickMatchStatusMsg`. +- `server/internal/wsapi/metrics.go` — `quickMatchQueued/Cancelled/Matched`. +- `server/internal/wsapi/wsapi_test.go` — `payloadCase` case, `testClient` + helpers `quickMatch()`/`cancelQuickMatch()`. +- `server/internal/wsapi/quick_match_test.go` (new) — 6 tests. + +Web (commit `e6f2d19`): +- `web/src/lib/ws/messages.js` — `quickMatch()`/`cancelQuickMatch()` builders. +- `web/src/lib/stores/game.svelte.js` — `state.queued`, `quickMatchStatus` + case, clears on `roomState`. +- `web/src/lib/i18n/vi.js` — quick-match strings, `already_in_a_room` / + `already_queued` error codes, one added sentence in `rulesRoomBody`. +- `web/src/routes/online/+page.svelte` — "Chơi ngay" button, waiting panel + (elapsed time, cancel, 20s bot nudge), cancel-on-leave. +- `web/tests/game-store.test.js`, `web/tests/ws-client.test.js` — new tests. +- `README.md` — one sentence in "Online play". + +## Design choices + +- Queue is a plain `[]*session` guarded by the hub's existing mutex — the + pool is small (brainstormer report B1), no key/skill needed. +- Reused `createInput`/`joinInput`/`beginGame` exactly as instructed. Added + one field, `createInput.autoStart`, carried into a room-goroutine-only + `room.autoStart` field; `handleJoin` broadcasts `RoomState` (both seats) + itself before calling `beginGame`, then clears the flag, so message order + is: `QuickMatchStatus{false}` (sent synchronously from `hub.quickMatch`, + before either room input is even sent) → `RoomState` → `GameStarted`. +- `hub.quickMatch` sends both `QuickMatchStatus` messages itself (to the + caller and to the popped waiter) before touching the room, which is what + guarantees neither status message can race a `RoomState`/`GameStarted` the + room produces afterwards — no separate synchronization needed, only + program order plus the channel send/receive happens-before edge. +- `roomLimiter` is charged once, for the caller of `QuickMatch`, mirroring + `CreateRoom`; the popped waiter (already charged when it first queued) is + not charged again. +- Dequeue on "enters a room another way" is hooked into `room.go`'s + `handleCreate`/`handleStartBot`/`handleJoin` (the actual seating point), + not into `session.go`'s pre-checks — so a refused attempt (rate limit, full + room) does not silently drop a real wait. +- `hub.shutdown` needed no new code: a queued session is already registered + in `h.sessions` (Hello ran before `QuickMatch` could), so the existing + broadcast to every session already reaches it; the queue slice is cleared + for tidiness. + +## Verification + +``` +cd server && gofmt -l . && go vet ./... && golangci-lint run ./... && go test ./... -race -count=1 +→ 0 issues; all packages ok (wsapi 30.3s, includes TestGoroutinesReturnToBaseline) + +cd web && npm run lint && npm run check && npm test +→ lint: 0 errors, 29 pre-existing `any`-type warnings (untouched files) +→ check: 380 files, 0 errors, 0 warnings +→ test: 12 files, 202 tests passed +``` + +## Deferred / not touched + +- `ClaimDeadEnd`, `ReportWord`, `PlayedWord.parts`, `MoveRejected.suggestion` + — the sibling agent's arms, per instructions. +- No changes to `proto/`, `server/gen`, `web/src/lib/proto` — generated code + untouched as instructed. +- Did not add a `queuedSinceMs` server-clock-synced timer; the waiting + panel's elapsed time is a client-side `setInterval`, consistent with it + being a "still looking" indicator rather than a deadline. + +## Unresolved questions + +None. Behaviour matches the spec's description of refusal codes, ordering, +idempotency, and the room-cap edge case, all covered by +`quick_match_test.go`.