From 13479cd194cd992c67805093e59f8b1dd1b5f3a2 Mon Sep 17 00:00:00 2001 From: "Somhairle H. Marisol" Date: Mon, 21 Sep 2026 06:05:15 +0800 Subject: feat(viewer): add user retry for failed step loads and regressions for same-run ordering and replay invalidation --- src/SomhairlesDream.Frontend/App.fs | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) (limited to 'src') diff --git a/src/SomhairlesDream.Frontend/App.fs b/src/SomhairlesDream.Frontend/App.fs index 6e07798..549b695 100644 --- a/src/SomhairlesDream.Frontend/App.fs +++ b/src/SomhairlesDream.Frontend/App.fs @@ -60,6 +60,7 @@ let mutable loadGeneration = 0 let mutable pendingLoad: (int * LoadOwner) option = None let mutable loadedStep: LoadOwner option = None let mutable viewMode = FrameMode +let mutable failedLoad: LoadOwner option = None let mutable displayedNode: ThreeNode option = None let mutable freezeRotation = false @@ -132,6 +133,7 @@ let clearGroup (group: ThreeNode) = let invalidateLoads () = loadGeneration <- loadGeneration + 1 pendingLoad <- None + failedLoad <- None let renderFrame (frame: ReplayFrame) = selectedFrame <- frame @@ -242,6 +244,14 @@ let fallbackView () = setText "viewport-note" "WebGL 视图待命 · 已保留重建数据" (element "viewport-panel").classList.add("viewport-fallback") +let setNoteRetryable (retryable: bool) = + let note = element "viewport-note" + + if retryable then + note.classList.add("is-retryable") + else + note.classList.remove("is-retryable") + let showFrameMetadata (frame: ReplayFrame) = setText "mesh-label" frame.Label setText "timeline-current" (sprintf "V%02d · %s" frame.Version frame.Label) @@ -262,6 +272,8 @@ let startStepLoad (view: ViewState) (owner: LoadOwner) = loadGeneration <- generation pendingLoad <- Some(generation, owner) viewMode <- StepMode + failedLoad <- None + setNoteRetryable false let loader = createGLTFLoader () let projectId = JS.encodeURIComponent owner.ProjectId let runId = JS.encodeURIComponent owner.RunId @@ -285,6 +297,8 @@ let startStepLoad (view: ViewState) (owner: LoadOwner) = if pendingLoad = Some(generation, owner) then pendingLoad <- None loadedStep <- None + failedLoad <- Some owner + setNoteRetryable true setText "viewport-note" (sprintf "管线几何加载失败 · %s" owner.Path)) let loadStepArtifact (view: ViewState) (stepPath: string) = @@ -297,6 +311,12 @@ let loadStepArtifact (view: ViewState) (stepPath: string) = | Some (_, pending) when pending = owner -> () | _ -> startStepLoad view owner +let retryFailedLoad () = + match viewState, failedLoad with + | Some view, Some owner when owner.ProjectId = currentProjectId && owner.RunId = currentRunId -> + loadStepArtifact view owner.Path + | _ -> () + let updateState (payload: SnapshotPayload) = let status = statusName payload setStatusClass status @@ -474,6 +494,7 @@ let boot () = fallbackView () (element "replay-button").addEventListener("click", fun _ -> toggleReplay ()) + (element "viewport-note").addEventListener("click", fun _ -> retryFailedLoad ()) (element "run-controls").addEventListener("submit", fun event -> event.preventDefault() startRun (selectedRun ())) -- cgit v1.2.3