using DeepDrftModels.DTOs; using DeepDrftPublic.Client.ViewModels; using Microsoft.AspNetCore.Components; namespace DeepDrftPublic.Client.Pages; public partial class TrackDetail : ComponentBase, IDisposable { private const string PersistKey = "track-detail"; [Parameter] public required string EntryKey { get; set; } [Inject] public required TrackDetailViewModel ViewModel { get; set; } [Inject] public required PersistentComponentState PersistentState { get; set; } private PersistingComponentStateSubscription _persistingSubscription; // The entry key the ViewModel currently holds. Tracks param-only navigations // (e.g. /track/A -> /track/B) which reuse this component instance and fire // OnParametersSet without re-running OnInitialized — without this, the page keeps // the prior track and Play streams the wrong audio. private string? _loadedEntryKey; protected override void OnInitialized() // Carry the prerendered track across the prerender -> interactive (WASM) seam. // Without this, the WASM pass gets a fresh scoped ViewModel, re-renders the // skeleton, and re-fetches. Mirror the TracksView bridge: persist on the way // out of prerender, restore on the interactive pass, and only fetch on a miss. => _persistingSubscription = PersistentState.RegisterOnPersisting(PersistTrack); protected override async Task OnParametersSetAsync() { // Re-run whenever the route key changes. Component instances are reused across // same-template navigations, so the load decision must live here, not in // OnInitialized (which fires once per instance). if (_loadedEntryKey == EntryKey) return; // Capture the key synchronously before any await so that a re-entrant call // (rapid navigation or a re-render that changes EntryKey while Load is in flight) // sees the correct guard state. Without this, a second OnParametersSetAsync // for the same EntryKey would bypass the guard above and start a second Load, // causing two ViewModel.Load calls to race on the single scoped instance. _loadedEntryKey = EntryKey; // Guard the bridge on the key: a payload for a different track must not seed this // page (stale-bridge bleed across navigation). if (PersistentState.TryTakeFromJson(PersistKey, out var restored) && restored is not null && restored.EntryKey == EntryKey) { ViewModel.Track = restored; ViewModel.NotFound = false; ViewModel.IsLoading = false; } else { await ViewModel.Load(EntryKey); } } private Task PersistTrack() { if (ViewModel.Track is not null) { PersistentState.PersistAsJson(PersistKey, ViewModel.Track); } return Task.CompletedTask; } public void Dispose() => _persistingSubscription.Dispose(); }