fix(editor): show an error instead of an endless skeleton when metadata fails#2044
Open
DPS0340 wants to merge 1 commit into
Open
fix(editor): show an error instead of an endless skeleton when metadata fails#2044DPS0340 wants to merge 1 commit into
DPS0340 wants to merge 1 commit into
Conversation
…ta fails
getRecordingMetaByPath returns Result<RecordingMeta, String> and fails
whenever recording-meta.json is missing or unparseable. The editor's
status derivation only looked at the query's data:
const meta = rawMetaQuery.data;
if (!meta) return "loading" as const;
A failed query has no data either, so a failure was indistinguishable
from a query still in flight. The Switch had no arm for the error case,
so it fell through to the spinner and stayed there -- which is CapSoftware#1812:
the editor opens on the loading skeleton and never leaves it.
EditorErrorScreen already existed for this, complete with a recovery
path; it was simply unreachable on a metadata failure. It is now wired
to the new error state.
The error also has to outrank the lockedToImporting latch. That latch
exists so the UI doesn't flicker while metadata is re-read, but if the
metadata stops being readable mid-import there is nothing to wait for,
and it would strand the import screen the same way.
Extracted both derivations into import-status.ts so the state machine is
testable without a running editor, and added 8 vitest cases. Confirmed
they fail without the fix: reverting just the two error branches gives
3 failed | 5 passed.
Comment on lines
+17
to
+20
| if (query.isError) return "error"; | ||
| if (!query.data) return "loading"; | ||
|
|
||
| const meta = query.data as { status?: unknown }; |
There was a problem hiding this comment.
Small hardening: "status" in meta will throw if query.data ever isn't an object (unexpected payload / bad deserialization). Guarding the shape keeps this from turning into a runtime crash.
Suggested change
| if (query.isError) return "error"; | |
| if (!query.data) return "loading"; | |
| const meta = query.data as { status?: unknown }; | |
| if (query.isError) return "error"; | |
| if (!query.data) return "loading"; | |
| if (typeof query.data !== "object" || query.data === null) return "ready"; | |
| const meta = query.data as { status?: unknown }; |
Comment on lines
+205
to
+210
| <Match when={importStatus() === "error"}> | ||
| <EditorErrorScreen | ||
| error={getEditorErrorMessage(rawMetaQuery.error)} | ||
| projectPath={projectPath() ?? ""} | ||
| /> | ||
| </Match> |
There was a problem hiding this comment.
EditorErrorScreen uses projectPath for recover/open-folder; passing "" feels a bit risky. Might be nicer to gate this branch on projectPath() like the importing/ready branches.
Suggested change
| <Match when={importStatus() === "error"}> | |
| <EditorErrorScreen | |
| error={getEditorErrorMessage(rawMetaQuery.error)} | |
| projectPath={projectPath() ?? ""} | |
| /> | |
| </Match> | |
| <Match | |
| when={importStatus() === "error" ? (projectPath() ?? null) : null} | |
| > | |
| {(path) => ( | |
| <EditorErrorScreen | |
| error={getEditorErrorMessage(rawMetaQuery.error ?? "Unknown error")} | |
| projectPath={path()} | |
| /> | |
| )} | |
| </Match> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #1812.
What @shreyastaware saw
Open a previous recording → the editor shows the loading skeleton → it never leaves. Their screenshot is the skeleton, and they noted "couldn't find logs in macOS", so there was nothing to report beyond "it hangs".
Why it hangs forever
getRecordingMetaByPathisResult<RecordingMeta, String>— it fails wheneverrecording-meta.jsonis missing or unparseable (RecordingMeta::load_for_projectdoesread_to_stringthenserde_json::from_str, both fallible). But the status derivation only consulteddata:A failed query has no
dataeither, so failure was indistinguishable from a query still in flight. The<Switch>had arms forimportingandreadyand nothing for failure, so it fell through to the spinner fallback — permanently. No error, no log, no way forward.EditorErrorScreenalready exists for exactly this, complete with a recovery path and a "reveal in folder" button. It was simply unreachable on a metadata failure. This wires it up.The second half
The error also has to outrank the
lockedToImportinglatch. That latch exists so the UI doesn't flicker while metadata is re-read, but if the metadata stops being readable mid-import there is nothing left to wait for — without this it would strand the import screen for the same reason it used to strand the skeleton.Verification
Both derivations are extracted into
import-status.tsso the state machine is testable without a running editor, and covered by 8 vitest cases.Confirmed the tests actually catch the bug rather than just passing: reverting only the two error branches in the extracted module gives
and restoring them gives
8 passed.npx tsc --noEmit -p apps/desktop/tsconfig.json→ 0 errors;biome checkclean.What I have not done: reproduce the hang on a real build. I can't compile the desktop crate here — the build script needs
binaries/cap-muxer-*andtarget/native-deps/Spacedrive.framework, and it fails identically on unmodifiedmain(verified by stashing). So the diagnosis is traced statically from the Rust command through to the<Switch>, and the fix is covered by unit tests rather than an end-to-end run.One thing worth a maintainer's judgement: I did not try to work out why @shreyastaware's
recording-meta.jsonwas unreadable. This makes the failure visible and offers recovery, but if there is an underlying cause producing broken recordings on macOS 26.4.1, that is a separate issue and this shouldn't be taken as closing it.