--- phase: 05-cloud-storage-backends plan: 07 subsystem: ui tags: [cloud-storage, vue3, pinia, vitest, settings, webdav, oauth, tailwind] # Dependency graph requires: - phase: 05-cloud-storage-backends plan: 06 provides: "backend/api/cloud.py with all 7 endpoints (list, disconnect, OAuth initiate/callback, WebDAV connect, status update) — consumed by frontend API client" provides: - "frontend/src/stores/cloudConnections.js: useCloudConnectionsStore with connections/loading/error state and fetchConnections, disconnect, disconnectAll actions" - "frontend/src/api/client.js: listCloudConnections, disconnectCloud, connectWebDav, updateDefaultStorage API functions" - "frontend/src/views/SettingsView.vue: 3-tab layout (Preferences/AI Configuration/Cloud Storage) with OAuth callback handling and success/error toast" - "frontend/src/components/settings/SettingsCloudTab.vue: all 4 provider rows with status badges, action buttons, REQUIRES_REAUTH banner, disconnect-all" - "frontend/src/components/cloud/CloudCredentialModal.vue: WebDAV/Nextcloud credential modal with authMethod radio toggle" - "frontend/src/components/settings/SettingsPreferencesTab.vue and SettingsAiTab.vue: extracted from original SettingsView" affects: [05-08] # Tech tracking tech-stack: added: [] patterns: - "Pinia composition API store pattern: defineStore with ref() state, async actions — matches existing folders.js pattern" - "vi.mock for Pinia store in component tests: mock the store module directly (no @pinia/testing) — same approach as folders.test.js" - "OAuth callback via URL query params: window.location.search parsed in onMounted; router.replace cleans params after read" - "OAuth initiation via window.location.href redirect: no fetch call needed — FastAPI handles the OAuth code exchange" key-files: created: - frontend/src/stores/cloudConnections.js - frontend/src/stores/__tests__/cloudConnections.test.js - frontend/src/components/settings/SettingsPreferencesTab.vue - frontend/src/components/settings/SettingsAiTab.vue - frontend/src/components/settings/SettingsCloudTab.vue - frontend/src/components/settings/__tests__/SettingsCloudTab.test.js - frontend/src/components/cloud/CloudCredentialModal.vue modified: - frontend/src/api/client.js - frontend/src/views/SettingsView.vue - frontend/package.json - frontend/vite.config.js key-decisions: - "Used vi.mock for store in component tests instead of @pinia/testing (not installed, not in package.json). Mock returns a plain object matching the store's public API — avoids dependency on @pinia/testing while satisfying CLAUDE.md testing requirement (W4)" - "Fixed pre-existing Vite build failure (top-level await in main.js) by adding build.target='esnext' to vite.config.js — esnext natively supports top-level await, cleanest fix with no code changes needed" - "REQUIRES_REAUTH row renders both Reconnect and Remove buttons per UI-SPEC Surface 2; Remove button triggers same ConfirmBlock pattern as ACTIVE/ERROR rows" patterns-established: - "Cloud provider row pattern: 4 providers always shown; connectionFor(providerKey) returns store connection or null; status badge + action button vary by status" - "Inline ConfirmBlock: confirmRemoveId ref tracks which row is in confirm mode; v-if/v-else renders either the action button or ConfirmBlock inline" - "SettingsView OAuth callback: onMounted reads URLSearchParams, sets activeTab='cloud', router.replace clears params, success auto-dismisses via setTimeout(5000)" requirements-completed: - CLOUD-01 - CLOUD-03 - CLOUD-04 - CLOUD-05 - CLOUD-06 # Metrics duration: 14min completed: 2026-05-29 --- # Phase 5 Plan 07: Cloud Storage Frontend UI Summary **Pinia cloudConnections store, 3-tab SettingsView with OAuth callback handling, SettingsCloudTab with 4 provider rows and status badges, and CloudCredentialModal for WebDAV/Nextcloud credential input** ## Performance - **Duration:** 14 min - **Started:** 2026-05-29T06:01:00Z - **Completed:** 2026-05-29T06:15:23Z - **Tasks:** 2 - **Files modified:** 11 ## Accomplishments - Created `useCloudConnectionsStore` Pinia store with `connections`, `loading`, `error` state and `fetchConnections()`, `disconnect(id)`, `disconnectAll()` actions — follows same composition API pattern as `useFoldersStore` - Added 4 cloud API functions to `frontend/src/api/client.js`: `listCloudConnections`, `disconnectCloud`, `connectWebDav`, `updateDefaultStorage` - Rewrote `SettingsView.vue` to a 3-tab layout (Preferences / AI Configuration / Cloud Storage) mirroring `AdminView.vue` tab strip verbatim; `onMounted` reads `?cloud_connected=` and `?cloud_error=` query params and shows toast/banner accordingly - Built `SettingsCloudTab.vue` showing all 4 providers (Google Drive, OneDrive, Nextcloud, WebDAV server) with inline status badges, per-status action buttons, `REQUIRES_REAUTH` yellow banner, inline `ConfirmBlock` for remove confirmation, and "Disconnect all" action - Built `CloudCredentialModal.vue` with server URL, username, `authMethod` radio (app_password / account_password), and password fields; escape/overlay-click dismiss; spinner during save - Extracted `SettingsPreferencesTab.vue` and `SettingsAiTab.vue` from the original flat `SettingsView` ## Task Commits 1. **Task 1: cloudConnections store + API client** - `612d542` (feat) 2. **Task 2: 3-tab SettingsView + all components** - `63a6829` (feat) ## Files Created/Modified - `frontend/src/stores/cloudConnections.js` — Pinia store for cloud connections state - `frontend/src/stores/__tests__/cloudConnections.test.js` — 4 Vitest unit tests (W4) - `frontend/src/api/client.js` — Added cloud storage section (listCloudConnections, disconnectCloud, connectWebDav, updateDefaultStorage) - `frontend/src/views/SettingsView.vue` — Rewritten as 3-tab layout with OAuth callback handling - `frontend/src/components/settings/SettingsPreferencesTab.vue` — Extracted from SettingsView - `frontend/src/components/settings/SettingsAiTab.vue` — Extracted from SettingsView - `frontend/src/components/settings/SettingsCloudTab.vue` — Provider card list with status badges, action buttons, modals - `frontend/src/components/settings/__tests__/SettingsCloudTab.test.js` — 2 mount tests (W4) - `frontend/src/components/cloud/CloudCredentialModal.vue` — WebDAV/Nextcloud credential modal - `frontend/package.json` — Added `"test": "vitest run"` script - `frontend/vite.config.js` — Added `build.target: 'esnext'` to fix pre-existing top-level await build failure ## Decisions Made - `@pinia/testing` is not installed and not in `package.json`. Used `vi.mock('../../../stores/cloudConnections.js', ...)` to mock the store in `SettingsCloudTab.test.js` — same approach as `folders.test.js` uses `vi.mock` for the API. No dependency installation needed. - Pre-existing `npm run build` failure (top-level `await router.isReady()` in `main.js` incompatible with default esbuild targets). Fix: `build.target = 'esnext'` in `vite.config.js` — esnext natively supports module-level await. Zero code change to `main.js`. - OAuth initiation for Google Drive and OneDrive uses `window.location.href = /api/cloud/oauth/initiate/{provider}` — no fetch call — matching the backend FastAPI `RedirectResponse` pattern. ## Deviations from Plan ### Auto-fixed Issues **1. [Rule 1 - Bug] Fixed pre-existing Vite build failure (top-level await)** - **Found during:** Task 2 verification (`npm run build`) - **Issue:** `main.js` uses `await router.isReady()` at module top-level, which esbuild's default target (`chrome87`/`es2020`) does not support. This caused every build to fail with "Top-level await is not available in the configured target environment". - **Fix:** Added `build: { target: 'esnext' }` to `frontend/vite.config.js`. No code changes to `main.js` required. - **Files modified:** `frontend/vite.config.js` - **Verification:** `npm run build` exits 0, bundle output 185 kB. - **Committed in:** `63a6829` (Task 2 commit) --- **Total deviations:** 1 auto-fixed (Rule 1 — pre-existing bug) **Impact on plan:** Fix was required for the plan's success criteria (`npm run build` exits 0). No scope creep. ## Issues Encountered - `@pinia/testing` package is not installed — the plan's `SettingsCloudTab.test.js` spec used `createTestingPinia` from it. Resolved by using `vi.mock` on the store module (the same pattern already established in `folders.test.js`). No package install required. - `npm run test` script did not exist in `package.json` — the plan required running tests via `npm run test`. Added `"test": "vitest run"` to the scripts block. ## Known Stubs None. All 4 provider rows are wired to the live `useCloudConnectionsStore` — `fetchConnections()` is called in `onMounted`. The "Not connected" state is the correct zero-state display (per UI-SPEC: "all 4 providers always shown"). ## Threat Surface Scan No new network endpoints introduced. Client-side changes only. | Flag | File | Description | |------|------|-------------| | T-05-07-02 mitigated | `SettingsView.vue` | `?cloud_error=` decoded via `decodeURIComponent` and displayed via `{{ oauthError }}` template binding — Vue auto-escaping prevents HTML injection | | T-05-07-03 accepted | `CloudCredentialModal.vue` | Password lives in `ref('')` only during modal interaction; `close()` is called on `@connected` which unmounts the form; `watch(props.show)` resets all refs to empty on reopen | ## Next Phase Readiness - All frontend cloud storage management UI is complete and building. - 61 Vitest tests pass (4 new store tests + 2 new component tests + 55 pre-existing). - Plan 05-08 can proceed: AppSidebar cloud tree nodes (`CloudProviderTreeItem`, `CloudFolderTreeItem`) depend on `useCloudConnectionsStore` (now available). ## Self-Check: PASSED Files verified present: - `frontend/src/stores/cloudConnections.js`: FOUND (1045 chars) - `frontend/src/stores/__tests__/cloudConnections.test.js`: FOUND (2129 chars) - `frontend/src/api/client.js`: FOUND (with listCloudConnections, disconnectCloud, connectWebDav, updateDefaultStorage) - `frontend/src/views/SettingsView.vue`: FOUND (with activeTab, oauthSuccessProvider, oauthError, SettingsPreferencesTab, SettingsCloudTab) - `frontend/src/components/settings/SettingsPreferencesTab.vue`: FOUND - `frontend/src/components/settings/SettingsAiTab.vue`: FOUND - `frontend/src/components/settings/SettingsCloudTab.vue`: FOUND (with google_drive, onedrive, nextcloud, webdav, CloudCredentialModal, useCloudConnectionsStore) - `frontend/src/components/settings/__tests__/SettingsCloudTab.test.js`: FOUND - `frontend/src/components/cloud/CloudCredentialModal.vue`: FOUND (with authMethod) Commits verified: - `612d542`: feat(05-07): cloud connections Pinia store + API client functions — FOUND - `63a6829`: feat(05-07): 3-tab SettingsView, SettingsCloudTab, CloudCredentialModal — FOUND Test verification: `npm run test` → 61 passed, 0 failed Build verification: `npm run build` → exit 0, 185 kB bundle --- *Phase: 05-cloud-storage-backends* *Completed: 2026-05-29*