# Phase 13: Virtual-Local Cloud Operations - Pattern Map **Mapped:** 2026-06-22 **Files analyzed:** 33 likely new/modified files **Analogs found:** 33 / 33 (6 are composite/no-exact) ## Inherited phase rules that stay locked - Keep `StorageBrowser.vue` as the only file browser. Cloud behavior extends emitted events and props; it does not fork layout. See [AGENTS.md], Phase 12 patterns, and [frontend/src/components/storage/StorageBrowser.vue]. - Keep cloud routing owner-scoped by connection UUID plus opaque `provider_item_id`; never parse provider refs on the client. Primary analogs: [backend/api/cloud/browse.py:183], [frontend/src/views/CloudFolderView.vue:42], [frontend/src/components/cloud/CloudFolderTreeItem.vue:42]. - Keep metadata reconciliation centralized in `reconcile_cloud_listing` / `apply_listing_and_finalize`; provider adapters never write `cloud_items` rows directly. Primary analogs: [backend/services/cloud_items.py:163], [backend/services/cloud_items.py:271]. - Keep service-layer exception discipline: services raise domain errors / `ValueError`, routers translate to `HTTPException`. Primary analogs: [AGENTS.md], [backend/services/cloud_items.py:1]. - Keep audit writes metadata-only and in the caller’s transaction. Primary analog: [backend/services/audit.py:27]. ## File Classification | Likely file | Role | Data flow | Closest analog | Match | |---|---|---|---|---| | `backend/api/cloud/operations.py` | route | request-response + file-I/O + CRUD | `backend/api/cloud/browse.py`, `backend/api/documents/content.py`, `backend/api/documents/upload.py`, `backend/api/folders.py` | composite | | `backend/api/cloud/connections.py` | route | request-response | itself + `backend/tasks/cloud_tasks.py` | exact | | `backend/api/cloud/schemas.py` | schema | transform | itself | exact | | `backend/services/cloud_operations.py` | service | CRUD + transform | `backend/services/cloud_items.py` + `backend/services/audit.py` | composite | | `backend/services/cloud_items.py` | service | CRUD + batch reconcile | itself | exact | | `backend/storage/cloud_base.py` | contract | transform | itself | exact | | `backend/storage/cloud_backend_factory.py` | factory | request-response | itself | exact | | `backend/storage/google_drive_backend.py` | provider | file-I/O + CRUD | itself | exact | | `backend/storage/onedrive_backend.py` | provider | file-I/O + CRUD | itself | exact | | `backend/storage/webdav_backend.py` | provider | file-I/O + CRUD | itself | exact | | `backend/storage/nextcloud_backend.py` | provider | file-I/O | itself + `webdav_backend.py` | exact | | `backend/tasks/cloud_tasks.py` | task | event-driven + batch | itself | exact | | `frontend/src/api/cloud.js` | API client | request-response | itself + `frontend/src/api/documents.js` | exact | | `frontend/src/stores/cloudConnections.js` | store | state transform | itself | exact | | `frontend/src/views/CloudFolderView.vue` | thin view | request-response + queue orchestration | itself + `frontend/src/views/FileManagerView.vue` | exact | | `frontend/src/components/storage/StorageBrowser.vue` | smart component | CRUD UI + queue UI | itself | exact | | `frontend/src/components/settings/SettingsCloudTab.vue` | smart component | request-response | itself | exact | | `frontend/src/components/cloud/CloudCredentialModal.vue` | modal/form | request-response | itself | exact | | `backend/tests/test_cloud.py` | integration test | request-response | itself | exact | | `backend/tests/test_cloud_security.py` | security-negative test | request-response | itself | exact | | `backend/tests/test_cloud_backends.py` | provider unit/contract test | file-I/O | itself | exact | | `backend/tests/test_cloud_provider_contract.py` | provider contract test | transform | itself | exact | | `backend/tests/test_cloud_items.py` | service/task test | CRUD + batch | itself | exact | | `backend/tests/test_cloud_mutations.py` | integration/contract test | CRUD + file-I/O | `test_cloud.py` + `test_cloud_security.py` + `test_cloud_items.py` | composite | | `backend/tests/test_cloud_reconnect.py` | integration/security test | request-response + task | `test_cloud.py` + `test_cloud_items.py` | composite | | `backend/tests/test_cloud_audit.py` | integration test | transform | `backend/tests/test_audit.py` + `backend/tests/test_cloud.py` | partial | | `frontend/src/views/__tests__/CloudFolderView.test.js` | view unit test | request-response | itself | exact | | `frontend/src/views/__tests__/CloudFolderRenderedFlow.test.js` | rendered-flow test | request-response | itself | exact | | `frontend/src/components/storage/__tests__/StorageBrowser.capabilities.test.js` | component unit test | CRUD UI | itself | exact | | `frontend/src/components/storage/__tests__/StorageBrowser.cloud-queue.test.js` | component unit test | queue UI | `StorageBrowser.capabilities.test.js` + `FileManagerView.vue` upload flow | composite | | `frontend/src/views/__tests__/CloudFolderOpenPreview.test.js` | rendered-flow test | request-response + preview | `CloudFolderRenderedFlow.test.js` + document content helpers | composite | | `frontend/src/stores/__tests__/cloudConnections.test.js` | store unit test | state transform | itself | exact | | `frontend/src/components/settings/__tests__/SettingsCloudTab.test.js` | component unit test | request-response | itself | exact | | `frontend/src/components/settings/__tests__/SettingsCloudTab.health.test.js` | component unit test | health/reconnect UI | `SettingsCloudTab.test.js` + store freshness tests | partial | ## Pattern Assignments ### `backend/api/cloud/operations.py` **Primary analogs** - Route/dependency skeleton: [backend/api/cloud/browse.py:19-42], [backend/api/cloud/browse.py:183-214] - Connection settings/test/update/disconnect patterns: [backend/api/cloud/connections.py:368-434], [backend/api/cloud/connections.py:492-579], [backend/api/cloud/connections.py:604-635] - Authorized byte streaming and cloud-error translation: [backend/api/documents/content.py:50-103] - Multipart upload proxy pattern: [backend/api/documents/upload.py:122-198] - Local create/rename/delete/move handler shape: [backend/api/folders.py:122-160], [backend/api/folders.py:232-265], [backend/api/folders.py:268-351], [backend/api/folders.py:357-375] **Copy these conventions** - Use `get_regular_user`, `get_db`, `account_limiter`, and `request.state.current_user = current_user` exactly like cloud browse/connections. - Resolve ownership through `resolve_owned_connection(...)` before any provider action. - Translate provider/service failures to controlled `HTTPException` messages like document content/upload already do; do not surface raw provider text. - For open/preview/download, copy the `StreamingResponse` + `CloudConnectionError` mapping from `content.py`, but key authorization off cloud connection/item ownership instead of `Document`. - For upload/create/rename/move/delete, keep router bodies thin: validate request shape, call service, return whitelisted schema, write no inline reconciliation logic. **No exact analog** - There is no existing single cloud route file that combines owner-scoped connection actions with content proxying and normalized mutation results. Keep the file shaped like `browse.py`, but borrow handler bodies from the document/folder routers above. ### `backend/api/cloud/connections.py` **Primary analogs** - WebDAV connect and health-check flow: [backend/api/cloud/connections.py:368-434] - Credential update flow: [backend/api/cloud/connections.py:492-579] - Display-name rename: [backend/api/cloud/connections.py:582-601] - Disconnect confirmation target: [backend/api/cloud/connections.py:604-635] **Copy these conventions** - Keep `_get_owned_connection(...)` for owner checks. - Keep URL validation before backend construction. - Keep audit writes adjacent to the successful state change. - If reconnect/test routes are added, model them after `connect_webdav` + `update_webdav_credentials`: validate, health-check, persist, audit, commit. **Phase 13-specific caution** - The current OAuth callback always inserts a new row (`_upsert_cloud_connection = _insert_cloud_connection`) at [backend/api/cloud/connections.py:128-129]. Do not copy that behavior for reconnect; Phase 13 needs connection-row preservation. ### `backend/api/cloud/schemas.py` **Primary analog** - Current cloud whitelist schemas: [backend/api/cloud/schemas.py:17-86] **Copy these conventions** - Add only explicit allowlist response/request models. - Keep validators at schema level for user-submitted fields, as in `ConnectionRenameRequest`. - Keep credentials, provider URLs, and raw provider payloads out of every schema. **No exact analog** - There is no current normalized mutation-result schema. Add new result types beside `CloudBrowseResponse`, not in routers. ### `backend/services/cloud_operations.py` **Primary analogs** - Owner resolution / stable identity / folder freshness: [backend/services/cloud_items.py:38-62], [backend/services/cloud_items.py:97-159], [backend/services/cloud_items.py:271-342] - Metadata-only audit helper: [backend/services/audit.py:27-58] - Background refresh orchestration: [backend/tasks/cloud_tasks.py:50-163] **Copy these conventions** - Accept UUIDs/strings the same way `cloud_items.py` does. - Raise domain errors or `ValueError`, never `HTTPException`. - Reconcile and finalize folder state before returning success. - Write audit rows through `write_audit_log(...)`, letting the caller own the transaction. **No exact analog** - There is no existing service that turns provider mutation outcomes into normalized API results. Treat this as the new orchestration seam; do not push that logic into routers or providers. ### `backend/services/cloud_items.py` **Primary analog** - Keep using the current service itself. **Copy these conventions** - Owner resolution: [backend/services/cloud_items.py:38-62] - Stable upsert-by-`(connection_id, provider_item_id)`: [backend/services/cloud_items.py:97-159] - Complete-vs-incomplete reconciliation: [backend/services/cloud_items.py:163-213] - Single freshness gate: [backend/services/cloud_items.py:271-342] - Controlled folder-state updates: [backend/services/cloud_items.py:345-384] **Planner note** - Any mutation service should feed its authoritative provider result back through these primitives instead of adding direct ORM writes elsewhere. ### `backend/storage/cloud_base.py` **Primary analog** - Keep extending the current contract file itself: [backend/storage/cloud_base.py:27-245] **Copy these conventions** - Stable action vocabulary and reason codes live here, not in routers or Vue. - Immutable normalized dataclasses (`CloudCapability`, `CloudResource`, `CloudListing`) are the pattern to follow for new mutation result types. - Keep provider-specific branching out of the shared contract. **No exact analog** - There is no Phase-13 mutation result/value type yet. New normalized result dataclasses belong here. ### `backend/storage/cloud_backend_factory.py` **Primary analog** - Current factory and adapter assertion: [backend/storage/cloud_backend_factory.py:7-66] **Copy these conventions** - Keep lazy imports. - Keep Nextcloud URL normalization in the factory boundary. - If a mutable-adapter subclass is introduced, assert the interface here instead of switching on provider names in routers. ### `backend/storage/google_drive_backend.py` **Primary analog** - Existing upload/download/delete/list/capability patterns: [backend/storage/google_drive_backend.py:140-172], [backend/storage/google_drive_backend.py:174-207], [backend/storage/google_drive_backend.py:257-272], [backend/storage/google_drive_backend.py:276-417] **Copy these conventions** - Wrap SDK calls in `asyncio.to_thread()`. - Centralize provider error classification in helper methods like `_handle_http_error(...)`. - Normalize provider metadata into `CloudResource` objects. - Keep the backend stateless: it may return refreshed credentials or typed errors, but must not write DB state directly. ### `backend/storage/onedrive_backend.py` **Primary analog** - Existing token lifecycle + upload/download/delete/list/capabilities: [backend/storage/onedrive_backend.py:83-154], [backend/storage/onedrive_backend.py:158-218], [backend/storage/onedrive_backend.py:220-245], [backend/storage/onedrive_backend.py:280-409] **Copy these conventions** - Refresh token on demand inside the backend boundary. - Keep Graph HTTP async with `httpx`. - Keep `CloudConnectionError` unified across providers. **Gap to flag** - There is no persistence pattern yet for refreshed OneDrive credentials; Phase 13 must add one above the backend, not inside it. ### `backend/storage/webdav_backend.py` **Primary analog** - Existing SSRF, PUT/GET/DELETE, list-folder, and capability patterns: [backend/storage/webdav_backend.py:66-137], [backend/storage/webdav_backend.py:139-174], [backend/storage/webdav_backend.py:222-237], [backend/storage/webdav_backend.py:241-370] **Copy these conventions** - Re-run `validate_cloud_url(...)` before every outbound request. - Keep basename/path-traversal protection when building destination names. - Keep WebDAV-specific overwrite/conflict semantics inside this provider file. ### `backend/storage/nextcloud_backend.py` **Primary analog** - Minimal specialization over WebDAV: [backend/storage/nextcloud_backend.py:38-90] **Copy these conventions** - Keep Nextcloud as a tiny specialization, not a parallel backend. - If Phase 13 needs Nextcloud-specific mutation quirks, implement them as narrow overrides that preserve the public contract. ### `backend/tasks/cloud_tasks.py` **Primary analog** - Current refresh worker: [backend/tasks/cloud_tasks.py:50-163], [backend/tasks/cloud_tasks.py:175-216] **Copy these conventions** - Revalidate ownership inside the worker. - Decrypt credentials inside the worker, never in broker payload. - Use sentinel exceptions to separate retryable provider failures from terminal auth failures. - Reuse this task model if reconnect or stale-metadata recovery needs post-success refresh. ### `frontend/src/api/cloud.js` **Primary analogs** - Existing connection browse/config/update methods: [frontend/src/api/cloud.js:11-18], [frontend/src/api/cloud.js:36-50], [frontend/src/api/cloud.js:73-88] - Cloud upload helper shape: [frontend/src/api/documents.js:48-54] **Copy these conventions** - Keep all Phase 13 cloud endpoints behind this domain module. - Keep connection UUID and encoded `parent_ref` handling here. - Add new `open/preview/upload/create/rename/move/delete/test/reconnect` helpers here instead of calling raw URLs from views/components. ### `frontend/src/stores/cloudConnections.js` **Primary analog** - Current store state and reset/mapping helpers: [frontend/src/stores/cloudConnections.js:33-138] **Copy these conventions** - Keep server state translation centralized in the store. - Keep sessionStorage limited to folder refs only. - Reset browse state on connection switch. **No exact analog** - There is no queue/conflict/resume state machine yet. Add it here only if it is shared across cloud views/settings; otherwise keep queue orchestration in `CloudFolderView`. ### `frontend/src/views/CloudFolderView.vue` **Primary analogs** - Existing cloud thin-view contract: [frontend/src/views/CloudFolderView.vue:1-20], [frontend/src/views/CloudFolderView.vue:125-170] - Local upload / create / rename / move / delete orchestration: [frontend/src/views/FileManagerView.vue:1-30], [frontend/src/views/FileManagerView.vue:103-182] **Copy these conventions** - Keep the view thin: store/router/API orchestration only. - Pass everything into `StorageBrowser`; do not add layout or grid logic here. - Reuse the local upload queue orchestration style from `FileManagerView`, but Phase 13 needs sequential pause/resume instead of the current all-at-once `Promise.allSettled(...)` placeholder at [frontend/src/views/CloudFolderView.vue:172-181]. - Use toasts and success/error handling the same way `FileManagerView` does. ### `frontend/src/components/storage/StorageBrowser.vue` **Primary analog** - Extend the current shared component itself: [frontend/src/components/storage/StorageBrowser.vue:140-275], [frontend/src/components/storage/StorageBrowser.vue:424-681] **Copy these conventions** - Props/events are the contract; keep new behavior behind emitted events and prop-driven state. - Capability-aware buttons and notices stay in this component: [frontend/src/components/storage/StorageBrowser.vue:360-418], [frontend/src/components/storage/StorageBrowser.vue:495-530]. - New folder / rename inline patterns already exist here: [frontend/src/components/storage/StorageBrowser.vue:118-135], [frontend/src/components/storage/StorageBrowser.vue:157-166], [frontend/src/components/storage/StorageBrowser.vue:543-593]. - File move picker and drag-to-move patterns already exist here: [frontend/src/components/storage/StorageBrowser.vue:253-344], [frontend/src/components/storage/StorageBrowser.vue:595-621]. **Gap to flag** - There is no existing conflict dialog / paused upload queue UI. That is a real no-analog area inside the shared browser and should be added here rather than in a cloud-only component. ### `frontend/src/components/settings/SettingsCloudTab.vue` **Primary analog** - Existing settings actions and confirm flows: [frontend/src/components/settings/SettingsCloudTab.vue:21-102], [frontend/src/components/settings/SettingsCloudTab.vue:104-170], [frontend/src/components/settings/SettingsCloudTab.vue:293-359] **Copy these conventions** - Keep provider rows, inline status badges, and destructive confirms in one shared settings tab. - Add Test/Reconnect controls beside existing Connect/Edit/Remove actions; do not create a parallel health screen. - Keep OAuth initiation in the component and store mutation calls behind the store/API layer. ### `frontend/src/components/cloud/CloudCredentialModal.vue` **Primary analog** - Existing submit branching for create vs edit: [frontend/src/components/cloud/CloudCredentialModal.vue:301-323] **Copy these conventions** - Keep credential edits inside the modal for WebDAV/Nextcloud. - If Phase 13 adds explicit “Test connection” before save, hang it off the same API module and modal state machine instead of duplicating form state elsewhere. ### `backend/tests/test_cloud.py` **Primary analog** - Current integration coverage for connect/upload/status/browse/rename/refresh: [backend/tests/test_cloud.py:30-65], [backend/tests/test_cloud.py:473-527], [backend/tests/test_cloud.py:532-655], [backend/tests/test_cloud.py:998-1315] **Copy these conventions** - Use the shared auth helper pattern and `async_client`. - Patch provider/network boundaries, not route internals, where possible. - Assert both HTTP contract and DB side effects. ### `backend/tests/test_cloud_security.py` **Primary analog** - Current security-negative suite: [backend/tests/test_cloud_security.py:37-117], [backend/tests/test_cloud_security.py:122-340] **Copy these conventions** - Add Phase 13 wrong-owner/admin/credential/no-byte/cross-connection negatives here. - Keep raw-provider-error sanitization assertions here, not only in happy-path integration tests. ### `backend/tests/test_cloud_backends.py` **Primary analog** - Provider backend behavior tests: [backend/tests/test_cloud_backends.py:334-420] and the rest of the file’s provider sections **Copy these conventions** - Provider-specific fixtures, normalized assertions. - Explicit “never download/mutate while browsing” checks. - Keep backend tests provider-neutral where possible and provider-specific only where semantics differ. ### `backend/tests/test_cloud_provider_contract.py` **Primary analog** - Canonical signature/identity/pagination/no-byte contract: [backend/tests/test_cloud_provider_contract.py:201-276], [backend/tests/test_cloud_provider_contract.py:376-536], [backend/tests/test_cloud_provider_contract.py:659-727] **Copy these conventions** - Any new mutable adapter methods should get the same style of canonical contract tests: signature, normalized result, trusted caller identity, no forbidden side effects. ### `backend/tests/test_cloud_items.py` **Primary analog** - Owner-scoped service and folder-state tests: [backend/tests/test_cloud_items.py:368-529], [backend/tests/test_cloud_items.py:545-859] **Copy these conventions** - Mutation reconciliation tests belong here: stable UUID across rename/move, complete/incomplete behavior, freshness truth, no quota mutation. ### `backend/tests/test_cloud_mutations.py` **Closest analogs** - API behavior: [backend/tests/test_cloud.py:473-527], [backend/tests/test_cloud.py:1112-1249] - Security negatives: [backend/tests/test_cloud_security.py:122-340] - Reconciliation truth: [backend/tests/test_cloud_items.py:411-499], [backend/tests/test_cloud_items.py:743-859] **No exact analog** - There is no existing end-to-end mutation suite for cloud items. Build it as a new file, but follow the helper/fixture style of `test_cloud.py`. ### `backend/tests/test_cloud_reconnect.py` **Closest analogs** - Connection lifecycle/status: [backend/tests/test_cloud.py:532-655] - Worker/freshness behavior: [backend/tests/test_cloud.py:1251-1315], [backend/tests/test_cloud_items.py:545-609] **No exact analog** - There is no dedicated reconnect/token-persistence suite today. ### `backend/tests/test_cloud_audit.py` **Closest analogs** - Audit helper behavior: [backend/services/audit.py:27-58] - Inline audit assertions in cloud routes: [backend/api/cloud/connections.py:351-359], [backend/api/cloud/connections.py:422-430], [backend/api/cloud/connections.py:567-575], [backend/api/cloud/connections.py:624-632] **No exact analog** - There is no cloud-mutation audit suite today; create one that asserts metadata-only payloads and same-transaction persistence. ### `frontend/src/views/__tests__/CloudFolderView.test.js` **Primary analog** - Existing thin-view cloud tests: [frontend/src/views/__tests__/CloudFolderView.test.js:73-294] **Copy these conventions** - Stub `StorageBrowser` when the test is about view orchestration only. - Assert route params, API calls, and props passed to the browser. ### `frontend/src/views/__tests__/CloudFolderRenderedFlow.test.js` **Primary analog** - Existing real-browser rendered flow: [frontend/src/views/__tests__/CloudFolderRenderedFlow.test.js:1-260] **Copy these conventions** - Render the real `StorageBrowser` when testing end-to-end view/browser interactions. - Keep only router/API boundaries mocked. ### `frontend/src/components/storage/__tests__/StorageBrowser.capabilities.test.js` **Primary analog** - Current accessibility and unsupported-capability suite: [frontend/src/components/storage/__tests__/StorageBrowser.capabilities.test.js:47-257] **Copy these conventions** - Assert emitted events, `aria-disabled`, notices, and touch-target classes from the real component. - Extend this file for new capability buttons only; do not hide cloud actions based on mode. ### `frontend/src/components/storage/__tests__/StorageBrowser.cloud-queue.test.js` **Closest analogs** - Capability interaction assertions: [frontend/src/components/storage/__tests__/StorageBrowser.capabilities.test.js:89-190] - Local upload queue behavior source: [frontend/src/views/FileManagerView.vue:103-128] **No exact analog** - There is no existing paused sequential queue test file. Create it as a dedicated component suite around the shared browser’s new queue/conflict UI. ### `frontend/src/views/__tests__/CloudFolderOpenPreview.test.js` **Closest analogs** - Rendered cloud flow: [frontend/src/views/__tests__/CloudFolderRenderedFlow.test.js:170-260] - Authorized document content fetch helper: [frontend/src/api/documents.js:60-81] **No exact analog** - There is no cloud open/preview rendered-flow test today. ### `frontend/src/stores/__tests__/cloudConnections.test.js` **Primary analog** - Existing store tests for selection, freshness, session state: [frontend/src/stores/__tests__/cloudConnections.test.js:28-237] **Copy these conventions** - Keep pure store tests focused on mapping and reset behavior. - If queue state or health-state translation lives in the store, test it here once. ### `frontend/src/components/settings/__tests__/SettingsCloudTab.test.js` **Primary analog** - Existing settings-tab tests: [frontend/src/components/settings/__tests__/SettingsCloudTab.test.js:1-132] **Copy these conventions** - Mock the store and API module, not network. - Keep component tests structural and action-trigger oriented. ### `frontend/src/components/settings/__tests__/SettingsCloudTab.health.test.js` **Closest analogs** - Existing settings tab structure: [frontend/src/components/settings/__tests__/SettingsCloudTab.test.js:51-132] - Store freshness/health state assertions: [frontend/src/stores/__tests__/cloudConnections.test.js:160-237] **No exact analog** - There is no dedicated health/test/reconnect settings suite yet. ## Shared Patterns to Reuse Everywhere ### Authentication / ownership - Regular-user-only dependency: [backend/api/cloud/browse.py:29-31], [backend/api/cloud/connections.py:25-27], [backend/api/documents/content.py:24-26] - Owner resolution before resource work: [backend/api/cloud/browse.py:205-214], [backend/api/cloud/connections.py:132-141], [backend/services/cloud_items.py:38-62] ### Router shape - Thin routers with typed bodies + `account_limiter`: [backend/api/cloud/connections.py:281-304], [backend/api/cloud/browse.py:183-200], [backend/api/folders.py:122-149] - Response shaping through explicit Pydantic schemas or dict helpers, never raw ORM/provider payloads: [backend/api/cloud/browse.py:64-97], [backend/api/cloud/schemas.py:17-67] ### Error translation - `CloudConnectionError` becomes controlled reconnect guidance: [backend/api/documents/upload.py:160-175], [backend/api/documents/content.py:61-76] - Incomplete/stale provider state becomes controlled warning, not fake success: [backend/services/cloud_items.py:319-342], [backend/api/cloud/browse.py:296-307] ### Audit - Use `write_audit_log(...)` after successful state change, inside caller-owned transaction: [backend/services/audit.py:27-58] ### Provider boundary - Providers normalize data and keep SDK/HTTP details private: [backend/storage/google_drive_backend.py:276-362], [backend/storage/onedrive_backend.py:300-386], [backend/storage/webdav_backend.py:241-332] - Factories own provider construction and URL normalization: [backend/storage/cloud_backend_factory.py:7-66] ### Frontend architecture - Thin view -> shared browser: [frontend/src/views/CloudFolderView.vue:1-20], [frontend/src/views/FileManagerView.vue:1-30] - Shared browser owns interaction/layout/state: [frontend/src/components/storage/StorageBrowser.vue:424-681] - Settings tab owns connection controls, not provider-specific subviews: [frontend/src/components/settings/SettingsCloudTab.vue:21-214] ### Testing - Integration tests assert API contract plus DB side effect: [backend/tests/test_cloud.py:532-655], [backend/tests/test_cloud.py:1112-1249] - Security tests assert absence of leaks and forbidden side effects: [backend/tests/test_cloud_security.py:197-301] - View tests stub the browser for orchestration-only checks; rendered-flow tests use the real browser: [frontend/src/views/__tests__/CloudFolderView.test.js:51-101], [frontend/src/views/__tests__/CloudFolderRenderedFlow.test.js:170-205] ## No-Analog / planner watchlist 1. Provider-neutral mutation result dataclasses do not exist yet. Add them in `backend/storage/cloud_base.py`; do not leak provider payloads. 2. OAuth reconnect that patches an existing connection row does not exist yet. Current callback flow inserts a new row. 3. Shared paused upload queue UI for cloud conflicts/errors does not exist yet. Add it in `StorageBrowser.vue`, not a cloud-only component. 4. Authorized cloud-item preview/open endpoints do not exist yet. Reuse document content streaming patterns, but this is a new endpoint family. 5. Dedicated reconnect/audit/open-preview test suites do not exist yet; create them instead of overloading unrelated files. ## Recommended implementation order from pattern proximity 1. Extend `cloud_base.py` + provider backends + factory. 2. Add `cloud_operations.py` and keep all reconciliation through `cloud_items.py`. 3. Add `api/cloud/operations.py`, then minimally extend `connections.py` / `schemas.py`. 4. Extend `api/cloud.js` + `cloudConnections.js` + `CloudFolderView.vue`. 5. Extend `StorageBrowser.vue` and `SettingsCloudTab.vue`. 6. Add the missing dedicated backend/frontend test files before broad polish. ## PATTERN MAPPING COMPLETE