27 Commits
Author SHA1 Message Date
tiennm99 0cb1b74952 docs(reports): record the whole-project review and fixes 2026-09-29 20:33:16 +07:00
tiennm99 d3eb13e36e docs(reports): record the dev branch review and refactor 2026-09-28 15:19:59 +07:00
tiennm99 80ef632598 Merge branch 'worktree-agent-a4990d1e576b04b80' into dev 2026-09-21 17:02:02 +07:00
tiennm99 007f50cbba docs(reports): record the whole-game UX pass implementation 2026-09-21 17:01:24 +07:00
tiennm99 85199df079 Merge branch 'worktree-agent-a38a77d87cb0d7781' into dev 2026-09-21 16:37:00 +07:00
tiennm99 fbb06ad100 docs(reports): record the server review implementation
Branch base, per-deliverable changes, verification tail, deferred
items and unresolved questions for today's server architecture review.
2026-09-21 16:35:49 +07:00
tiennm99 8c5bb8bef4 Merge branch 'worktree-agent-ae280f2081bd718f6' into dev 2026-09-21 16:32:13 +07:00
tiennm99 df2ad83770 docs(reports): record the web review actions implementation
Also keep a copy of the architecture review this work implements, alongside
the report — it was untracked in the checkout this branch forked from.
2026-09-21 16:31:02 +07:00
tiennm99 c4502f2793 refactor(web): split the game store into shape, apply and store files
Type GameState for real instead of returning any from initialState(), which
made every game.state.* read in every component unchecked. apply() now
switches on payload.case so the oneof narrows, and is wrapped in try/catch so
a throw partway through a case cannot leave a half-mutated snapshot on
screen. Also key chain meanings by position instead of gloss, since the
dictionary gives no gloss-uniqueness guarantee, and gate the word field's
turn-seed effect on the same connection check `enabled` already uses so a
reconnect cannot seed a field the player cannot submit from.
2026-09-21 16:03:03 +07:00
tiennm99 e4f9917312 docs(plans): whole-project server, web and UX reviews 2026-09-21 16:01:54 +07:00
tiennm99 1358c3d136 fix: close the review findings on the improvement branch
Chat lines are keyed by a client ordinal rather than author plus
timestamp, which one seat sending a burst could duplicate within a
millisecond and turn into a Svelte runtime error in every tab. The nginx
snippet now sets X-Forwarded-For, without which naming that proxy as
trusted would let each client pick its own limiter key. Quick-match skips
a waiter whose connection has already ended instead of seating a ghost,
and a match the server could not open clears the waiting panel. A
near-miss suggestion is withheld when the word would not link or is
already used, so taking it cannot earn a second refusal. The builder's
delete-then-rename fallback is confined to Windows, where the rename over
an existing file fails; elsewhere a failed rename is reported, not made
worse by deleting the good database.
2026-09-21 02:20:16 +07:00
tiennm99 6a7cb34897 Merge branch 'worktree-agent-a582df720cc6e3dea' into dev
# Conflicts:
#	server/internal/wsapi/wsapi_test.go
#	web/src/lib/ws/messages.js
#	web/src/routes/online/+page.svelte
2026-09-21 01:58:02 +07:00
tiennm99 2adab1318b docs(plans): record the in-game feedback implementation report 2026-09-21 01:55:46 +07:00
tiennm99 36915a67bd docs(reports): record the quick-match implementation 2026-09-21 01:40:23 +07:00
tiennm99 8223f40b16 feat(server): counters, readiness, drain mode and a version stamp
expvar counters for connections, rooms, games, submissions by rejection
reason, eliminations, chat, joins and bot moves, served on a separate
debug address so they never sit on the public mux, plus one structured
word_rejected log line per refused word carrying the normalized word and
its link. GET /readyz flips to 503 while draining; SIGTERM stops new
rooms, waits up to NOITU_DRAIN_TIMEOUT for live games, then shuts down.
GET /version and the startup log carry the build's git describe.

Fuzz targets for the frame decoder, the text sanitizer and Vietnamese
normalization; the last one found that composing before lowercasing
could leave a non-NFC result, now recomposed after lowering.

CI runs on dev as well as main, gates gofmt and golangci-lint, tracks the
buf major instead of an exact pin, and dependabot watches every
ecosystem. The lint findings that had been hidden by the default
per-issue cap are fixed.
2026-09-21 01:17:52 +07:00
tiennm99 90cd679639 feat(web): state the rules, reach the chat from the top of the board, lint the tree
A /rules page says, in one place, what nothing in the app said before:
the chain rule, the clock, what a dead end costs, elimination and the last
player standing, how a word is scored and the reconnect window. Linked
from the landing page and from the board and lobby headers.

