Skip to content

Web fixes - #266

Open
diocas wants to merge 8 commits into
cernboxfrom
dev
Open

Web fixes#266
diocas wants to merge 8 commits into
cernboxfrom
dev

Conversation

@diocas

@diocas diocas commented Aug 14, 2026

Copy link
Copy Markdown
  • 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)

diocas and others added 8 commits August 12, 2026 22:37
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>
@diocas
diocas requested a review from Gerry-Cern August 14, 2026 07:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant