- Tags are permanent once pushed; never reuse or move existing tags
- If a release is missing a fix, increment version and create new tag
- Force pushing (branches or tags) requires explicit user confirmation
- Added to Section 10 (Release Workflow) for AI agents
init() called updatePeerList() directly but never set lastKnownPeers.
On the first PEER_UPDATE message (heartbeat, play/pause, any state
change), detectPeerChanges() saw lastKnownPeers=[] and treated ALL
peers including self as newly joined — triggering 'name joined the room'
toast on every action while the popup was open.
- FORCE_SYNC_TIMEOUT in shared/constants.js was 5000 but all code uses
8500ms. Update constant to 8500 and reference it in background.js
instead of hardcoded values (4 occurrences)
- Add errorToken counter in popup showError() so stale 5s timeout
doesn't clear styling of a newer error that arrived in between
- Fix EVENT_ACK handler indentation in server/index.js to match
surrounding socket.on handlers
- Add 10s timeout to ensureState() so extension doesn't hang forever if
chrome.storage.session.get() never calls back (storage API failure)
- Skip emit(LEAVE_ROOM) in leaveOldRoomIfSwitching when socket is down;
server already cleaned up via disconnect handler, avoid queued no-op
- Persist cleared peers to storage in socket.onclose to prevent stale
peer list restoration on service worker restart
- Store and clean up forceSyncReset setTimeout in popup unload handler
and when force_sync_execute completes
- Store real client IP from x-forwarded-for on socket for use in JOIN_ROOM
auth rate limiting (was using proxy IP, breaking brute-force protection)
- Remove clearEpisodeLobbyState() from socket.onclose to preserve lobby
across brief disconnects; ensureState() recovers lobby + timeout on reconnect
- Lobby is still properly cleared on intentional LEAVE_ROOM and room switch
Two parallel JOIN_ROOM to a non-existent room could race past each
other during bcrypt.hash, causing the second to overwrite the first
room (password hash lost). The lock was read but never written.
- Create lock promise before bcrypt.hash async boundary
- Release in finally to cover all exit paths (success, MAX_ROOMS, error)
- Concurrent waiters now correctly await existing room creation
- Add renderEmpty() helper with icons and hints for peers/history/logs/rooms
- Implement onboarding tour (4 steps) with overlay, dots, skip/next
- Dev tab logs only poll when tab is visible (isDevTabVisible flag)
- Expand username generation from 12/12 to 30/30 adjective-noun pairs
- Update ROADMAP.md to remove implemented features
- popup.js: guard peers/state.acks in updateLastActionUI()
- popup.js: guard state.duration in refreshDebugInfo()
- popup.js: guard elements.lobbyPeerStatus in updateLobbyUI()
- background.js: use Array.isArray() for currentRoom.peers.length checks