Summary
A tab's loading state is cleared only when the readTextFile promise settles. The Rust side performs a synchronous read_to_end inside the async command, so a file whose OS-level read blocks (iCloud/Dropbox online-only placeholder that needs to download, hung network mount, TCC consent gate, special file) pins the tab on "Loading..." forever. There is no timeout anywhere in the chain, and clicking the file again does not retry: the tab is found by path and merely re-activated. Recovery requires closing the tab or restarting the app. Each stuck read also occupies a blocking slot in the async runtime, so several stuck tabs (e.g. restored at startup from a dead mount) can stall other IPC.
Reproduction (release build of f485396, macOS)
- Create a file whose read blocks indefinitely. Deterministic stand-in for the cloud-placeholder case:
mkfifo workspace/research/stuck-doc.md.
- Open it in the app (Open With / file association).
- The tab shows "Loading..." indefinitely (screenshot taken 10 s after open; still identical after 10+ minutes). Clicking the file again does nothing.
Root cause
loading cleared only when the read settles: src/hooks/useTabs.ts:105-125; no timeout, no cancellation.
readTextFile is a single invoke (@tauri-apps/plugin-fs dist-js/index.js, readTextFile), and the plugin does a blocking file_handle.read_to_end inside the async command (tauri-plugin-fs-2.5.1/src/commands.rs:556-591), so a blocking syscall pends the promise forever and holds a runtime thread.
- No retry on reopen:
openInActive returns early via setActiveId when a tab with that path exists (src/hooks/useTabs.ts:139-143).
- Same pattern for the workspace scan:
scan_markdown is spawn_blocking with no timeout (src-tauri/src/tauri_api/mod.rs:28-34), so a hung filesystem leaves the sidebar scanning forever.
Proposed fix
- Add a timeout to the tab load (and surface a retriable error state in the tab instead of an eternal spinner).
- Make activating an existing tab retry the load when the previous load errored or is still pending past the timeout.
- Consider a timeout or cancellation for the scan command, and non-blocking reads (or a bounded thread pool) on the Rust side.
Screenshot
Tab pinned on "Loading..." (unchanged after 10+ minutes; file exists, its read blocks):

Summary
A tab's loading state is cleared only when the
readTextFilepromise settles. The Rust side performs a synchronousread_to_endinside the async command, so a file whose OS-level read blocks (iCloud/Dropbox online-only placeholder that needs to download, hung network mount, TCC consent gate, special file) pins the tab on "Loading..." forever. There is no timeout anywhere in the chain, and clicking the file again does not retry: the tab is found by path and merely re-activated. Recovery requires closing the tab or restarting the app. Each stuck read also occupies a blocking slot in the async runtime, so several stuck tabs (e.g. restored at startup from a dead mount) can stall other IPC.Reproduction (release build of f485396, macOS)
mkfifo workspace/research/stuck-doc.md.Root cause
loadingcleared only when the read settles:src/hooks/useTabs.ts:105-125; no timeout, no cancellation.readTextFileis a single invoke (@tauri-apps/plugin-fs dist-js/index.js,readTextFile), and the plugin does a blockingfile_handle.read_to_endinside the async command (tauri-plugin-fs-2.5.1/src/commands.rs:556-591), so a blocking syscall pends the promise forever and holds a runtime thread.openInActivereturns early viasetActiveIdwhen a tab with that path exists (src/hooks/useTabs.ts:139-143).scan_markdownisspawn_blockingwith no timeout (src-tauri/src/tauri_api/mod.rs:28-34), so a hung filesystem leaves the sidebar scanning forever.Proposed fix
Screenshot
Tab pinned on "Loading..." (unchanged after 10+ minutes; file exists, its read blocks):