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.

Fix player display by discovering playerTwo from moves

- Include AT URIs in Constellation-based fetchGameMoves/fetchGamePasses
- Use Constellation (discovers all players) as primary move source in
game page, falling back to PDS-based fetch
- Infer playerTwoDid from move records when not in DB or PDS record
- Improve homepage player inference to check all moves, not just 2nd

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

+75 -31
+2 -2
src/lib/atproto-client.ts
··· 213 213 214 214 const body: ConstellationBacklinksResponse = await res.json(); 215 215 for (const rec of body.records) { 216 - allMoves.push(rec.value as MoveRecord); 216 + allMoves.push({ ...(rec.value as MoveRecord), uri: rec.uri }); 217 217 } 218 218 cursor = body.cursor ?? undefined; 219 219 } while (cursor); ··· 250 250 251 251 const body: ConstellationBacklinksResponse = await res.json(); 252 252 for (const rec of body.records) { 253 - allPasses.push(rec.value as PassRecord); 253 + allPasses.push({ ...(rec.value as PassRecord), uri: rec.uri }); 254 254 } 255 255 cursor = body.cursor ?? undefined; 256 256 } while (cursor);
+14 -14
src/routes/+page.svelte
··· 335 335 actualStatus = 'waiting'; 336 336 } 337 337 338 - // Infer player_two from second move if not set 338 + // Infer player_two from moves if not set in game record 339 339 let playerTwo = item.record.playerTwo || null; 340 340 if (!playerTwo && movesByGame.has(atUri)) { 341 341 const gameMoves = movesByGame.get(atUri)!; 342 - gameMoves.sort((a, b) => { 343 - const moveNumA = a.value?.moveNumber || 0; 344 - const moveNumB = b.value?.moveNumber || 0; 345 - return moveNumA - moveNumB; 346 - }); 347 - if (gameMoves.length >= 2) { 348 - const secondMoveUri = gameMoves[1].uri; 349 - if (secondMoveUri) { 350 - const match = secondMoveUri.match(/^at:\/\/(did:[^\/]+)\//); 351 - if (match) { 342 + const playerOneDid = item.record.playerOne || item.did; 343 + // Find any move by a player other than player one 344 + for (const m of gameMoves) { 345 + const moveDid = m.value?.player; 346 + if (moveDid && moveDid !== playerOneDid) { 347 + playerTwo = moveDid; 348 + break; 349 + } 350 + // Also try extracting DID from the move's AT URI 351 + if (!moveDid && m.uri) { 352 + const match = m.uri.match(/^at:\/\/(did:[^\/]+)\//); 353 + if (match && match[1] !== playerOneDid) { 352 354 playerTwo = match[1]; 355 + break; 353 356 } 354 - } 355 - if (!playerTwo && gameMoves[1].value?.player) { 356 - playerTwo = gameMoves[1].value.player; 357 357 } 358 358 } 359 359 }
+59 -15
src/routes/game/[id]/+page.svelte
··· 7 7 resolveDidToHandle, 8 8 fetchGameRecord, 9 9 fetchGameActionsFromPds, 10 + fetchGameMoves, 11 + fetchGamePasses, 12 + fetchGameResigns, 10 13 fetchGameReactions, 11 14 fetchUserProfile, 12 15 fetchCloudGoProfile, ··· 337 340 338 341 // Use database values as fallback if PDS record is stale 339 342 const playerOneDid = record?.playerOne || data.playerOneDid; 340 - const playerTwoDid = record?.playerTwo || data.playerTwoDid; 343 + let playerTwoDid = record?.playerTwo || data.playerTwoDid; 341 344 342 - // Resolve handles and fetch profiles using the final player DIDs (with database fallback) 345 + // Resolve handles and fetch profiles for player one 343 346 if (playerOneDid) { 344 347 resolveDidToHandle(playerOneDid).then((h) => { 345 348 playerOneHandle = h; ··· 352 355 }).catch(err => console.error('Failed to fetch player one Cloud Go profile:', err)); 353 356 } 354 357 358 + // Fetch game actions - use Constellation (discovers ALL players) as primary, 359 + // fall back to PDS-based fetch if Constellation fails 360 + console.log('[loadGameData] Fetching game actions...', { playerOneDid, playerTwoDid, gameAtUri: data.gameAtUri }); 361 + let fetchedMoves: MoveRecord[]; 362 + let fetchedPasses: PassRecord[]; 363 + let fetchedResigns: ResignRecord[]; 364 + 365 + try { 366 + // Constellation backlinks find moves from any player - no need to know DIDs upfront 367 + const [constellationMoves, constellationPasses, constellationResigns] = await Promise.all([ 368 + fetchGameMoves(data.gameAtUri), 369 + fetchGamePasses(data.gameAtUri), 370 + fetchGameResigns(data.gameAtUri), 371 + ]); 372 + fetchedMoves = constellationMoves; 373 + fetchedPasses = constellationPasses; 374 + fetchedResigns = constellationResigns; 375 + console.log('[loadGameData] Constellation fetch succeeded:', { 376 + moves: fetchedMoves.length, 377 + passes: fetchedPasses.length, 378 + resigns: fetchedResigns.length 379 + }); 380 + } catch (constellationErr) { 381 + console.warn('[loadGameData] Constellation fetch failed, falling back to PDS:', constellationErr); 382 + const result = await fetchGameActionsFromPds(playerOneDid, playerTwoDid, data.gameAtUri); 383 + fetchedMoves = result.moves; 384 + fetchedPasses = result.passes; 385 + fetchedResigns = result.resigns; 386 + } 387 + 388 + // If playerTwoDid is unknown, infer it from the moves/passes 389 + if (!playerTwoDid && playerOneDid) { 390 + const allPlayerDids = new Set<string>(); 391 + for (const m of fetchedMoves) allPlayerDids.add(m.player); 392 + for (const p of fetchedPasses) allPlayerDids.add(p.player); 393 + for (const r of fetchedResigns) allPlayerDids.add(r.player); 394 + allPlayerDids.delete(playerOneDid); 395 + if (allPlayerDids.size > 0) { 396 + playerTwoDid = allPlayerDids.values().next().value!; 397 + console.log('[loadGameData] Inferred playerTwoDid from moves:', playerTwoDid); 398 + // Update the game record derived state so the UI shows player two 399 + if (gameRecord && !gameRecord.playerTwo) { 400 + gameRecord = { ...gameRecord, playerTwo: playerTwoDid }; 401 + } 402 + } 403 + } 404 + 405 + // Resolve handles and fetch profiles for player two (now that we may have discovered them) 355 406 if (playerTwoDid) { 356 407 resolveDidToHandle(playerTwoDid).then((h) => { 357 408 playerTwoHandle = h; ··· 364 415 }).catch(err => console.error('Failed to fetch player two Cloud Go profile:', err)); 365 416 } 366 417 367 - // Fetch moves, passes, and resigns from both players' PDS repos. 368 - console.log('[loadGameData] Fetching game actions...', { playerOneDid, playerTwoDid, gameAtUri: data.gameAtUri }); 369 - const result = await fetchGameActionsFromPds( 370 - playerOneDid, 371 - playerTwoDid, 372 - data.gameAtUri 373 - ); 374 418 console.log('[loadGameData] Game actions fetched:', { 375 - moves: result.moves.length, 376 - passes: result.passes.length, 377 - resigns: result.resigns.length 419 + moves: fetchedMoves.length, 420 + passes: fetchedPasses.length, 421 + resigns: fetchedResigns.length 378 422 }); 379 - moves = result.moves; 380 - passes = result.passes; 381 - resigns = result.resigns; 423 + moves = fetchedMoves; 424 + passes = fetchedPasses; 425 + resigns = fetchedResigns; 382 426 loadingMoves = false; 383 427 console.log('[loadGameData] loadingMoves set to false'); 384 428