Skip to content

[#442] Fix locations tab websocket sync for shot location changes#368

Merged
progressions merged 4 commits into
masterfrom
feature/442-websocket-location-updates
Feb 8, 2026
Merged

[#442] Fix locations tab websocket sync for shot location changes#368
progressions merged 4 commits into
masterfrom
feature/442-websocket-location-updates

Conversation

@progressions

@progressions progressions commented Feb 8, 2026

Copy link
Copy Markdown
Owner

Summary

Fixes a regression where location changes made from shot counter character details did not consistently appear in the encounter Locations tab in real time.

Related Fizzy Card: #442 - Websocket Location updates

Changes

  • Harden useLocations websocket handling to accept both array and wrapped payload shapes
  • Track websocket fight_id updates to avoid cross-fight location updates
  • Add encounter-driven fallback refresh when shot location_id assignments change
  • Keep fallback refresh non-blocking to avoid loading flicker in the panel

Files Changed

  • src/hooks/useLocations.ts - Improve real-time location sync robustness and add encounter fallback refetch

Test Plan

  • Run npx eslint src/hooks/useLocations.ts
  • In an encounter, open Locations tab and move a character location from shot counter character detail
  • Verify Locations tab updates dynamically without manual refresh

Copilot AI review requested due to automatic review settings February 8, 2026 21:27

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Improves the useLocations hook’s real-time synchronization so the encounter Locations tab stays up-to-date when shot location changes occur elsewhere (e.g., shot counter character detail), including additional WebSocket payload handling and a fallback refetch path.

Changes:

  • Extends useLocations WebSocket handling to accept multiple payload shapes and adds additional fight scoping.
  • Adds an encounter-driven fallback that refetches locations when shot→location assignments change.
  • Makes the fallback refetch non-blocking (no loading flicker) by optionally skipping the loading state.
Comments suppressed due to low confidence (3)

src/hooks/useLocations.ts:153

  • fetchLocations(false) is triggered on every detected signature change from the encounter subscription, which can cause a burst of GET requests if multiple shot assignments change in quick succession (e.g., dragging several entities or rapid WS updates). Consider coalescing these refetches (debounce/throttle) or guarding against concurrent in-flight getFightLocations calls so the locations panel doesn’t spam the API and risk out-of-order updates overwriting newer state.
    src/hooks/useLocations.ts:156
  • The new WebSocket-handling branches (accepting both array and wrapped payload shapes) plus the encounter-driven fallback refetch add several edge cases (payload shape variations, cross-fight filtering, signature-change detection) but there are currently no unit tests for useLocations (while other hooks in src/hooks/__tests__ are covered). Adding tests that mock subscribeToEntity and client.getFightLocations would help prevent regressions in this real-time sync logic.
  useEffect(() => {
    if (!fightId) return

    const unsubscribe = subscribeToEntity("locations", (data: unknown) => {
      const currentFightId = latestWsFightId.current
      if (currentFightId && currentFightId !== fightId) {
        return
      }

      const locationsData = Array.isArray(data)
        ? (data as Location[])
        : data &&
            typeof data === "object" &&
            "locations" in (data as Record<string, unknown>) &&
            Array.isArray((data as { locations: unknown }).locations)
          ? ((data as { locations: Location[] }).locations ?? [])
          : null

      if (!locationsData) return

      const hasFightIds = locationsData.some(loc => !!loc.fight_id)
      const isForThisFight =
        locationsData.length === 0 ||
        !hasFightIds ||
        locationsData.some(loc => loc.fight_id === fightId)

      if (!isForThisFight) return

      // Update locations without loading state changes to avoid UI flicker.
      setLocations(locationsData)
    })

    return unsubscribe
  }, [fightId, subscribeToEntity])

  return {
    locations,
    loading,
    error,
    refetch: fetchLocations,
  }
}

src/hooks/useLocations.ts:146

  • The fallback refetch subscribes to encounter and builds a signature from encounter.shots using shot.id and shot.location_id, but encounter shots in this codebase are shot groups shaped like { shot, characters, vehicles } (see LocationsPanel’s encounter.shots.forEach((shotGroup) => ...)). As written, shot.id/shot.location_id will be undefined, so this signature won’t change when a character’s location changes, and the refetch won’t reliably run.

Adjust the signature logic to derive a stable mapping from the encounter payload that actually changes on shot-location edits (e.g., per character/vehicle shot_id + their location string), so location changes from the shot counter can trigger the refetch.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@progressions progressions merged commit a2f0041 into master Feb 8, 2026
4 checks passed
@progressions progressions deleted the feature/442-websocket-location-updates branch February 8, 2026 21:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants