# Phase 12: Cloud Resource Foundation - Pattern Map

**Mapped:** 2026-06-18
**Status:** Complete

## Purpose

Map Phase 12 files and data flow to existing DocuVault patterns so implementation extends established architecture without duplicating helpers, grids, or provider-specific UI logic.

## Backend Pattern Map

| New/changed area | Closest analog | Pattern to preserve |
|------------------|----------------|---------------------|
| `backend/migrations/versions/0006_cloud_resource_foundation.py` | `0005_system_settings.py` | Typed Alembic table creation, named constraints/indexes, timezone timestamps, exact `down_revision="0005"`, reversible downgrade |
| `CloudItem` and connection fields in `backend/db/models.py` | `CloudConnection`, `Document`, `Folder` | UUID PKs, explicit owner FKs with cascade, compound owner/navigation indexes, human names in DB only |
| `backend/storage/cloud_base.py` | `backend/storage/base.py`, `backend/ai/base.py` | Small abstract interface; normalized domain values; provider adapters own translation |
| Provider capability/metadata normalization | Existing Google/OneDrive/Nextcloud adapters | Keep SDK/HTTP details inside provider files, wrap synchronous SDK calls with `asyncio.to_thread()`, preserve canonical `CloudConnectionError` import |
| `backend/services/cloud_items.py` | Owner-scoped query patterns in `api/folders.py`, service exception policy in AGENTS.md | Filter by owner in every statement; service raises `ValueError`/domain exceptions, never `HTTPException`; use shared `deps.utils.parse_uuid` at router boundary |
| `backend/api/cloud/` package | `backend/api/documents/` and `backend/api/admin/` | One aggregator owns `/api/cloud`; sub-routers have no prefix; root empty-path routes register on parent if FastAPI requires it |
| Cloud response schemas | `CloudConnectionOut` in `backend/api/schemas.py` | Explicit field whitelist with `from_attributes`; never serialize ORM objects or `credentials_enc` directly |
| Metadata refresh/backoff | Celery retry conventions in document tasks | Bounded exponential retry, idempotent upsert/reconciliation, no retry around ownership/validation failures |

### Required Backend Corrections

- Current `_upsert_cloud_connection()` and `_get_active_connection()` use `(user_id, provider)` and must not be copied. Phase 12 uses connection UUID as identity to support multiple same-provider accounts.
- Current listing cache key `{user_id}:{provider}:{folder_id}` is insufficient. Any transitional key includes connection UUID; durable metadata belongs in PostgreSQL.
- Current provider listing dictionaries contain only `id`, `name`, `is_dir`, `size`. New adapters normalize parent, MIME/type, mtime, etag/version, per-item capabilities, and completeness/pagination.
- `api/folders.py` contains a private `_parse_uuid`; new cloud routers must import `parse_uuid` from `backend/deps/utils.py` under the project’s shared-helper rule.

## Frontend Pattern Map

| New/changed area | Closest analog | Pattern to preserve |
|------------------|----------------|---------------------|
| Capability-aware rows | `frontend/src/components/storage/StorageBrowser.vue` | This remains the only grid; props drive layout/state and generic events travel upward |
| Sync indicator | `frontend/src/components/ui/BreadcrumbBar.vue` | Extend shared breadcrumb rather than adding cloud-only breadcrumb markup |
| Connection roots | Current `CloudStorageView.vue` plus `StorageBrowser.vue` | View owns store/router only; feed normalized root rows into shared browser |
| Cloud folder route | `CloudFolderView.vue`, `FileManagerView.vue` | Thin data provider; no duplicated toolbar, row, action, or tooltip layout |
| API functions | `frontend/src/api/cloud.js` + `client.js` barrel | Change route arguments behind the domain module; consumers retain barrel imports |
| Connection state | `frontend/src/stores/cloudConnections.js` | Pinia owns connection/folder state and errors; components stay store-agnostic |
| Shared formatting | `frontend/src/utils/formatters.js` | Reuse `formatDate`, `formatSize`, and provider helpers; do not redefine locally |
| Icons | `frontend/src/components/ui/AppIcon.vue` | Add registry entries only when an existing icon name cannot express status/action |

### UI Contract Patterns

- Unsupported actions use `aria-disabled="true"`, remain focusable/tappable, and suppress action emission while surfacing explanation.
- Permanent unsupported state is gray; temporary unavailable state is amber. Do not hide actions based on `mode === "local"`.
- `StorageBrowser` receives capabilities, folder freshness, connection root, last refresh time, and byte availability as props.
- Local storage supplies a supported capability set so local/cloud share one rendering path.
- Minimum touch target stays 36x36 px and all controls use existing indigo focus-ring utilities.
- Session-only last folder state uses `sessionStorage` keyed by connection UUID; JWT tokens remain Pinia-memory-only.

## Testing Pattern Map

| Coverage | Existing analog | Phase 12 use |
|----------|-----------------|--------------|
| API/ownership | `backend/tests/test_cloud.py` | Multiple same-provider accounts, foreign connection/item 404, admin rejection, credential exclusion |
| Adapter contracts | `backend/tests/test_cloud_backends.py` | Shared normalized fixtures, async methods, pagination/completeness, no destructive probes |
| DB/service | async fixtures in `backend/tests/conftest.py` | Fast SQLite unit loop plus marked real-PostgreSQL constraint/index/cascade tests |
| Pinia | `frontend/src/stores/__tests__/cloudConnections.test.js` | Connection UUID routes, duplicate providers, freshness/error transitions |
| Storage browser | `StorageBrowser.dragmove/showSearch/skeleton.test.js` | Capability rendering, action suppression, focus/tap explanations, local regression |
| Breadcrumb | `components/ui/__tests__/BreadcrumbBar.test.js` | Refreshing/fresh/warning labels, timestamps, fade behavior |
| Thin views | `views/__tests__/FileManagerView.test.js` | Cloud views pass props/handle events without grid duplication |

## Data Flow

1. Router authenticates regular user and parses `connection_id` with shared request helpers.
2. Service resolves `(connection_id, user_id)` and reads durable children by connection/parent.
3. Provider adapter normalizes a complete paginated listing and capability states.
4. Service upserts metadata using `(connection_id, provider_item_id)` and reconciles only after complete success.
5. Explicit Pydantic schemas return rows, capability messages, freshness, and connection identity without credentials.
6. Pinia/view maps API data into `StorageBrowser`; the browser renders shared rows/actions and emits generic commands.
7. Browse never calls provider byte download, writes MinIO, or changes quota.

## Files Likely Modified

### Backend

- `backend/migrations/versions/0006_cloud_resource_foundation.py`
- `backend/db/models.py`
- `backend/storage/cloud_base.py`
- `backend/storage/cloud_backend_factory.py`
- provider backend modules
- `backend/services/cloud_items.py`
- `backend/api/cloud/__init__.py`, `connections.py`, `browse.py`, `schemas.py`
- `backend/main.py` only if aggregator import changes
- focused backend tests and PostgreSQL integration coverage

### Frontend

- `frontend/src/api/cloud.js`
- `frontend/src/stores/cloudConnections.js`
- `frontend/src/router/index.js`
- `frontend/src/views/CloudStorageView.vue`
- `frontend/src/views/CloudFolderView.vue`
- `frontend/src/components/storage/StorageBrowser.vue`
- `frontend/src/components/ui/BreadcrumbBar.vue`
- focused component/store/view tests

### Documentation/Versioning

- `AGENTS.md`, `README.md`, `backend/main.py`, and `frontend/package.json` per the project documentation/version protocol after user-facing plan completion.

## PATTERN MAPPING COMPLETE
