extremely claude-assisted go game based on atproto! working on cleaning up and giving a more unique design, still has a bit of a slop vibe to it.
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Add URL parameter support for move navigation

- URL now updates with ?move=N when navigating to a specific move
- Visiting a game URL with ?move=N navigates directly to that move
- Move number is 1-indexed in URL for user-friendliness
- Supports sharing links to specific game positions

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

+35 -2
+1 -1
TODOS.md
··· 1 1 Features not yet implemented 2 2 - [x] currently reactions are being saved to PDS using the move number rather than the rKey, this causes a mismatch and prevents them from loading properly in the app since they dont match with the constellation RKEY for moves. (FIXED: reactions now use move AT URIs) 3 3 - [x] allow viewing all reactions at once rather than just for a given move 4 - - [ ] add url parameters to navigate to a specific move URI in a game rather than the current move. 4 + - [x] add url parameters to navigate to a specific move URI in a game rather than the current move. 5 5 - [ ] swap board view from to more scalable and robust tenuki library https://github.com/aprescott/tenuki?tab=readme-ov-file, which also includes a scoring engine 6 6 - [ ] fix the scoring logic upon completion of a game to use tenuki's scoring engine and pre-populate scores based on these, still allow changes if the owner disagrees but suggesting a score makes it much easier than counting manually.
+34 -1
src/routes/game/[id]/+page.svelte
··· 12 12 } from '$lib/atproto-client'; 13 13 import type { MoveRecord, PassRecord, GameRecord, ResignRecord } from '$lib/types'; 14 14 import { onMount, onDestroy } from 'svelte'; 15 + import { browser } from '$app/environment'; 16 + import { goto } from '$app/navigation'; 17 + import { page } from '$app/stores'; 15 18 16 19 let { data }: { data: PageData } = $props(); 17 20 ··· 198 201 passes = result.passes; 199 202 resigns = result.resigns; 200 203 loadingMoves = false; 204 + 205 + // Check for move parameter in URL after moves are loaded 206 + if (browser && moves.length > 0) { 207 + const moveParam = $page.url.searchParams.get('move'); 208 + if (moveParam) { 209 + const moveNum = parseInt(moveParam, 10); 210 + // moveNum is 1-indexed from URL, convert to 0-indexed 211 + if (!isNaN(moveNum) && moveNum >= 1 && moveNum <= moves.length) { 212 + // Delay to ensure boardRef is available 213 + setTimeout(() => { 214 + reviewMove(moveNum - 1, false); 215 + }, 100); 216 + } 217 + } 218 + } 201 219 202 220 // Fetch reactions for the game (async, don't block) 203 221 fetchGameReactions(data.gameAtUri).then((reactionsMap) => { ··· 479 497 } 480 498 }); 481 499 482 - function reviewMove(moveIndex: number) { 500 + function updateMoveUrl(moveIndex: number | null) { 501 + if (!browser) return; 502 + const url = new URL(window.location.href); 503 + if (moveIndex !== null) { 504 + url.searchParams.set('move', String(moveIndex + 1)); // 1-indexed for user-friendliness 505 + } else { 506 + url.searchParams.delete('move'); 507 + } 508 + goto(url.toString(), { replaceState: true, noScroll: true }); 509 + } 510 + 511 + function reviewMove(moveIndex: number, updateUrl = true) { 483 512 reviewMoveIndex = moveIndex; 484 513 if (boardRef) { 485 514 boardRef.replayToMove(moveIndex); 486 515 } 516 + if (updateUrl) { 517 + updateMoveUrl(moveIndex); 518 + } 487 519 } 488 520 489 521 function backToCurrentGame() { ··· 491 523 if (boardRef) { 492 524 boardRef.replayToMove(moves.length - 1); 493 525 } 526 + updateMoveUrl(null); 494 527 } 495 528 496 529 // Adapt moves to the format the Board component expects