---
phase: "05-cloud-storage-backends"
plan: 10
subsystem: "cloud-storage"
tags: [oauth, ui, webdav, nextcloud, gap-closure]
dependency_graph:
  requires: ["05-05", "05-06", "05-07", "05-08", "05-09"]
  provides: ["oauth-json-initiate", "nextcloud-edit-round-trip", "error-state-edit", "confirm-overflow-fix"]
  affects: ["frontend/src/components/settings/SettingsCloudTab.vue", "frontend/src/components/cloud/CloudCredentialModal.vue", "backend/api/cloud.py"]
tech_stack:
  added: []
  patterns: ["fetch-with-bearer-for-oauth", "non-secret-config-endpoint", "vue-watch-edit-pre-population"]
key_files:
  created: []
  modified:
    - backend/api/cloud.py
    - backend/tests/test_cloud.py
    - frontend/src/api/client.js
    - frontend/src/components/settings/SettingsCloudTab.vue
    - frontend/src/components/cloud/CloudCredentialModal.vue
    - frontend/src/components/ui/ConfirmBlock.vue
decisions:
  - "Added GET /api/cloud/connections/{id}/config to expose non-secret WebDAV connection fields (server_url, connection_username) for the edit modal — password never included"
  - "CloudCredentialModal rewritten with full edit-mode support: existing prop, getConnectionConfig() call, showAdvanced/customEndpoint for Nextcloud custom paths"
  - "Updated test_connect_google_drive to expect 200 JSON (was 302 redirect) — regression fix following oauth_initiate behavior change"
metrics:
  duration: "~20 minutes"
  completed: "2026-05-30T09:30:26Z"
  tasks_completed: 2
  files_modified: 6
---

# Phase 05 Plan 10: Cloud UI Gap Closure — OAuth Initiate + Edit Fixes Summary

Fixed four cloud settings UI gaps: OAuth initiate 401, Nextcloud custom endpoint lost on edit, missing Edit button on ERROR rows, and confirmation text overflow.

## Tasks Completed

| Task | Description | Commit | Files |
|------|-------------|--------|-------|
| 1 | Fix OAuth initiate: return 200 JSON {url} instead of 302 redirect | e2e499b | backend/api/cloud.py, backend/tests/test_cloud.py |
| RED | Failing tests for OAuth initiate JSON return | 9b6d3f9 | backend/tests/test_cloud.py |
| 2 | Frontend OAuth fetch, Nextcloud edit fix, Edit on ERROR, text overflow | 87de148 | 5 frontend/backend files |

## What Was Built

**Backend changes:**
- `GET /api/cloud/oauth/initiate/{provider}` now returns `200 JSON {"url": authorization_url}` instead of `302 RedirectResponse`. The Bearer-authenticated frontend can now read the URL and navigate with `window.location.href = data.url` — closing the 401 gap caused by the browser not sending auth headers on bare navigation.
- `GET /api/cloud/connections/{connection_id}/config` — new endpoint returning non-secret WebDAV/Nextcloud connection fields (`server_url`, `connection_username`, never the password) for the edit modal pre-population flow.

**Frontend changes:**
- `client.js`: Added `initiateOAuth(provider)` using `request()` (injects Bearer header, handles 401 → refresh). Added `getConnectionConfig(connectionId)` for edit modal.
- `SettingsCloudTab.vue`: `handleConnect` for OAuth providers now uses `await initiateOAuth()` + `window.location.href = data.url` with error display. Added `handleEdit()` function. Added Edit buttons to ACTIVE and ERROR blocks (non-OAuth providers only). Wrapped all `ConfirmBlock` instances in `div.w-full.overflow-hidden`.
- `CloudCredentialModal.vue`: Full rewrite with edit-mode support — `existing` prop, `getConnectionConfig()` call on open, `serverBase`/`username`/`showAdvanced`/`customEndpoint` refs, computed `autoServerUrl`/`resolvedServerUrl`. Nextcloud watch handler detects when stored `server_url` differs from auto-constructed URL and opens Advanced section with the custom endpoint pre-filled.
- `ConfirmBlock.vue`: Added `break-words` class to message paragraph.

