[episodes] Harden episode-scoped loads after the wrong-episode fix - #2205
Conversation
A shot, asset, edit or sequence announced by a socket event while its list was loading was fetched first and parked behind the list promise. An entity deleted during that window was then re-inserted as a ghost row: the delete handler only removes what the map holds, and the parked payload landed after the response that dropped it. Fetch after the list settled instead: the payload is younger than the response by construction and a deleted entity fails the fetch. A displayed entity is still refreshed right away. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SzHbFrhkjczjtxYnwhL5N2
loadEdits only checked the production of a late response, and CLEAR_EDITS lets a same-production load start without queueing behind the one in flight. A slow episode response could then land under the key of a newer production-wide load and stay on screen. Compare the recorded loading key instead, which covers the production switch too. CLEAR_EDITS reuses LOAD_EDITS_START rather than repeating its assignments. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SzHbFrhkjczjtxYnwhL5N2
No consumer told the shared marker from the partial one: both only made the key differ from the one the pages rebuild. Keep a single marker. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SzHbFrhkjczjtxYnwhL5N2
The episodes watcher redirected the detail page of a remotely deleted episode to another episode's page, with no way back. Send it to the episodes list instead, on a live deletion and on a stale direct link. The episode refetch is gated on the store's loaded flag rather than on the list length, which live episode:new events can fill before the response is in. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SzHbFrhkjczjtxYnwhL5N2
The playlist page rebuilt a playlist under all or main against the map a shots load had just emptied. The breakdown page rebound its production snapshot after the episodes fetch, so a production switched meanwhile kept the old column widths. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SzHbFrhkjczjtxYnwhL5N2
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SzHbFrhkjczjtxYnwhL5N2
loadAssets awaits the episodes when a TV show has none loaded. A production switch during that fetch let it resume with the production left: LOAD_ASSETS_START wiped the new production's assets and raised a loading flag that no response lowered. Every later loadAssets then queued behind that settled load and re-dispatched in a loop of microtasks, freezing the tab. Give up when the production changed while waiting. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SzHbFrhkjczjtxYnwhL5N2
The redirect to the episodes list only covered the episode detail page on a live deletion. A task page of that episode still got another episode under the same URL. A direct link to either page went through the production configuration instead, which picked a stand-in episode as well. Treat the episode detail page and its task pages alike, on both paths. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SzHbFrhkjczjtxYnwhL5N2
loadAssets and loadShots only checked the production of a late response, the gap loadEdits had. A production switch and back during a slow load let the old response land under the key of the load started on return: the Assets page kept another episode's assets under all without refetching. On shots, the discarded response also cleared the flag and the key of the load in flight, so live shot:new events were dropped afterwards. Compare the recorded loading key instead, which covers the production switch too. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SzHbFrhkjczjtxYnwhL5N2
A live update refreshes a displayed shot, asset, edit or sequence without waiting. When its deletion event landed before the refresh response, the entity was gone from the map and the response inserted it back as a ghost row. Skip the insertion when the entity was displayed as the refresh started: it was deleted since, or dropped by a list load whose own response decides. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SzHbFrhkjczjtxYnwhL5N2
|
There are two points to correct: src/store/modules/assets.js · The two markers were not equivalent: the in-flight dedup in tests/unit/store/assets.spec.js · This test passes on main too: with no current episode and an empty episode list, |
The in-flight dedup in loadAssets compares the whole loading key. With a single partial marker, the schedule under the all pseudo-episode and the production-wide load of the breakdown and the concepts recorded the same P/all#partial key: a breakdown opened while the schedule was loading adopted its dataset, without tasks or shared assets, and nothing refetched. Restore the #shared marker so the second load waits, then fetches its own. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01J8vdEJjSK2MsTYP3TVX6a2
The test stopped on the no episode path before it reached the production guard, so it passed without it. Resolve an episode in the loadEpisodes stub, as the real action does, and mock getAssets. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01J8vdEJjSK2MsTYP3TVX6a2
Problem
Solution