Always INSERT new connection rows (never upsert by provider) to allow same-provider multi-account
PUT /connections/{id}/credentials preserves existing password when field omitted
CloudFolderTreeItem uses connectionId prop instead of provider slug everywhere
Cloud Storage sidebar link active only on exact /cloud route
duration
completed
tasks_completed
files_changed
~30 minutes
2026-06-21
3
14
Phase 12 Plan 06: Cloud Connection Navigation and Multi-Account Support Summary
One-liner: UUID-only cloud navigation with same-provider multi-account independence, PUT credential-update endpoint, and route-aware sidebar/breadcrumb selection.
What Was Built
Task 1: Connection-ID native lifecycle
Backend:
Replaced _upsert_cloud_connection (upsert by provider) with _insert_cloud_connection (always INSERT new UUID row). Two POSTs for Nextcloud now create two independent rows with distinct UUIDs and independent encrypted credentials.
Added PUT /api/cloud/connections/{id}/credentials endpoint: owner-scoped (404 on wrong owner), merges submitted fields with existing credentials (password omitted = preserve existing), re-runs validate_cloud_url and health-check before committing (T-12-06-04 SSRF gate), audit-logged, credential-free response.
CloudCredentialModal.submit: calls updateWebDavCredentials(existing.id, ...) on edit (targeting the specific UUID), connectWebDav(...) only for new connections.
SettingsCloudTab: replaced connectionFor(provider) (first-match) with connectionsFor(provider) (all matches), renders each as an independent named row with its own Rename/Edit/Remove actions, plus an always-visible "Add account" / "Connect" button per provider.
utils.js request(): FastAPI validation arrays (detail: [{loc, msg}]) now summarised into a concise field: message string instead of bare HTTP 422.
New API export updateWebDavCredentials in cloud.js.
Task 2: UUID-only navigation and sidebar repair
CloudProviderTreeItem: uses getCloudFoldersByConnectionId(connection.id, '') (never getCloudFolders(provider, ...)), routes to /cloud/{connection.id}/root, exposes isActive computed from exact route prefix match.
CloudFolderTreeItem: accepts connectionId prop (replaces provider slug), uses getCloudFoldersByConnectionId(connectionId, folder.id) for children, routes to /cloud/{connectionId}/{folder.id}, isActive = exact path match.
AppSidebar: Cloud Storage nav-link active only on $route.path === '/cloud' (exact), not startsWith — prevents generic highlight while inside a connection.
CloudFolderView was already connection-ID based (prior plan); breadcrumbs already use connectionRoot prop.
Version bumped 0.2.1 → 0.2.2 in backend/main.py and frontend/package.json.
Verification Against UAT Failures
UAT Symptom
Fixed by
POST Nextcloud 201 → sidebar browse → 422 (UUID endpoint received provider slug)
CloudProviderTreeItem now passes connection.id not connection.provider
Two same-provider connections not possible (upsert merged them)
_insert_cloud_connection always creates a new row
Settings showed only first Nextcloud account
connectionsFor() renders all accounts per provider
Editing one connection could affect another same-provider row
PUT /connections/{id}/credentials is owner + ID scoped
Bare HTTP 422 shown to user on validation failure
utils.js now formats FastAPI validation arrays into field messages
Deviations from Plan
None — plan executed exactly as written.
Known Stubs
None — all cloud navigation uses real connection IDs from API responses.
Threat Flags
None — new PUT /connections/{id}/credentials endpoint follows the same security pattern as existing endpoints: owner scope via _get_owned_connection, SSRF validation, health-check, credential-free response, audit log.