Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
77 commits
Select commit Hold shift + click to select a range
8f4bab9
Fix: addresses variations not loading. The cause is that in 359522630…
Johnniedarkoo May 22, 2026
35a800a
Fixed 2 more guard-checks using run-time values where the render prim…
Johnniedarkoo May 22, 2026
0ecb5bd
Added debug: Added the Kibitz secondary-board debugging behind the va…
Johnniedarkoo May 23, 2026
8a47aa6
Added debug: Added the deeper redraw-path tracing in `src/views/Kibit…
Johnniedarkoo May 23, 2026
fe68404
Added debug: the next layer of gated tracing in `src/views/Kibitz/Kib…
Johnniedarkoo May 23, 2026
5f96801
Implemented the mobile ownership split in `src/views/Kibitz/KibitzRoo…
Johnniedarkoo May 23, 2026
013a7f4
Implemented the controller-birth readiness gate in `src/views/Kibitz/…
Johnniedarkoo May 23, 2026
7b6467c
Fix: removed the handoff of gobanDiv between different react states. …
Johnniedarkoo May 23, 2026
a82488c
basic share variation test works
GreenAsJade May 23, 2026
a10841d
Merge branch 'e2e_kibitz_create_room' into e2e_kibitz_share_variation
GreenAsJade May 23, 2026
d56f5cf
Fix: Updated the Kibitz variation selector so it no longer treats “no…
Johnniedarkoo May 23, 2026
21b62b8
Removed size from boardHostReadinessKey in src/views/Kibitz/KibitzBoa…
Johnniedarkoo May 23, 2026
ae147c3
Merge remote-tracking branch 'Johnniedarkoo/kibitz-jd-latest' into ki…
GreenAsJade May 23, 2026
cf6de86
First try at detecting broken variation
GreenAsJade May 23, 2026
7467a8e
Merge remote-tracking branch 'Johnniedarkoo/kibitz-jd-latest' into e2…
GreenAsJade May 23, 2026
f47b061
Check that main board was loaded correctly before adding variation
GreenAsJade May 23, 2026
d1c05aa
Partial fix against reloads on mobile: Tightened the split between li…
Johnniedarkoo May 23, 2026
74faa0e
Full fix against reloads mobile:
Johnniedarkoo May 23, 2026
c06c7b9
Merge branch 'e2e_kibitz_share_variation' into kibitz_update
GreenAsJade May 24, 2026
ab838ae
Fixing the reloads and moving to the new simple datamodel removed the…
Johnniedarkoo May 24, 2026
11eaa6f
Integrated existing checks into the new headless GobanEngine approach…
Johnniedarkoo May 24, 2026
923f123
Merge remote-tracking branch 'Johnniedarkoo/kibitz-jd-latest' into ki…
GreenAsJade May 25, 2026
affc832
cspell
GreenAsJade May 25, 2026
7b7a60f
Patched the resize/recenter timing to fix possible resize jitter / st…
Johnniedarkoo May 25, 2026
2fc940a
Addresses PR Review: canReconnectController creates a circular depend…
Johnniedarkoo May 25, 2026
60d2090
Partial fix for the mobile remount/hydration race in Kibitz. Don't d…
Johnniedarkoo May 25, 2026
2ca596b
Final Fix for mobile main board hydration after returning from variat…
Johnniedarkoo May 25, 2026
bfc8ac0
Addresses PR review: Bug: requestSelectedGameBaseSnapshot — stale cac…
Johnniedarkoo May 25, 2026
df82962
Address PR review: Plausible regression: mobile draft board renders w…
Johnniedarkoo May 25, 2026
8de0f43
Fixed the unlikely case where the mobile goban goes a little off scre…
Johnniedarkoo May 25, 2026
b8f57ec
Merge remote-tracking branch 'Johnniedarkoo/kibitz-jd-latest' into ki…
GreenAsJade May 25, 2026
d23a981
Performance: Avoid redundant Kibitz base snapshot fetches
Johnniedarkoo May 25, 2026
b76539e
Better error handling if snapshot fails.
Johnniedarkoo May 25, 2026
bdc5e95
Fixes a potential hand-off issue in draft-from-posted variation hydra…
Johnniedarkoo May 25, 2026
5a21f4e
Fix: remove blocker for mobile draft board ownership for old-game var…
Johnniedarkoo May 25, 2026
ec84b3d
Merge branch 'main' of https://github.com/online-go/online-go.com int…
Johnniedarkoo May 27, 2026
3ff03e1
Address PR Review: KibitzBoard.tsx — scheduleInitialResizeRetry silen…
Johnniedarkoo May 27, 2026
fe355c0
Merge remote-tracking branch 'Johnniedarkoo/kibitz-jd-latest' into ki…
GreenAsJade May 27, 2026
dcd6810
cspell
GreenAsJade May 27, 2026
d7372b6
Added e2e test for editing room details
GreenAsJade May 27, 2026
cff98ce
Arrange subscriptions so that directory finds out about room descript…
GreenAsJade May 27, 2026
fa866e7
Test editing the room details is broadcast
GreenAsJade May 27, 2026
5a5c26c
Make sure we send the description even if its empty
GreenAsJade May 27, 2026
087b0a6
Added e2e test for "change game" in a room.
GreenAsJade May 27, 2026
be04075
Address PR Review: 4. Empty moves array causes a permanent non-retrya…
Johnniedarkoo May 28, 2026
513959a
Address PR Review: 3. Non-null assertion on potentially-undefined mov…
Johnniedarkoo May 28, 2026
0c73cd1
Address PR Review: 2. Unbounded requestAnimationFrame polling loop (K…
Johnniedarkoo May 28, 2026
8352b0a
Address PR Review: 1. O(n) move-tree traversal in the render path (Ki…
Johnniedarkoo May 28, 2026
742466c
Added cspell and carved out local exceptions for refetches and unhydr…
Johnniedarkoo May 28, 2026
f2a5e21
Stop reloading secondary variation base when the desired dirty variat…
Johnniedarkoo May 28, 2026
54a8109
Implemented the hydration fix without touching resize or Goban sizing…
Johnniedarkoo May 28, 2026
b0b9b3f
Implemented the live-root safety fix.
Johnniedarkoo May 28, 2026
6b01dd9
Follow up: after fetching the live game, install it into the main goban.
Johnniedarkoo May 28, 2026
51adae8
Followup for mobile. Implemented the narrow restore-to-tail fix.
Johnniedarkoo May 29, 2026
418e451
Merge branch 'main' into kibitz_update
GreenAsJade May 30, 2026
92dadbc
Merge remote-tracking branch 'Johnniedarkoo/kibitz-jd-latest' into ki…
GreenAsJade May 30, 2026
d38e034
Addresses SVG renderer destroy loop: Added a narrow lifecycle-only ha…
Johnniedarkoo May 30, 2026
e50728b
Bugfix: when variation board is open and watching a variation, the ma…
Johnniedarkoo May 30, 2026
fcadbe8
UI Desktop split divider now says "Game chat" / "Kibitz chat" in (ins…
Johnniedarkoo May 30, 2026
211092d
Address PR Review: Bug: Stale mainGame?.live in reportMainBoardHydrat…
Johnniedarkoo May 30, 2026
a66b4ee
Merge remote-tracking branch 'Johnniedarkoo/kibitz-jd-latest' into ki…
GreenAsJade May 30, 2026
8f82ea1
UX: Anonymous users see "Sign in to create room" text instead of the …
Johnniedarkoo May 30, 2026
5e23232
Merge remote-tracking branch 'Johnniedarkoo/kibitz-jd-latest' into ki…
GreenAsJade May 30, 2026
8377e3e
Squash Merge for mobile Kibitz board resizing
Johnniedarkoo Jun 3, 2026
fb27c6c
Merge branch 'main' of https://github.com/online-go/online-go.com int…
Johnniedarkoo Jun 3, 2026
2148616
Removed leftover development file from Kibitz
Johnniedarkoo Jun 3, 2026
2f9d851
Merge remote-tracking branch 'Johnniedarkoo/kibitz-jd-latest' into ki…
GreenAsJade Jun 4, 2026
5b25e33
cspell, and --no-progress for cleaning yarn target output
GreenAsJade Jun 4, 2026
58d7128
Add Kibitz streamer mode layout support. Enable streamer mode on desk…
Johnniedarkoo Jun 5, 2026
2fdce2f
Merge remote-tracking branch 'Johnniedarkoo/kibitz-jd-latest' into ki…
GreenAsJade Jun 6, 2026
eaf4d88
Add proper Scoreboard with player names avatar, time left, stones cap…
Johnniedarkoo Jun 9, 2026
ea8192d
Merge remote-tracking branch 'Johnniedarkoo/kibitz-jd-latest' into ki…
GreenAsJade Jun 11, 2026
a8b7e93
cspell fixes
Johnniedarkoo Jun 12, 2026
a8aedad
Addresses PR Review: Bug — Single malformed variation blocks all vari…
Johnniedarkoo Jun 12, 2026
02b4df7
Addresses PR Review: Finding 1: GobanEngine instantiated without clea…
Johnniedarkoo Jun 12, 2026
d142280
Merge remote-tracking branch 'Johnniedarkoo/kibitz-jd-latest' into ki…
GreenAsJade Jun 13, 2026
46991c2
Merge branch 'main' into kibitz_update
GreenAsJade Jun 28, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 22 additions & 3 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,26 @@ jobs:
submodules: recursive
lfs: true

- name: Run spell check
uses: streetsidesoftware/cspell-action@v6
- uses: actions/setup-node@v4
with:
node-version: "20"

- name: Cache node modules
id: cache-node-modules
uses: actions/cache@v4
with:
path: node_modules
key: ${{ runner.os }}-node-modules-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-node-modules-

- name: yarn install
if: steps.cache-node-modules.outputs.cache-hit != 'true'
uses: nick-fields/retry@v3
with:
files: src/**/*.{ts,tsx}
timeout_minutes: 10
max_attempts: 3
command: yarn install

- name: Run spell check
run: yarn spellcheck
5 changes: 5 additions & 0 deletions .vscode/cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -154,13 +154,17 @@
"pofile",
"ponnuki",
"postback",
"Postcheck",
"Postchecks",
"pprettier",
"qrcode",
"quickmatch",
"rackcdn",
"rdil",
"refetches",
"rengo",
"replot",
"retryable",
"rgba",
"rollups",
"roundrobin",
Expand Down Expand Up @@ -210,6 +214,7 @@
"unannulable",
"ungap",
"unhighlight",
"unhydrated",
"uniquification",
"unitify",
"Unranked",
Expand Down
5 changes: 4 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ pretty prettier lint-fix format:
npm run prettier
npm run lint:fix

spellcheck:
npm run spellcheck


analyze visualizer bundle-visualizer:
npm run bundle-visualizer
Expand All @@ -47,6 +50,6 @@ GOBAN_SOCKET_WORKER_VERSION=0.2
update-worker: build
cp dist/modules/GobanSocketWorkerScript.js ../ogs-node/src/GobanSocketWorker/GobanSocketWorkerScript-$(GOBAN_SOCKET_WORKER_VERSION).js

.PHONY: dev build test analyze pretty prettier lint-fix .husky visualizer bundle-visualizer update-worker
.PHONY: dev build test analyze pretty prettier lint-fix spellcheck .husky visualizer bundle-visualizer update-worker

-include Makefile.production
9 changes: 7 additions & 2 deletions e2e-tests/helpers/game-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
* This program is distributed in the hope that it will be useful,
*/

import { Page } from "@playwright/test";
import { Locator, Page } from "@playwright/test";
import { expect } from "@playwright/test";

type BoardSize = "19x19" | "13x13" | "9x9";
Expand Down Expand Up @@ -87,6 +87,11 @@ export const clickOnGobanIntersection = async (
page: Page,
coord: string,
boardSize: BoardSize = "19x19",
// Optional locator to scope which Goban to click. Defaults to the first
// pointer-bound goban on the page. Pass a scoped locator when more than
// one such goban can be present (e.g. Kibitz compare mode where the
// main board and the secondary analysis board are both interactive).
gobanLocator?: Locator,
) => {
const boardLetters: { [size: string]: string } = {
// cspell:disable
Expand Down Expand Up @@ -125,7 +130,7 @@ export const clickOnGobanIntersection = async (
// Row: N (top) -> 0, 1 (bottom) -> N-1
const row = sizeNumber - rowNumber;

const goban = page.locator(".Goban[data-pointers-bound]");
const goban = gobanLocator ?? page.locator(".Goban[data-pointers-bound]");
await goban.waitFor({ state: "visible" });
const box = await goban.boundingBox();
if (!box) {
Expand Down
144 changes: 137 additions & 7 deletions e2e-tests/kibitz/kibitz-basic-room.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,24 +19,38 @@

import type { CreateContextOptions } from "@helpers";
import { BrowserContext, expect } from "@playwright/test";
import {
acceptDirectChallenge,
createDirectChallenge,
defaultChallengeSettings,
} from "@helpers/challenge-utils";
import { playMoves, resignActiveGame } from "@helpers/game-utils";
import { expectOGSClickableByName } from "@helpers/matchers";

import { createKibitzRoomForLiveGame } from "./kibitz-helpers";

/*
* Verify the basic room flow: an authenticated user can create a Kibitz
* room pointing at a live in-progress game, the room renders correctly
* (board, room list, presence panel), and the user can post a chat
* message that shows up in the room stream.
* Verify the basic room flow end-to-end:
*
* Exercises the create-room API path, the watcher navigation into the
* room, and the comm-server kibitz-<roomId> Redis chat channel.
* 1. An authenticated user can create a Kibitz room pointing at a live
* in-progress game, the room renders correctly, and the user can post
* a chat message that shows up in the room stream.
* 2. The room owner can change the watched game: the players end their
* first game, start a fresh one, and the owner switches the room's
* board via the settings popover -> game picker overlay -> Change board
* flow (POST /api/v1/kibitz/rooms/:id/change-board).
*
* Exercises the create-room API path, the comm-server kibitz-<roomId>
* Redis chat channel, and the change-board endpoint plus its UIPush
* propagation back to the watcher's room view.
*/
export const kibitzBasicRoomTest = async ({
createContext,
}: {
createContext: (options?: CreateContextOptions) => Promise<BrowserContext>;
}) => {
const { watcherPage, roomId } = await createKibitzRoomForLiveGame(createContext);
const { watcherPage, blackPlayerPage, whitePlayerPage, whiteUsername, roomId } =
await createKibitzRoomForLiveGame(createContext);

// URL ends up at /kibitz/<roomId>.
expect(watcherPage.url()).toMatch(new RegExp(`/kibitz/${roomId}$`));
Expand Down Expand Up @@ -83,4 +97,120 @@ export const kibitzBasicRoomTest = async ({
);
await expect(roomFeed.getByText(message)).toBeVisible({ timeout: 10000 });
console.log("[kibitz basic-room] chat message rendered in room stream");

// Phase 2: change the room's watched game. The players end their current
// game, start a fresh one, and the watcher (room owner) switches the
// board via the settings popover.

// The prelude only played 4 moves; OGS gates "resign" behind a 6-move
// threshold (before that the action is "cancel game", which has a
// different confirmation dialog that resignActiveGame doesn't match).
// Play two more moves so the next turn (move 7) is resign-eligible for
// blackPlayerPage. Coordinates are non-conflicting with the prelude's
// E5/G5/E7/G7.
console.log("[kibitz basic-room] playing two more moves so resign is available");
await playMoves(blackPlayerPage, whitePlayerPage, ["C3", "G3"], "9x9");

console.log("[kibitz basic-room] black resigning the first game");
await resignActiveGame(blackPlayerPage);

// Give the second game a title whose HEAD differs from the prelude's
// first game ("E2E Kibitz live source game"). The board-subtitle-link
// assertion below matches a head-anchored regex, so anchoring the
// distinguishing prefix at character 0 makes the check robust against
// future text-truncation (the title is currently rendered verbatim but
// the CSS already truncates visually with ellipsis, and a future
// refactor could JS-truncate too).
const secondGameTitleHead = "Kibitz target";
const secondGameName = `${secondGameTitleHead} (phase 2)`;
console.log("[kibitz basic-room] starting a second live game between the same players");
await createDirectChallenge(blackPlayerPage, whiteUsername, {
...defaultChallengeSettings,
gameName: secondGameName,
boardSize: "9x9",
speed: "live",
timeControl: "byoyomi",
mainTime: "600",
timePerPeriod: "60",
periods: "5",
ranked: false,
});
await acceptDirectChallenge(whitePlayerPage);

// Capture the second game's id from the white player URL, same approach
// as the prelude.
await whitePlayerPage.waitForURL(/\/(game|play)\/\d+/, { timeout: 30000 });
const secondGameUrl = new URL(whitePlayerPage.url());
const secondGameMatch = secondGameUrl.pathname.match(/\/(game|play)\/(\d+)/);
if (!secondGameMatch) {
throw new Error(
`Expected /game/<id> or /play/<id> URL on white player page after second challenge, got ${secondGameUrl.pathname}`,
);
}
const secondGameId = Number(secondGameMatch[2]);
console.log(`[kibitz basic-room] second game id: ${secondGameId}`);

// Open the room settings popover and click "Change live game" -- this is
// gated on canChangeBoard, which is owner-or-moderator
// (kibitz/permissions.py:42-50). The watcher created the room, so they
// have it.
const gearButton = watcherPage.locator(".board-settings-button");
await expect(gearButton).toBeVisible({ timeout: 15000 });
await expect(gearButton).toBeOGSClickable();
await gearButton.click();

const popover = watcherPage.locator(".popover-container .KibitzRoomSettingsPopover");
await expect(popover).toBeVisible({ timeout: 15000 });

const changeBoardMenuButton = popover.getByRole("button", { name: /^Change live game$/ });
await expect(changeBoardMenuButton).toBeVisible({ timeout: 15000 });
await expect(changeBoardMenuButton).toBeOGSClickable();
console.log("[kibitz basic-room] owner clicking Change live game");
await changeBoardMenuButton.click();
// Clicking Change live game closes the popover and opens the game-picker
// overlay (KibitzRoomStage.tsx:1735-1738 -- close_all_popovers ->
// onChangeBoard).
await expect(popover).toBeHidden({ timeout: 15000 });

// The game-picker overlay is the same component as the create-room flow,
// in "change-board" mode (KibitzGamePickerOverlay.tsx). The game-id input
// has the same id (kibitz-game-picker-input); the footer's confirm
// button reads "Change board" instead of "Create room" in this mode.
const overlay = watcherPage.locator(".KibitzGamePickerOverlay");
await expect(overlay).toBeVisible({ timeout: 15000 });

const gameIdInput = overlay.locator("#kibitz-game-picker-input");
await expect(gameIdInput).toBeVisible({ timeout: 15000 });
await gameIdInput.fill(String(secondGameId));
await expect(gameIdInput).toHaveValue(String(secondGameId));

const loadButton = await expectOGSClickableByName(watcherPage, /^Load$/);
await loadButton.click();

// Wait for the overlay's footer confirm button to enable, then click. The
// disabled state lifts once the picker has resolved the entered game id
// and built the preview (canChangeBoard becomes true in the component).
const confirmChangeButton = overlay.getByRole("button", { name: /^Change board$/ });
await expect(confirmChangeButton).toBeVisible({ timeout: 15000 });
await expect(confirmChangeButton).toBeEnabled({ timeout: 15000 });
console.log("[kibitz basic-room] confirming Change board");
await confirmChangeButton.click();

// After the POST /change-board round-trip and the board-changed UIPush
// (KibitzRoomChangeBoard.post:329) propagate, the header's
// .board-subtitle-link href should reference the new game id
// (KibitzRoomStage.tsx:5281: href={`/game/${mainGame.game_id}`}). Two
// assertions on the same element: the structural href attribute (proves
// the room is bound to the new game's id) and the content-level text
// (proves the new game's metadata, not just its id, made it through).
// The text assertion uses a head-anchored regex so a future visual or
// JS truncation of the title would not silently weaken the check.
const boardSubtitleLink = watcherPage.locator(".board-subtitle-link");
await expect(boardSubtitleLink).toHaveAttribute("href", `/game/${secondGameId}`, {
timeout: 15000,
});
await expect(boardSubtitleLink).toHaveText(new RegExp(`^${secondGameTitleHead}`), {
timeout: 15000,
});
console.log(`[kibitz basic-room] room now watching game ${secondGameId}`);
};
4 changes: 4 additions & 0 deletions e2e-tests/kibitz/kibitz-basic.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,11 @@

import { ogsTest } from "@helpers";
import { kibitzBasicRoomTest } from "./kibitz-basic-room";
import { kibitzEditRoomDetailsTest } from "./kibitz-edit-room-details";
import { kibitzShareVariationTest } from "./kibitz-share-variation";

ogsTest.describe("@Kibitz basic flows", () => {
ogsTest("Create room, navigate in, and post a chat message", kibitzBasicRoomTest);
ogsTest("Share an analysis variation", kibitzShareVariationTest);
ogsTest("Owner edits room details; non-owner cannot", kibitzEditRoomDetailsTest);
});
Loading
Loading