Kibitz update#3579
Conversation
…e added secondary game-id freshness check. This is still good and necessary to prevent old controllers having any form of influence. However the game_id was checked against controller.goban.config.game_id, which isn't stable (null) during loading of the variation. This then causes the controller load to be rejected. Therefore this guard in KibitzRoomStage.tsx now uses a stable expectedSecondaryBoardGameId from the JSX state.
…itive already exist: In KibitzRoomStage.tsx, the main-board:official-tail-advanced guard now uses selectedVariationGameId instead of selectedVariation?.game_id. In the same file, isCurrentPendingSecondarySnapshotLoad now compares against selectedVariationGameId instead of selectedVariation.game_id.
…riation debug flag in `src/views/Kibitz/KibitzRoomStage.tsx`. What changed: Added gated secondary-board readiness logging for on-ready and on-ready-null. Added gated remount-ready logging with controller/game/container state. Added gated visible-redraw scheduling logs with controller DOM and container measurements. Kept the extra container ref needed for those logs, but only the debug payload work runs when isKibitzVariationDebugEnabled() is true. Moved the callback placement so TypeScript no longer sees forward references.
…z/KibitzRoomStage.tsx`. What’s now logged, only when isKibitzVariationDebugEnabled() is true: visible-goban:redraw-schedule visible-goban:redraw-stale-controller visible-goban:redraw-detached visible-goban:redraw-deferred-zero-size visible-goban:redraw-request What it captures: role and reason expected size controller game id whether the controller parent is connected summarized measured element and parent chain current/official move numbers at the redraw point
…itzRoomStage.tsx`: secondary-board:detached-remount-reset at `1254` (line 1254) secondary-board:detached-remount-requested at `1273` (line 1273) secondary-board:room-change-teardown at `1747` (line 1747)
…mStage.tsx`. What changed: Added a single mobile secondary owner: preview, draft, variation, or none at `:1469` (line 1469). Derived mobileCompareTargetActive from that owner instead of from secondaryBoardGame presence at `:1495` (line 1495). Added mobileSecondaryBoardKey so owner changes remount cleanly on mobile at `:1488` (line 1488). Threaded mobileSecondaryOwner and mobileSecondaryBoardKey into the gated debug logs at `:1513` (line 1513). Replaced the independent mobile preview/variation booleans with a single owner-based render path at `:3110` (line 3110) and `:3139` (line 3139). Kept secondaryBoardGame as data only; it no longer decides preview ownership by itself.
…KibitzBoard.tsx`. What changed: Added a shell-first render path using `PersistentElement` so the goban DOM can attach before the controller is created. Added a host-readiness key and layout-driven RAF check before creating GobanController. The controller effect now bails out until the board host is attached and measurable. onReady(null) is now only emitted if a controller was actually published. The shell path still preserves the existing zero-size secondary defer behavior. Relevant spots: Host readiness gate: src/views/Kibitz/KibitzBoard.tsx (line 164) Readiness watcher: src/views/Kibitz/KibitzBoard.tsx (line 245) Controller creation gate: src/views/Kibitz/KibitzBoard.tsx (line 287) Conditional onReady(null): src/views/Kibitz/KibitzBoard.tsx (line 483) Shell render path: src/views/Kibitz/KibitzBoard.tsx (line 704)
…Just one simple persistent goban-container, no shell vs main. Along with previous commits (controller-birth readiness and one single owner for board) have prevented many paths for issues and seemed to have made mobile much more stable. What changed: KibitzBoard now owns one stable goban-container -> PersistentElement path for the whole controller lifetime. setGoban(...) still stores the controller goban locally, but it no longer swaps the DOM between a shell branch and a live GobanContainer branch. The resize and recenter logic that GobanContainer was providing is now handled locally in KibitzBoard. The deferred-size placeholder path is still preserved for the invalid-size secondary case. What this removes: the shell/live DOM ownership handoff the same gobanDiv being moved between two React-owned subtrees the detach window that showed up after on-ready
… main controller context” as a hard stop when a usable room-base snapshot already exists. In src/views/Kibitz/KibitzInner.tsx (line 1141), the new-variation snapshot flow now:uses the fresh visible main-board controller when it is actually current falls back to the cached room-base snapshot when the controller is missing or stale and that cached snapshot is usable keeps the stale-controller guard intact for real stale-controller cases
…rd.tsx (line 203). Kept the separate resize/redraw effect intact, so size changes now trigger redraws instead of remounts. This will prevent unnecessary remount loops while the goban is reaching its size.
Code Review — Bugs & Potential Issues1.
|
…ve-room safety and snapshot freshness. src/views/Kibitz/KibitzInner.tsx (line 949) now computes two separate values:roomLiveMoveNumber from resolvedRoom?.current_game?.move_number currentGameBaseSnapshotFreshnessMoveNumber from Math.max(room live, cached snapshot tail). It used to be one number but that isn't accurate, snapshots are a kibitz internal mechanic of making copies of the game for internal use (plotting variations on top), while live move number is of course just the state of the actual watched game. src/views/Kibitz/KibitzInner.tsx (line 970) passes the room live move number to the keeper. src/views/Kibitz/KibitzInner.tsx (line 1010) passes the snapshot-freshness number to the broker. The keeper now uses the authoritative room/live move number for its reconnect guard: src/views/Kibitz/useKibitzCurrentGameConnectionKeeper.ts (line 132) logs kibitz-current-game-keeper:connect-skipped when the controller’s official tail is behind the room live tail. src/views/Kibitz/useKibitzCurrentGameConnectionKeeper.ts (line 240) keeps the deferred reconnect burst for controller changes. The comparison uses last_official_move, not the cursor, so the guard is based on the official trunk state. I also made the broker naming explicit: src/views/Kibitz/useKibitzCurrentGameBaseBroker.ts (line 33) now takes currentSnapshotFreshnessMoveNumber, which matches what that hook is actually using.
The remaining race was the board-level fallback. I removed the extra raw game/connect from the Kibitz board’s hydration fallback in D:/Go/online-go-impl/src/views/Kibitz/KibitzBoard.tsx (line 755). That path still logs main-board:hydrate-request, but it now defers to the controller’s normal OGSConnectivity load/gamedata path instead of sending a second connect while the board is still hydrating. This results in a clean model: normal Goban/OGS load hydrates the board broker/main snapshots feed variation boards keeper only reconnects when the controller is already compatible with the live room KibitzBoard no longer sends a second raw connect while hydration is incomplete
|
Code Review: One confirmed issue found. requestSecondaryBoardDetachedRemount: duplicate debug guard emits stale epoch/nonce File: src/views/Kibitz/KibitzRoomStage.tsx The function now has two consecutive if (isKibitzVariationDebugEnabled()) blocks. The second block emits 'secondary-board:detached-remount-requested' BEFORE the mutations that give the remount its identity (secondaryBoardControllerEpochRef.current += 1 and bumpSecondaryBoardRemountNonce() both execute after both blocks). The nonce and epoch in the 'requested' event are therefore the pre-bump values, but the subsequent 'secondary-board:remount-controller-ready' event uses the post-bump epoch. When correlating these two events in debug logs the nonces will not match, making it appear as if they belong to different operations. The two if blocks should be merged into one block, and the second event ('detached-remount-requested') should either be emitted after the mutations or removed entirely, since the 'detached-remount-reset' event already captures the pre-reset state. |
… ability to open older variations, because this datamodel only manages the live game. Added gobanengine to fulfill hydration of variation boards for older variations. Details: The cross-game variation path is back, but it now resolves through a separate headless selected-game snapshot loader instead of the risky live board path. What changed: Added a headless selected-game snapshot builder/fetcher in src/views/Kibitz/KibitzRoomStage.tsx (line 105) and src/views/Kibitz/KibitzRoomStage.tsx (line 139). It uses GobanEngine, caches by game id, ignores stale responses, and logs the selected-game snapshot lifecycle. Wired the variation resolver to use selectedGameBaseSnapshot for cross-game variations in src/views/Kibitz/KibitzRoomStage.tsx (line 2614), including the not-fresh-enough branch instead of retrying against the room snapshot. Added a regression test for the headless snapshot builder in src/views/Kibitz/KibitzRoomStage.test.ts (line 229).
… for older boards. Cross-game posted variations are now handled through a freshness-aware headless selected-game snapshot path, without reusing the risky live board. What changed: Added isSelectedGameBaseSnapshotFreshEnough plus a selected-game snapshot backoff/cache path in src/views/Kibitz/KibitzRoomStage.tsx (line 139) and src/views/Kibitz/KibitzRoomStage.tsx (line 1228). fetchSelectedGameBaseSnapshot now takes requiredSnapshotMoveNumber and only returns a ready snapshot when the fetched headless GobanEngine trunk is deep enough, otherwise it returns a not-fresh-enough result instead of feeding a shallow cache hit back into the loop. See src/views/Kibitz/KibitzRoomStage.tsx (line 165). The cross-game variation branch now uses the freshness check directly and logs the real not-fresh-enough case instead of falling through to room-based retries. See src/views/Kibitz/KibitzRoomStage.tsx (line 2721). captureRoomBaseSnapshotForVariation now accepts an omitted sourceGame when the selected-game snapshot already proves the game identity, so older-game variations can apply even when the source game is not available in room-local caches. See src/views/Kibitz/KibitzRoomStage.tsx (line 740). Tests added: Fresh enough and too-shallow headless selected-game snapshot coverage in src/views/Kibitz/KibitzRoomStage.test.ts (line 268). Cross-game consumption without sourceGame coverage in src/views/Kibitz/KibitzRoomStage.test.ts (line 292). Freshness predicate coverage in src/views/Kibitz/KibitzRoomStage.test.ts (line 338).
Code ReviewI found one confirmed bug and one plausible regression. No nit-picks. Bug:
|
Code Review — Bugs & Performance Notes1.
|
…ale metric recentering. The debounced path now only schedules onResize(true) and no longer recenters using stale metrics.
…ency on controller remount. The keeper now permits exactly one controller-backed bootstrap connect per active room/game, so a freshly mounted live board can receive gamedata even while still at move 0. Connects sent before a controller exists do not consume that bootstrap. After the bootstrap is consumed, the existing live-tail hydration guard continues to block later unsafe reconnects from unhydrated controllers.
Code Review — Kibitz UpdateTwo issues worth flagging: Bug: Stale
|
|
Review item 1: Corner case when game ends on move zero. Not applicable. Review item 2: Claude reckons:
The comment identifies a real architectural inconsistency (two No action required at this time. |
…rdening patch in KibitzBoard to stop delayed callbacks from redrawing a destroyed or replaced Goban.
…in tree doesn't update with the live game. Issue: The data-model is "main board gets the real game, variation board updates from there". And the variation board does use the main board as authority to build snapshots, but fallbacks to use it's own stale tree still existed. These fallbacks are now removed entirely to enforce the clean data model.
…tead of "Game" / "Room"), and the same on the mobile buttons.
…ion useCallback. Added mainGame?.live to the reportMainBoardHydration callback dependency list.
|
For bug 1: a fix is in. The risk is tiny but so is the fix so why not :) |
…Create Room button, on both desktop and mobile.
Code Review: Kibitz updateFinding 1: GobanEngine instantiated without cleanup in buildSelectedGameBaseSnapshotFromDetails buildSelectedGameBaseSnapshotFromDetails in KibitzRoomStage.tsx directly instantiates new GobanEngine(engineConfig) to extract snapshot data, but never calls any cleanup on it. This is the first place in the codebase that directly creates a GobanEngine instance rather than obtaining one through GobanController. If GobanEngine registers event listeners, timers, or other global state in its constructor (as a subclass of EventEmitter in the goban library), instances accumulated across repeated variation-game snapshot fetches would leak. The existing fetchCurrentGameBaseSnapshot in KibitzInner.tsx demonstrates the established pattern: it wraps the controller in a try/finally and calls snapshotController.destroy(). The same discipline should apply here. Finding 2: getMoveTreeTrunkTail O(n) walk on every render of KibitzInner getMoveTreeTrunkTail (a while-loop trunk traversal, O(n) in move count) is called unconditionally in the render body of KibitzInner.tsx: KibitzInner re-renders on every game event subscription (move-made, gamedata, last_official_move, etc.). For a game in progress with many moves, this traversal runs on every render and grows with move count. The result could be tracked as state updated by a move-made event listener on the controller, or at minimum wrapped in a useMemo invalidated by a move-counter derived from those events. |
Code Review - Kibitz UpdateThis is a substantial refactor with solid architecture overall. A few issues worth flagging: Bug: boardHostReadinessKey in Goban creation effect triggers unnecessary controller teardowns (KibitzBoard.tsx)The Goban creation effect includes both Two concrete consequences:
Suggested fix: remove Bug: mobileDraftSourceSnapshotRefreshNonce can loop indefinitely (KibitzRoomStage.tsx)The draft-source snapshot fetch effect calls When that happens: the snapshot is in the cache, A straightforward fix: cap the nonce at a small maximum retry count, or track whether the fetch already ran for a given Bug: dead AbortError guard masks incorrect failure classification (KibitzRoomStage.tsx)In Practical impact: if a |
Use a CSS-scaled mobile resize model that separates the native Goban backing size from the visible board size. During divider drags, the visible Goban follows the target layout by transform; on release, the scaled presentation is committed instead of handing off to a different native size. This keeps mobile board resizing smooth across main, variation, and draft board states.
…o kibitz-jd-latest
|
Code Review: PR #3579 -- Kibitz update Large Kibitz feature update: change-board flow, variation sharing, room rename propagation, mobile resize/drag overhaul, anonymous create-room gate, new E2E tests and unit tests. The overall structure is solid and test coverage is thorough. Bug: The snapshot now requires Worth confirming every snapshot-creation call path sets Performance: Eager DOM measurements outside the debug guard in
The fix is a debug guard before constructing the argument object: if (isKibitzBoardSizeDebugEnabled()) {
recordKibitzBoardSizeEvent('...', { ...getKibitzBoardMetricsSnapshot('...'), ... });
}The same guard should wrap |
Code Review — Kibitz UpdateThis is a substantial PR. Most of it is well-structured. A few concrete issues worth addressing: Bug —
|
…top in the room settings menu (gear icon)
Code Review NotesBug: Fetch not cancelled at network level on room/game change (
|
…tured and metadata: time controls and handicap settings. Added to both desktop and mobile. Mobile has an animation to temporarily show metadata for a few seconds. This respects a users reduce-motion accessibilty setting, nonethelss I consider this experimental and fully understandable if we don't add animation.
|
Reviewed the diff for bugs and performance issues. Missing null guard in In the newly added debug capture handler,
Everything else I looked at checked out:
|
…ation rendering. Skip malformed non-selected visible variations when composing the posted variation board instead of letting one bad room-history entry block all variation rendering. Keep selected variations strict, preserve snapshot freshness checks, and log skipped malformed entries for diagnostics.
…nup in buildSelectedGameBaseSnapshotFromDetails Ensure the temporary DOM node used for current-game base snapshot construction is removed even if the temporary GobanController fails to construct. Destroy successfully created temporary controllers during cleanup without changing snapshot freshness or fetch behavior.
Code Review
The outer board container div reads In that window the outer div still receives The imperative path (setting Fix: introduce a companion boolean state variable (e.g. |
Fixes everything ;)