Conversation
diocas
commented
Aug 14, 2026
- Ensure that the EOS virtual space is treated just as a fallback (try to load all the other spaces first)
- Load spaces async (instead of loading projects on startup) and ensure we try first projects, then mount points, then fallback
- When creating a new file, open it in the current tab (like a normal opening)
The synthetic EOS space (driveType 'explorer', driveAlias 'eos') is meant to be a fallback for locations no real space covers, but 'eos' is a URL-prefix of every real EOS driveAlias (eos/user/..., eos/project/...), so it was winning over real spaces: - getSpaceByDriveAliasAndItem used .find(), so the first array match won. The fallback is appended in loadSpaces while mount points are appended later, meaning it beat every mount point even once they were loaded. - Because the fallback always matched, `!matchingSpace` was never true and the lazy loadMountPoints() recovery was dead code on EOS - the mount point that should have won was frequently not even fetched. - isOnlyItemPathChanged did a bare startsWith() on the current driveAlias, so once the fallback was resolved it stayed latched for the rest of the component's life, and no navigation could escape it. - reloadProjectSpaces filtered the fallback out and nothing re-added it, so opening Spaces/Trash/the project picker destroyed it until a page reload. Resolution is now an explicit chain: already-known real spaces, then a lazy mount point fetch and retry, and only then the fallback. Matching prefers the most specific (longest) driveAlias, so array order no longer decides. Space membership is compared by whole path segments via a new isSegmentPrefix helper, which also fixes eos/project/c/cern swallowing eos/project/c/cernbox/x. Adds isFallbackSpaceResource so the drive type is checked in one place instead of via string literals spread across several files. Also fixes the spaces store test harness: getWrapper never awaited an async setup, so those assertions ran after the test had already returned and failures were never attributed to them. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…en route getMatchingSpace short-circuited on the ambient `spacesStore.currentSpace`: whenever the EOS fallback space was the open route, every resource resolved to it, regardless of which space the resource actually came from and ignoring an explicitly passed space. Worst case is data-destructive rather than a failed request. Copying files from a project space and pasting them while browsing the fallback made useFileActionsPaste resolve the clipboard entries to the fallback, so the cross-space guard in ResourceTransfer (sourceSpace.id !== targetSpace.id) no longer fired and moveFiles/copyFiles joined /files/<user>/eos with a path that is relative to a different space's root. SaveAsModal (destination from the embed picker) and useOpenEmptyEditor had the same exposure. Resources listed by the fallback do need it - their path is relative to its webdav root, so resolving them by storage id would be equally wrong. But that is a property of the resource, not of the current route: `webDavPath` records which space listed it (/files/<user>/eos/... for the fallback, /spaces/<id>/... for every real space, including graph-derived shares and favourites). Matching on that resolves fallback-listed resources to the fallback while leaving clipboard entries, picker results and search hits with the space they came from. It also needs no new state and survives the postMessage hop SaveAsModal relies on. An explicitly passed space keeps taking precedence, as before. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Spaces were fetched in one bootstrap batch: personal and project in parallel on login, mount points lazily on first access. Every session therefore paid for listing every project drive - potentially hundreds on CERNBox - even when it never opened one. The personal space is still loaded up front, since virtually every part of the app needs it. Project and mount point spaces are now fetched only when something actually needs them: `loadSpacesByType` loads a type once per session, shares in-flight requests between concurrent callers so a type is never fetched twice, and deduplicates by id and driveAlias. The drive resolver walks personal -> project -> mount point, retrying after each type lands, and only falls back to the catch-all once every type has been tried - so a location is never attributed to the fallback merely because its real space hadn't been fetched yet. Views that show or depend on those types refresh them on entry, so newly created projects and newly accepted shares appear without a page reload: the Spaces overview and Shared with me both refresh project and mount point spaces, the latter via a new `force` flag that re-fetches an already initialized type (additively - known spaces are not duplicated). `spacesLoading` keeps meaning "the initial bootstrap is running" and is deliberately not flipped by on-demand loads: the application layout swaps the whole router view for a spinner while it is true, so a view that refreshes spaces on mount would unmount and remount itself in an endless loop. `spacesInitialized` likewise keeps meaning "bootstrap finished", not "everything is loaded" - redefining it would leave `areSpacesLoading` permanently true and hang the drive resolver. `reloadProjectSpaces` now also evicts same-alias share/mountpoint entries: with lazy loading those can arrive before the project spaces, and a synthesized share root must never shadow the real project space it was derived from. `initializedTypes` is exposed as state rather than through `isTypeInitialized` because createTestingPinia stubs every function a store returns. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
AppWrapper's `loadFileTask` bailed out inside its `try`, so the `finally` cleared `loading` even though nothing had been loaded. The slot then rendered with `resource` still undefined. The wrapped app gets the resource as a prop and, in the external app's case, only ever acts on the first value it sees: its props watcher bails when the resource is missing, and every later invocation compares against `oldProps` - which, being a `deep` watch on a reactive props object, is the same object as `newProps`, so the comparison always reports "unchanged". Mounting it with an undefined resource therefore meant it never called app/open at all. This only surfaced once space resolution became asynchronous. Personal and project spaces are usually already loaded, so resolution completes before the first render and the app mounts with its resource. A share reached by its owner path needs the mount point drives fetched first, which leaves a window where the context exists but the resource does not - exactly the window the premature `loading = false` exposed. Moving the guard ahead of the try/finally keeps `loading` set until the task actually runs, so the slot renders once, with a resource. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The Spaces nav item listed every project space *and* the catch-all fallback space in `activeFor`. Application.vue turns those into hrefs and marks the item active when the current href merely starts with one of them - and the fallback's driveAlias is `eos`, a prefix of every real eos driveAlias. So any `eos/...` route matched it. That alone wasn't enough to light the item up, because `isActive` also has to pass. But `currentSpace` is null for a moment on every navigation (the outgoing route's resolver clears it in onUnmounted, racing the incoming one), and `isActive` treats "no space" as active - it has to, since the projects overview itself never resolves a space. During that window both conditions held, so opening a share showed "Shares" and "Spaces" highlighted at the same time. Dropping the fallback from `activeFor` leaves the href check meaningful: only real project paths, which can't prefix-match a share or personal path. The clause predates the fallback space actually existing, so it was matching nothing until now. Side effect: browsing the fallback space no longer highlights any nav item. That seems right - it isn't reachable from the nav - but it is a behaviour change worth knowing about. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…oints Favourites can live in any space. When one had no matching space loaded, the list went straight to fetching mount points - which made sense when project spaces were always loaded at bootstrap, so a missing space could only be a share. Project spaces are now loaded on demand, so a favourite in a project also looks "missing". That both triggered the expensive mount point listing needlessly and failed to resolve the favourite, since the project space it actually belongs to was never fetched. Project spaces are now resolved first, and mount points only if something is still unaccounted for - which is often not the case at all. The mount point load stays unawaited, as before, so the list renders immediately and the space labels fill in when it arrives. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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.