Summary
Reloading currently creates a new player/invite instead of resuming the same player. We also need a durable recovery story so a player can reload and continue as the same player with their board state intact.
This work should introduce a stable player identity and formalize a two-layer recovery model that supports current behavior and future privacy-focused constraints (e.g., not transmitting full card identity when it should be hidden).
Current behavior
- Reload results in a new ephemeral identity
- Player gets added as a new invite / duplicate presence entry
- Player’s local state may be lost or partially reset
Expected behavior
- Reload resumes the same player identity (no duplicate invites)
- Player can recover their game state
- Recovery model must support future privacy choices around what data is shared over the network
Design requirement: Two-layer recovery model (intentional, future-proof)
We should treat state as having two categories, regardless of how it’s currently implemented:
Layer 1: Shared / network-recoverable state
State that can be reconstructed from shared sources (e.g., Yjs doc + append-only action log + awareness).
Examples:
- Public/shared zones and objects
- Positions/transforms that are intended to be consistent for everyone
- Any information that is safe to replicate to all peers
Important: This layer may include full card identity today, but the model must not require that. We want the freedom to reduce shared data in the future (e.g., “only share card details when public”).
Layer 2: Owner-local / private state
State that is:
- specific to the owning player/device
- not required (or not safe) to share with other peers
- necessary for the owner to fully restore their personal view
Examples:
- Per-player private zone details (hand/library identities, local-only zones)
- UI-only state (arrangement preferences, selection, filters)
- Potential future: mapping from opaque IDs → actual card identity for private objects
This layer must be persisted locally (localStorage/IndexedDB) so that reload can restore it.
Proposed changes
1) Stable client/player identity
- Generate a stable
clientId once and persist in localStorage
- Use
clientId as the canonical key for “this is the same player” across reloads
- On rejoin, dedupe/resume instead of creating a new invite/player entry
Suggested key:
2) Recovery flow on load
On joining a table:
- Shared recovery: sync/replay shared sources to reconstruct shared state
- Local recovery: load the owner-local state keyed by
(tableId, clientId) and apply it
Suggested keys:
arcanetable:table:<tableId>:client:<clientId>:localState
3) Do not block future privacy changes
Implementation must not assume the shared log always contains full card identity. If today’s log includes full identity, local recovery can be minimal initially — but the architecture should allow:
- card instances represented by opaque IDs
- identity resolution for private objects to come from Layer 2 in the future
- explicit “reveal” semantics if/when you decide to limit what’s shared
Acceptance criteria
- Reload does not create a new player/invite
- Presence/player list remains stable across reloads (deduped by
clientId)
- Player recovers shared game state after reload
- Player recovers owner-local state after reload when local persistence exists
- If owner-local state is missing, game still loads (graceful fallback)
- Architecture supports (does not prevent) future “share less until public” privacy model
Notes / follow-ups
- Multi-tab behavior (same
clientId) should not create duplicate players
- Consider an optional “Reset local identity/state” button for debugging
- Potential future enhancement: encrypted export/import of owner-local state for cross-device recovery
Summary
Reloading currently creates a new player/invite instead of resuming the same player. We also need a durable recovery story so a player can reload and continue as the same player with their board state intact.
This work should introduce a stable player identity and formalize a two-layer recovery model that supports current behavior and future privacy-focused constraints (e.g., not transmitting full card identity when it should be hidden).
Current behavior
Expected behavior
Design requirement: Two-layer recovery model (intentional, future-proof)
We should treat state as having two categories, regardless of how it’s currently implemented:
Layer 1: Shared / network-recoverable state
State that can be reconstructed from shared sources (e.g., Yjs doc + append-only action log + awareness).
Examples:
Important: This layer may include full card identity today, but the model must not require that. We want the freedom to reduce shared data in the future (e.g., “only share card details when public”).
Layer 2: Owner-local / private state
State that is:
Examples:
This layer must be persisted locally (localStorage/IndexedDB) so that reload can restore it.
Proposed changes
1) Stable client/player identity
clientIdonce and persist in localStorageclientIdas the canonical key for “this is the same player” across reloadsSuggested key:
arcanetable:clientId2) Recovery flow on load
On joining a table:
(tableId, clientId)and apply itSuggested keys:
arcanetable:table:<tableId>:client:<clientId>:localState3) Do not block future privacy changes
Implementation must not assume the shared log always contains full card identity. If today’s log includes full identity, local recovery can be minimal initially — but the architecture should allow:
Acceptance criteria
clientId)Notes / follow-ups
clientId) should not create duplicate players