## Test Results

All 25 tests in `test_cloud.py` pass:
- 2 new tests: `test_oauth_initiate_returns_json_url`, `test_oauth_initiate_requires_auth`
- `test_connect_google_drive` updated to expect 200 JSON (was 302 — stale after behavioral change)
- Frontend build: zero errors (1 pre-existing dynamic import warning)

## Deviations from Plan

### Auto-added Missing Critical Functionality

**1. [Rule 2 - Missing] Added GET /api/cloud/connections/{id}/config backend endpoint**
- **Found during:** Task 2 — CloudCredentialModal needs existing server_url to pre-populate edit form
- **Issue:** The plan described `existing.server_url` and `existing.connection_username` as available from the `existing` prop passed from SettingsCloudTab, but `CloudConnectionOut` (the whitelist model) only exposes `id`, `provider`, `display_name`, `status`, `connected_at` — no decrypted credential fields
- **Fix:** Added a dedicated `/config` endpoint that decrypts just the non-secret fields (server_url, username — never password). Added `getConnectionConfig()` to client.js. Modal calls this endpoint when `existing` prop is set.
- **Files modified:** backend/api/cloud.py, frontend/src/api/client.js

**2. [Rule 1 - Bug] Updated test_connect_google_drive to expect 200 JSON**
- **Found during:** Task 1 implementation — existing test expected 302 redirect, which is now 200 JSON
- **Fix:** Updated test to mock `Flow.from_client_config` and assert `resp.status_code == 200` + `data["url"]` starts with Google domain
- **Files modified:** backend/tests/test_cloud.py

**3. [Rule 2 - Missing] Added Edit button to ACTIVE block as well**
- **Found during:** Task 2 — Plan said "mirror the ACTIVE block" for ERROR, but ACTIVE block had no Edit button
- **Fix:** Added Edit button to both ACTIVE and ERROR blocks for non-OAuth providers (Nextcloud/WebDAV)
- **Files modified:** frontend/src/components/settings/SettingsCloudTab.vue

**4. [Rule 2 - Missing] Rewrote CloudCredentialModal with full edit-mode support**
- **Found during:** Task 2 — Plan described fixing a watch handler with specific logic (`serverBase`, `customEndpoint`, `showAdvanced`) that didn't exist yet in the modal
- **Fix:** Added all missing reactive state, the advanced section UI, and the full watch handler with Nextcloud custom endpoint detection
- **Files modified:** frontend/src/components/cloud/CloudCredentialModal.vue

## Known Stubs

None — all functionality is fully wired. The edit modal requires the user to re-enter their password (backend connect_webdav always requires password for health-check). A future enhancement could add a PATCH endpoint that accepts partial credential updates (password optional on edit).

## Threat Flags

| Flag | File | Description |
|------|------|-------------|
| threat_flag: new-endpoint | backend/api/cloud.py | GET /api/cloud/connections/{id}/config — new endpoint decrypting partial credentials. Mitigations: get_regular_user enforced, 404 on wrong-owner (ID enumeration prevention), password field excluded, only applicable to VALID_WEBDAV_PROVIDERS |

## Self-Check: PASSED

| Check | Result |
|-------|--------|
| backend/api/cloud.py exists | FOUND |
| backend/tests/test_cloud.py exists | FOUND |
| frontend/src/api/client.js exists | FOUND |
| SettingsCloudTab.vue exists | FOUND |
| CloudCredentialModal.vue exists | FOUND |
| ConfirmBlock.vue exists | FOUND |
| 05-10-SUMMARY.md exists | FOUND |
| Commit 9b6d3f9 (RED tests) | FOUND |
| Commit e2e499b (GREEN implementation) | FOUND |
| Commit 87de148 (Task 2 frontend) | FOUND |
| JSONResponse in cloud.py | FOUND |
| initiateOAuth in client.js | FOUND |
| handleEdit in SettingsCloudTab.vue | FOUND |
| break-words in ConfirmBlock.vue | FOUND |
| existing prop in CloudCredentialModal.vue | FOUND |
| All 25 tests pass | PASSED |
| Frontend build | ZERO ERRORS |
