---
phase: "12"
plan: "06"
subsystem: cloud-navigation
tags: [cloud, navigation, multi-account, uuid, security]
dependency_graph:
  requires: ["12-05"]
  provides: [connection-id-native-lifecycle, uuid-only-navigation, multi-account-settings]
  affects: [backend/api/cloud/connections.py, frontend/src/components/cloud, frontend/src/components/settings]
tech_stack:
  added: []
  patterns: [connection-ID-based-routing, owner-scoped-credential-update, always-insert-pattern]
key_files:
  created:
    - frontend/src/components/cloud/__tests__/CloudProviderTreeItem.test.js
    - frontend/src/components/cloud/__tests__/CloudFolderTreeItem.test.js
    - frontend/src/components/layout/__tests__/AppSidebar.test.js
  modified:
    - backend/api/cloud/connections.py
    - backend/tests/test_cloud.py
    - frontend/src/api/cloud.js
    - frontend/src/api/utils.js
    - frontend/src/components/cloud/CloudCredentialModal.vue
    - frontend/src/components/cloud/CloudProviderTreeItem.vue
    - frontend/src/components/cloud/CloudFolderTreeItem.vue
    - frontend/src/components/layout/AppSidebar.vue
    - frontend/src/components/settings/SettingsCloudTab.vue
    - backend/main.py
    - frontend/package.json
decisions:
  - "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"
metrics:
  duration: "~30 minutes"
  completed: "2026-06-21"
  tasks_completed: 3
  files_changed: 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.
- Backend tests: `test_same_provider_connections_are_independent` proves two distinct UUIDs; `test_credential_update_wrong_owner_returns_404` covers T-12-06-01 IDOR.

**Frontend:**
- `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.

### Task 3: Full suites, build, version, docs

- All 516 backend tests pass (7 xfailed, 17 skipped — pre-existing).
- All 334 frontend tests across 42 files pass.
- Production build clean (610 ms).
- npm audit: 0 vulnerabilities.
- 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.

## Self-Check: PASSED

- `backend/api/cloud/connections.py` — modified, `_insert_cloud_connection` present
- `PUT /connections/{id}/credentials` endpoint registered in router
- `frontend/src/components/cloud/CloudProviderTreeItem.vue` — uses `getCloudFoldersByConnectionId`
- `frontend/src/components/cloud/CloudFolderTreeItem.vue` — uses `connectionId` prop
- All commits verified: 731b65e, 057b499, c85e4ab
