diff --git a/.planning/phases/05-cloud-storage-backends/05-10-SUMMARY.md b/.planning/phases/05-cloud-storage-backends/05-10-SUMMARY.md new file mode 100644 index 0000000..6a21ed1 --- /dev/null +++ b/.planning/phases/05-cloud-storage-backends/05-10-SUMMARY.md @@ -0,0 +1,119 @@ +--- +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 |