#5719 ("allow 2nd (and 3rd etc) saveas to work via doc reinit", merged in 9.1.1) fixed the original bug where a second Save As in one session silently no-opped, but introduced two related defects in the reload path it added.
1. Route/identity is mutated before the reload is confirmed, spawning a redundant second editor instance
switchToSavedAsFile() in src/view/Office.vue calls FilesAppIntegration.changeFilesRoute(node.fileid) before await this.load(). Changing the route causes the Files app Viewer to mount a second Office component for the new fileid, which independently runs its own mounted() → load() cycle. So a single Save As can produce two concurrent host-side loads (and two WOPI token mints) for the same document, and nothing rolls the route back if the second load fails — the browser URL and "current file" state end up pointing at a document that never finished loading.
2. load()'s failure path never arms the loading-timeout, and is never caught
In load(), this.loadingTimeout = setTimeout(...) — the only mechanism that converts a stuck LOADING state into a visible FAILED error — is set after the awaited axios.post(generateUrl('/apps/richdocuments/token'), ...) call. If that POST rejects, the function returns before the timeout is ever armed. load() is called bare (no try/catch) from mounted(), so the rejection becomes an unhandled promise rejection with no user-visible error: the loading spinner stays up indefinitely.
Combined effect
Any transient failure of POST /apps/richdocuments/token during the second, redundant load (finding 1) — e.g. a brief backend hiccup, or eventual consistency in a clustered/load-balanced deployment right after the file-creating PutRelativeFile write — produces a permanently stuck editor with no error shown, on what looks to the user like the very first Save As of the session. We saw this reproduce reliably behind a load balancer with multiple app-server nodes; not reproducible on a single-node install.
Suggested fix
- Defer
changeFilesRoute() (and any route/identity mutation) until the load reaches DOCUMENT_READY, so a failed reload doesn't leave a redundant, doomed second instance running — or suppress the Viewer's re-open for a route change the editor itself just initiated.
- Wrap the body of
load() in try/catch and arm the loading-timeout independently of (or before) the /token POST, setting LOADING_STATE.FAILED plus a visible error on any rejection, the way the existing Action_Load_Resp failure branch already does.
Environment observed: Nextcloud 32.x, richdocuments 9.1.1, multiple app servers behind a load balancer.
#5719 ("allow 2nd (and 3rd etc) saveas to work via doc reinit", merged in 9.1.1) fixed the original bug where a second Save As in one session silently no-opped, but introduced two related defects in the reload path it added.
1. Route/identity is mutated before the reload is confirmed, spawning a redundant second editor instance
switchToSavedAsFile()insrc/view/Office.vuecallsFilesAppIntegration.changeFilesRoute(node.fileid)beforeawait this.load(). Changing the route causes the Files app Viewer to mount a secondOfficecomponent for the new fileid, which independently runs its ownmounted()→load()cycle. So a single Save As can produce two concurrent host-side loads (and two WOPI token mints) for the same document, and nothing rolls the route back if the second load fails — the browser URL and "current file" state end up pointing at a document that never finished loading.2.
load()'s failure path never arms the loading-timeout, and is never caughtIn
load(),this.loadingTimeout = setTimeout(...)— the only mechanism that converts a stuckLOADINGstate into a visibleFAILEDerror — is set after the awaitedaxios.post(generateUrl('/apps/richdocuments/token'), ...)call. If that POST rejects, the function returns before the timeout is ever armed.load()is called bare (no try/catch) frommounted(), so the rejection becomes an unhandled promise rejection with no user-visible error: the loading spinner stays up indefinitely.Combined effect
Any transient failure of
POST /apps/richdocuments/tokenduring the second, redundant load (finding 1) — e.g. a brief backend hiccup, or eventual consistency in a clustered/load-balanced deployment right after the file-creatingPutRelativeFilewrite — produces a permanently stuck editor with no error shown, on what looks to the user like the very first Save As of the session. We saw this reproduce reliably behind a load balancer with multiple app-server nodes; not reproducible on a single-node install.Suggested fix
changeFilesRoute()(and any route/identity mutation) until the load reachesDOCUMENT_READY, so a failed reload doesn't leave a redundant, doomed second instance running — or suppress the Viewer's re-open for a route change the editor itself just initiated.load()in try/catch and arm the loading-timeout independently of (or before) the/tokenPOST, settingLOADING_STATE.FAILEDplus a visible error on any rejection, the way the existingAction_Load_Respfailure branch already does.Environment observed: Nextcloud 32.x, richdocuments 9.1.1, multiple app servers behind a load balancer.