--- 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 |