The chat control moves above the chain as a pill with the unread count,
where it can be reached on a phone mid-game, and the lobby's folded chat
now carries an unread badge too.

ESLint with the Svelte and JSDoc plugins, run in CI; the real findings
it turned up (missing each keys, untyped timer handles) are fixed.
2026-09-21 01:17:52 +07:00
tiennm99 a5052fca3f docs(plans): research, brainstorm and health-scan reports with synthesis brief 2026-09-21 00:30:10 +07:00
tiennm99 b65b4eade1 docs(reports): record the UI/UX review of the game's layout and comfort
Three read-only reviews, one per surface: the solo turn loop, the online
room, and the global layout with its tokens and accessibility.

They are stateful records of what was found on 2026-09-10, not evergreen
authority — the contrast tables and the drift inventory describe the
palette as it was measured, before the fixes that follow.
2026-09-10 13:03:52 +07:00
tiennm99 2f893c9877 docs(reports): record why Hoàng Phê cannot be shipped as data 2026-09-10 09:51:58 +07:00
tiennm99 3ba99ec107 docs(reports): record the codebase cleanup review 2026-09-08 23:10:44 +07:00
tiennm99 f00d0ef974 docs(plans): record the dump corpus plan, measurement, review and journal 2026-09-08 22:48:26 +07:00
tiennm99 10a47ebc76 docs(plans): record the dictionary corpus switch, its audit and the source research 2026-09-08 16:37:45 +07:00
tiennm99 1c32cfa31a docs(plans): survey viwiktionary and undertheseanlp as dictionary sources
Measured against the shipped corpus rather than estimated: viwiktionary is
already a third of what we ship, and undertheseanlp is bigger and denser but
carries an unlicensed Vietlex derivative in one of its three branches.
2026-09-08 15:36:48 +07:00
tiennm99 a72af1f0ad docs(plans): record the survey of Vietnamese dictionary sources 2026-09-08 13:07:49 +07:00
tiennm99 64106735ec feat(web): add SvelteKit vs-bot frontend
The board, the home screen and the game-over panel, in Vietnamese, served by
the Go binary as a static single-page app.

The store is a reducer over ServerMessage and computes nothing. Validity, turn
order, scores and the result are read from the wire, which is what lets one
screen serve the bot now and online play later. Every Vietnamese string lives in
one module, including the map from RejectReason to a message, so the server can
send UI keys instead of prose.

The word field is uncontrolled. A Telex or VNI input method composes a diacritic
across several keystrokes, and writing the value back on each one cancels the
composition. It is read on submit and cleared only there.

The countdown is drawn against the server's clock, estimated from the ping round
trip, and settles 300ms early so the ring never claims more time than the server
allows.

The screen owns the socket and the game while it is mounted, and gives the
current game up on the way out. Asking for a game is stored intent rather than a
condition inferred from the board being empty: clearing the board for a rematch
is that same condition, so the inference sent a second StartBotGame and the
server built two rooms that then destroyed each other.

A test greps the built bundle for dictionary words and holds a size budget, so
the wordlist cannot reach the browser unnoticed. Another reads the error codes
out of the Go transport and fails when one has no Vietnamese message.
2026-09-05 12:55:02 +07:00
tiennm99 48b3b3c8ac feat(proto): add protobuf wire contract and cross-language codegen
proto/noitu/v1/game.proto is the single source of truth for every WebSocket
message. buf generates Go types into server/gen and JavaScript types into
web/src/lib/proto; both trees are committed so building needs no codegen
toolchain.

The Go suite emits binary fixtures into proto/testdata and the JavaScript suite
decodes the same bytes, so the two generated clients are checked against one
artifact rather than against each other's assumptions. CI lints the schema,
rejects breaking changes against main, and fails when the committed generated
trees drift from the schema.

game.NumRejectReasons and game.NumEndReasons let the mapping tests prove every
engine reason has a wire value without guessing where the enum ends.
2026-09-05 12:06:58 +07:00
tiennm99 582ba27354 docs: add noi tu web game plan and dictionary research
Research the Vietnamese noi tu word-chain game and plan a 7-phase web
implementation: SvelteKit frontend, Go backend, WebSocket transport with
Protobuf framing, and a server-authoritative dictionary over SQLite.

The server validates every move so the browser never holds the wordlist,
which keeps player-vs-player cheat-resistant and lets the bot and PvP
modes share one rule implementation.

Records the validated decisions: words of two or more syllables linking
on first and last syllable, a 20s turn limit, user-typed nicknames, and
Docker deployment behind a reverse proxy.
2026-09-04 16:25:29 +07:00