Both reports belonged to shipped work:
- docs-manager-260410-1344: docs sweep, superseded by docs/ updates in cbad690
- researcher-260410-2132: pre-2026 version audit, superseded by the pinned
deps in server/build.gradle.kts and client/package.json
All 5 plans implemented and merged. Deleting to keep the plans/ directory
focused on active work:
- 260409-1701-caro-simplification (shipped d871cc2)
- 260409-1812-web-gomoku-client (shipped 77e141c, later replaced by Phaser)
- 260410-0913-phaser-web-client (shipped 22bb9c1)
- 260410-1843-refactor-project-structure (shipped c71aa6a/5b68ee9/2d74117/1297b7d)
- 260410-2101-websocket-protobuf-migration (shipped 945a249 through 42d94a2)
plans/reports/ kept for historical cross-plan reports.
Watch feature was half-implemented since day one and stayed broken through
the typed-protobuf migration:
1. Server (WatchGameHandler): only pushed the WatchGameSuccessResponse ack.
A mid-game watcher joined a blank scene because the server never told
them who the players were or what moves had been played.
2. Client: had a Watch button that sent the request but no event bus
handler for GAME_WATCH_SUCCESSFUL, so nothing happened visually.
Server fix:
- On successful watch, if room.status == STARTING, bootstrap the watcher
with a synthesised GameStartingResponse (player ids, nicknames, board
size) and replay every move in room.getMoveHistory() as individual
GameMoveSuccessResponse messages on that channel.
- Black/white player lookup uses room.getBlackPlayerId/getWhitePlayerId
against the clientSideMap so we don't reassign roles.
- Move replay resolves playerNickname from the same map.
Client fix:
- menu-ui.js: new GAME_WATCH_SUCCESSFUL handler flips gameState.isSpectating
= true. The subsequent GameStartingResponse flows through the existing
menu-scene handler (transitions to GameScene) and the existing
game-state-service handler (populates room state, resets moves).
- Move replay events propagate through the global GAME_MOVE_SUCCESS handler
in game-state-service before GameScene.create() runs, so GameScene's
existing rejoin/spectate loop at create() renders every stone.
After the typed-protobuf migration the event bus delivers the full
ShowRoomsResponse message to the handler — a {rooms: [...]} object, not
the raw array the old JSON envelope used to unwrap. showRoomList was
still doing `Array.isArray(rooms) ? rooms : []`, so the guard always
failed and the UI rendered "No rooms available" even when rooms existed
server-side.
Repro: create a PVP room from tab A, open Join Room from tab B — empty list.
Fix: read payload.rooms (defensively default to [] so the empty-state row
still shows when protobufjs strips the empty repeated field).
docker build failed with "/public: not found" at the client stage. The
client tree has never contained a public/ directory (vite doesn't need one
for this project — index.html lives at the client root and vite.config.js
has no publicDir override). Remove the dead COPY line.
Verified: docker build -f client/Dockerfile client succeeds end-to-end.
Last Build & Test run failed with exit 127 "./gradlew: No such file or
directory". After the Maven-to-Gradle conversion the wrapper lives at
server/gradlew, not at the repo root. Two fixes:
1. Workflow invokes server/gradlew directly instead of ./gradlew.
2. Mark server/gradlew as executable (mode 100755) in the git index so the
Linux runner can exec it without a chmod step.
Phase 04 of the WebSocket protobuf migration:
- Add protobufjs@7.5.4 runtime dep and protobufjs-cli@1.1.3 devDep
- Add proto:gen npm script that runs pbjs + pbts against server/src/main/proto/
- Commit static-module output at client/src/generated/protocol.{js,d.ts}
- Rewrite connection-service.js:
* Set binaryType='arraybuffer' and default URL to ws://localhost:1999/ratel
* Replace string-keyed send(code, data) with 13 typed send helpers
(sendNickname, sendGameMove, sendCreatePveRoom, ...)
* Decode incoming BinaryWebSocketFrame via Response.decode and map
each oneof case to a ClientEventCode for the event bus
- Update 14 call sites across menu-ui, game-ui, game-scene
- game-state-service CLIENT_CONNECT now reads data.clientId
- menu-ui NICKNAME_SET toast only triggers when invalidLength > 0
- Drop ServerEventCode enum from protocol-constants.js
Phase 03 cleanup — now that typed records replace JSON envelopes and inner
JSON payloads, remove the helpers that served them:
- delete common/entity/Msg.java (envelope record)
- delete common/utils/JsonUtils.java (gson wrapper)
- delete common/helper/MapHelper.java (JSON dict builder)
- delete common/helper/TimeHelper.java (orphaned)
- delete common/transfer/{ByteKit,ByteLink,TransferProtocolUtils}.java (TCP framing)
- delete common/handler/DefaultDecoder.java (TCP framing)
- delete common/enums/ServerEventCode.java (string keys for reflection dispatch)
- delete common/enums/ClientEventCode.java (no longer referenced after proto migration)
- drop com.google.code.gson:gson from build.gradle.kts
All 37 unit tests still pass.
Replace every UnsupportedOperationException stub in RequestDispatcher with a
real handler call. All 15 handlers now live under com.miti99.caro.server.event.handler
and take a typed ClientRequest record, emitting typed Response protos via
ChannelUtils.push.
Handlers ported:
- SetClientInfoHandler, SetNicknameHandler
- CreateRoomHandler, CreatePveRoomHandler, GetRoomsHandler, JoinRoomHandler
- GameStartingHandler, GameReadyHandler
- GameMoveHandler (full PVP + PVE AI + game-over broadcast)
- GameResetHandler (noop, never wired before)
- WatchGameHandler, WatchGameExitHandler
- ClientExitHandler, ClientOfflineHandler
WebsocketTransferHandler.clientOfflineEvent now dispatches to ClientOfflineHandler.
RoomClearTask reuses ClientExitHandler for stale-room cleanup.
All 37 unit tests pass.
7-phase plan to drop TCP, replace JSON envelope with typed protobuf
Request/Response oneofs on single port 1999, and dispatch via Java
sealed records. Includes pre-2026 dep version audit (netty, junit,
shadow, protobufjs).
Version change:
- Drop the "-beta" suffix across all version declarations.
- server/build.gradle.kts: version = "0.0.1"
- client/package.json: "version": "0.0.1"
- client/package-lock.json: regenerated
- server/Dockerfile: COPY path references the new jar filename
- All docs + README refer to caro-server-0.0.1.jar.
Terminology cleanup:
- Replace the word "frontend" with "client" so the whole project uses
one consistent term (server / client).
- README.md Credits section: "Frontend build tool" -> "Client build tool".
- No other "frontend" occurrences found in code or docs.
Also correct two lingering Maven-era stale paths in deployment-guide.md
("server/target/..." -> Gradle output location) that slipped past the
earlier Maven-to-Gradle commit.
Versioning note in codebase-summary.md simplified to plain MAJOR.MINOR.PATCH
(dropped the "-beta suffix during pre-1.0" clause).
Validation: gradlew clean shadowJar + test passes (37 tests on Java 25);
client build succeeds with new package name/version.
The PDR suffix was unclear (Product Design Requirements). Plain
project-overview.md matches the file's contents better and aligns
with the naming of the other docs.
Also update the directory tree in codebase-summary.md.
- Delete docs/project-roadmap.md (no longer maintained).
- project-overview-pdr.md: drop Roadmap & Status table; tweak PVP
feature description (remove non-existent chat); update Success
Criteria to reference CI instead of CI/CD with deploy.
- codebase-summary.md: drop project-roadmap.md from directory tree.
- system-architecture.md: simplify static handler note to describe
current pipeline state instead of refactor history.
Modernization (opportunistic, low-risk only):
- Convert Msg (WebSocket JSON envelope) from mutable POJO to record.
Gson 2.11 natively supports record serialization via canonical
constructor + accessor methods, so wire format is preserved
(null components still skipped by default). Update both producers
(ChannelUtils) and consumer (WebsocketTransferHandler).
- Convert 3 switch statements to switch expressions:
GomokuHelper.getWinnerMessage (GameResult -> String, exhaustive),
GomokuHelper board-cell rendering (PieceType -> char),
GomokuAI.getNextMove (difficulty -> strategy),
ServerEventListener_CODE_ROOM_CREATE_PVE.getDifficultyName.
- Sprinkle var for obvious local types in ChannelUtils and
WebsocketTransferHandler where RHS type is self-evident.
Non-goals preserved: no Netty handler rewrites, no threading changes,
no sealed types, no pattern matching in switches.
Validation: mvn verify on Java 25 — all 37 tests pass.
- Move all 11 shared sub-packages (channel, entity, enums, exception,
features, handler, helper, print, robot, transfer, utils) under
com.miti99.caro.common.
- Move server sub-packages (event, handler, proxy, timer) + SimpleServer
+ ServerContains under com.miti99.caro.server.
- Move tests under com.miti99.caro.common.{helper,robot}.tests.
- Rewrite package declarations and imports across all 58 .java files via
regex script (server rules applied before common to avoid overlap).
- Update <mainClass> in server/pom.xml to com.miti99.caro.server.SimpleServer.
- Update .proto files' package + java_package to com.miti99.caro.common.entity
(for future regeneration).
- Fix generate.sh relative output path (common/ no longer exists).
- Include rewrite-packages.py script under plans/ for auditability.
Note: protoc-generated ClientTransferData.java / ServerTransferData.java
retain internal_static_* variable names and embedded descriptor byte strings
with the old package — these are implementation details that do not affect
the public Java package and preserve protobuf wire compatibility.
Validation: mvn -f server/pom.xml clean verify on Java 25 — all 37 tests
pass (29 GomokuHelperTest + 8 GomokuAITest).
6-phase serial plan: remove CLI client + static UI, merge common into
server, collapse to standalone Maven (Java 25, shade, gson, JUnit 5),
rename to com.miti99.caro.{common,server}, rename web-client to client.
- Replace outdated Build.yml (v2 actions, JDK 1.8, 3-OS matrix)
with modern workflow (v4 actions, JDK 21, single ubuntu runner)
- Add separate web client build job
- Add deploy-pages.yml for auto-deploying web-client to GitHub Pages
on push to master (web-client/** changes only)
- Set Vite base path to /caro/ for GitHub Pages compatibility
Separate web-client/ directory with Phaser 3 + Vite + vanilla JS:
- Services: event bus, WebSocket connection (heartbeat, reconnect),
game state, protocol constants matching server enums
- Scenes: BootScene (connect), MenuScene (DOM overlay), GameScene
(canvas board with grid, stones, hover, click-to-move, animations)
- Objects: Board (wood grid, star points, labels), Stone (gradient
circles with drop tween animation)
- UI: DOM overlays for nickname, lobby, PVP/PVE menus, room list,
waiting room, game HUD, game over, toast notifications
- Audio: Web Audio API stone click + win/lose tones
- Full game flow: nickname → lobby → create/join/PVE → play → game over
Standalone Phaser 3 + Vite + vanilla JS (JSDoc) Gomoku web client.
Connects to existing server via WebSocket. DOM overlays for menus,
Canvas for board. 6 phases, ~12h effort.
- Add StaticFileHandler to serve static files from Netty WS server
- Create single-page HTML with 8 screens (nickname, lobby, PVP/PVE
menus, room list, waiting room, game, game over)
- Dark theme CSS with responsive layout and animations
- WebSocket connection with heartbeat and auto-reconnect toast
- Event bus state machine for screen transitions
- Canvas board: wood texture, grid, gradient stones, hover preview,
last-move indicator, placement animation (easeOutBack)
- Full lobby: create/join rooms, room list, spectator mode
- Move history panel with coordinate display
- Game over with personalized win/lose/draw result
- Web Audio API sound effects (no external files needed)
- Toast notification system for errors
- Remove unused currentPlayer field from Room
- Remove unused RobotEventListener interface
- Remove RobotDecisionMakers.init() calls from proxy classes
- Delete dead RegxUtils class
- Fix poker javadoc in TransferProtocolUtils
- Remove Chinese date in TimeHelper
- Update README for Gomoku project
- Rewrite GAME_STARTING to display board info and prompt moves
- Rewrite GAME_OVER for Gomoku win/draw display
- Create 5 new move handlers (success, invalid, occupied, OOB, not turn)
- Rewrite GAME_WATCH for Gomoku spectator mode
- Simplify SimpleClient (remove remote server list fetching)
- Simplify settings handler (remove poker display format)
- Remove all Chinese comments from client code
- Create ServerEventListener_CODE_GAME_MOVE with move validation,
win detection, and inline AI response for PVE
- Rewrite GAME_STARTING for 2-player Gomoku (black/white assignment)
- Rewrite ROOM_JOIN to auto-start at 2 players
- Rewrite ROOM_CREATE_PVE for single AI opponent
- Simplify RoomClearTask (remove robot substitution logic)
- Fix ClientRole.PLAYER references in handlers
- Replace Chinese comments with English
Remove ~35 files: poker entities/enums/helpers, old robot AI,
legacy server/client event handlers, Chinese i18n, docker configs,
and obsolete markdown docs.