///
/**
* ============================================================================
* BUG REPRO: Nested array elements are undefined when reading cross-session data
* ============================================================================
*
* SUMMARY:
* When one browser session sets a cell's data, and another browser session
* reads that same cell, the nested array elements resolve as `undefined`.
*
* OBSERVED BEHAVIOR:
* - otherData: {label: 'Player 2', items: Array(2)} ✓ object resolves
* - otherData.items: [undefined, undefined] ✗ array elements undefined!
* - otherData.items[0]: undefined ✗
* - otherData.items[0]?.id: undefined ✗
*
* EXPECTED BEHAVIOR:
* - otherData.items[0]: {id: 10, name: 'P2-Ship1', start: {...}, hits: [...]}
* - otherData.items[0]?.id: 10
*
* KEY CONDITIONS TO TRIGGER:
* 1. Parent pattern owns cells with Writable>
* 2. Child pattern receives cells as Writable (no Default wrapper)
* 3. Cell data is SET by one browser session (piece instance)
* 4. Cell data is READ by a DIFFERENT browser session (piece instance)
* 5. Data contains nested arrays with objects
*
* STEPS TO REPRODUCE:
* 1. Deploy this pattern:
* CT_API_URL=http://localhost:8000 CT_IDENTITY=./claude.key \
* deno task ct piece new packages/patterns/battleship/multiplayer/repro-minimal.tsx \
* --root packages/patterns/battleship --space gideon
*
* 2. Open the piece URL in Browser Tab 1
* 3. Click "Join as P1" → navigates to Child pattern
* 4. Open the SAME piece URL in Browser Tab 2 (new session)
* 5. Click "Join as P2" → this sets data2 cell
* 6. Go back to Tab 1 (P1's view)
* 7. Click "Check OTHER Data"
* 8. Observe console: items array has length 2 but elements are undefined
*
* NOTE: "Check MY Data" works correctly because myData was set in the same session.
*/
import {
action,
computed,
Default,
handler,
NAME,
navigateTo,
pattern,
UI,
Writable,
} from "commontools";
// ============================================================================
// Types - Nested structure similar to battleship's Ship type
// ============================================================================
interface Coordinate {
row: number;
col: number;
}
/** Item with nested object (start) and nested array (hits) - like Ship in battleship */
interface Item {
id: number;
name: string;
start: Coordinate; // Nested object
hits: boolean[]; // Nested array
}
/** Container holds an array of Items - the items array elements become undefined */
interface Container {
label: string;
items: Item[]; // <-- BUG: elements of this array are undefined when read cross-session
}
// ============================================================================
// Child Pattern - receives cells WITHOUT Default<> wrapper
// ============================================================================
interface ChildInput {
/** Cell set by THIS session - works correctly */
myData: Writable;
/** Cell set by OTHER session - BUG: nested array elements are undefined */
otherData: Writable;
whichPlayer: 1 | 2;
}
const Child = pattern(
({ myData, otherData, whichPlayer }) => {
/**
* CHECK OTHER DATA - This demonstrates the bug!
* When otherData was set by a different browser session,
* the array elements are undefined even though the array has length > 0.
*/
const checkOther = action(() => {
console.log(`[Child P${whichPlayer}] Checking OTHER player's data...`);
const other = otherData.get();
console.log("[Child] otherData:", other);
console.log("[Child] otherData.items:", other?.items);
console.log("[Child] otherData.items.length:", other?.items?.length);
if (other?.items && other.items.length > 0) {
// BUG: These all log undefined even though items.length is 2
console.log("[Child] otherData.items[0]:", other.items[0]);
console.log("[Child] otherData.items[0]?.id:", other.items[0]?.id);
console.log(
"[Child] otherData.items[0]?.start:",
other.items[0]?.start,
);
console.log(
"[Child] otherData.items[0]?.start?.row:",
other.items[0]?.start?.row,
);
}
});
/**
* CHECK MY DATA - This works correctly!
* When myData was set by THIS browser session, everything resolves properly.
*/
const checkMine = action(() => {
console.log(`[Child P${whichPlayer}] Checking MY data...`);
const mine = myData.get();
console.log("[Child] myData:", mine);
console.log("[Child] myData.items:", mine?.items);
console.log("[Child] myData.items.length:", mine?.items?.length);
if (mine?.items && mine.items.length > 0) {
// WORKS: These all resolve correctly
console.log("[Child] myData.items[0]:", mine.items[0]);
console.log("[Child] myData.items[0]?.id:", mine.items[0]?.id);
}
});
return {
[NAME]: computed(() => `Child P${whichPlayer}`),
[UI]: (
Child Pattern - Player {whichPlayer}
checkMine.send()}>
Check MY Data
checkOther.send()}>
Check OTHER Data