--- phase: "12.1" plan: "03" type: tdd wave: 3 depends_on: - "12.1-02" files_modified: - frontend/src/views/CloudFolderView.vue - frontend/src/stores/cloudConnections.js - frontend/src/components/cloud/CloudProviderTreeItem.vue - frontend/src/components/cloud/CloudFolderTreeItem.vue - frontend/src/components/storage/StorageBrowser.vue - frontend/src/views/__tests__/CloudFolderView.test.js - frontend/src/stores/__tests__/cloudConnections.test.js - frontend/src/components/cloud/__tests__/CloudProviderTreeItem.test.js - frontend/src/components/cloud/__tests__/CloudFolderTreeItem.test.js - frontend/src/components/storage/__tests__/StorageBrowser.skeleton.test.js - frontend/src/components/cloud/__tests__/CloudBreadcrumbNavigation.test.js - backend/main.py - frontend/package.json - frontend/package-lock.json - AGENTS.md - README.md - RUNBOOK.md - SECURITY.md autonomous: true requirements: - CLOUD-01 - CONN-04 - SYNC-01 must_haves: truths: - "Cloud UI classifies items only with kind and never relies on the absent is_dir field" - "Folder navigation sends provider_item_id while stable DocuVault id remains the row key/metadata identity" - "The frontend renders response.freshness and never manufactures fresh state or a browser-clock refresh timestamp after HTTP 200" - "Cached rows remain visible and navigable while freshness is refreshing or warning" - "Opaque provider references, including slashes, question marks, hashes, percent signs, spaces, and Unicode, are transported without path interpretation" - "Breadcrumbs come from explicit navigation lineage and never split provider_item_id into path segments" - "CloudFolderView remains a thin data provider and StorageBrowser remains the single file browser" artifacts: - path: "frontend/src/views/CloudFolderView.vue" provides: "Normalized item split, provider-reference navigation, and server freshness mapping" contains: "provider_item_id" - path: "frontend/src/views/__tests__/CloudFolderView.test.js" provides: "kind/provider_item_id/truthful-freshness regressions" contains: "warning" - path: "frontend/src/components/cloud/__tests__/CloudFolderTreeItem.test.js" provides: "Nested folder kind and provider reference tests" contains: "provider_item_id" key_links: - from: "CloudBrowseResponse.items[].kind" to: "StorageBrowser folders/files props" via: "CloudFolderView computed classification" pattern: "kind === 'folder'" - from: "folder click/tree expansion" to: "GET /api/cloud/connections/{id}/items?parent_ref=" via: "provider_item_id, never CloudItem.id" pattern: "provider_item_id" - from: "CloudBrowseResponse.freshness" to: "StorageBrowser/BreadcrumbBar freshness props" via: "Pinia browse state" pattern: "refresh_state|last_refreshed_at" --- Align the cloud frontend with the normalized API. Use `kind` for classification, `provider_item_id` for provider navigation, stable `id` for DocuVault row identity, and backend freshness as the only source of synchronization truth. @$HOME/.claude/get-shit-done/workflows/execute-plan.md @$HOME/.claude/get-shit-done/templates/summary.md @.planning/phases/12.1-fix-nextcloud-root-listing-and-sync-visibility/12.1-CONTEXT.md @.planning/phases/12.1-fix-nextcloud-root-listing-and-sync-visibility/12.1-PATTERNS.md @.planning/phases/12-cloud-resource-foundation/12-03-PLAN.md @frontend/src/views/CloudFolderView.vue @frontend/src/stores/cloudConnections.js @frontend/src/components/storage/StorageBrowser.vue Task 1: Add red normalized-item and navigation regressions frontend/src/views/__tests__/CloudFolderView.test.js, frontend/src/components/cloud/__tests__/CloudProviderTreeItem.test.js, frontend/src/components/cloud/__tests__/CloudFolderTreeItem.test.js, frontend/src/components/storage/__tests__/StorageBrowser.skeleton.test.js, frontend/src/components/cloud/__tests__/CloudBreadcrumbNavigation.test.js - frontend/src/views/CloudFolderView.vue — current is_dir filtering, item.id navigation, and fabricated freshness - frontend/src/components/cloud/CloudProviderTreeItem.vue — root folder filtering - frontend/src/components/cloud/CloudFolderTreeItem.vue — nested filtering and navigation - frontend/src/components/storage/StorageBrowser.vue — shared row keys/events and freshness props - frontend/src/components/cloud/__tests__/CloudBreadcrumbNavigation.test.js — explicit lineage and opaque-reference breadcrumb regressions - backend/api/cloud/schemas.py — canonical response shape Replace legacy-shaped fixtures with realistic CloudItemOut objects containing distinct `id` and `provider_item_id`, `kind`, nullable metadata, and no `is_dir`. Add tests named `renders_kind_folder_and_file_in_shared_browser`, `folder_click_uses_provider_item_id_not_id`, `tree_expansion_filters_kind_folder`, `nested_tree_uses_provider_item_id`, and `stable_docuvault_id_remains_row_key`. Include duplicate display names with distinct IDs/references so tests cannot pass accidentally by name. Add opaque-reference fixtures containing `/`, `?`, `#`, `%`, spaces, and Unicode. Assert CloudFolderView only passes props/emits events through StorageBrowser and does not add another grid/tree implementation. Verify root sentinel remains `parent_ref=null` at the API boundary and nested folders pass their opaque provider reference unchanged through named Vue Router navigation and the API client's query serializer—never manual route-string interpolation. Add breadcrumb tests proving labels and navigation refs come from an explicit session-only lineage of visited `{name, provider_item_id}` folder nodes (or equivalent API-provided ancestors), not `split('/')` or any other parsing of provider references. Breadcrumb clicks must restore the exact stored opaque reference. - tests fail against current is_dir and item.id behavior - fixtures omit is_dir and distinguish id from provider_item_id - root and nested provider navigation are both covered - StorageBrowser remains the rendered browser component - no test relies on provider-specific path reconstruction in Vue - reserved-character provider references survive route/query transport and breadcrumb navigation unchanged cd frontend && npm test -- --run src/views/__tests__/CloudFolderView.test.js src/components/cloud/__tests__/CloudProviderTreeItem.test.js src/components/cloud/__tests__/CloudFolderTreeItem.test.js src/components/storage/__tests__/StorageBrowser.skeleton.test.js src/components/cloud/__tests__/CloudBreadcrumbNavigation.test.js Red frontend tests reproduce invisible folders and wrong-reference navigation using the actual API shape. Task 2: Make backend freshness authoritative in Pinia and the view frontend/src/stores/cloudConnections.js, frontend/src/views/CloudFolderView.vue, frontend/src/stores/__tests__/cloudConnections.test.js, frontend/src/views/__tests__/CloudFolderView.test.js - frontend/src/stores/cloudConnections.js — folderFreshness and lastRefreshedAt state API - frontend/src/views/CloudFolderView.vue — unconditional fresh/browser-clock assignment - frontend/src/components/ui/BreadcrumbBar.vue — expected refreshing/fresh/warning values and timestamp rendering - frontend/src/views/__tests__/CloudFolderView.test.js — existing fetch/error behavior - frontend/src/stores/__tests__/cloudConnections.test.js — store reset/persistence conventions Add tests `maps_server_refreshing_freshness`, `maps_server_warning_and_last_success`, `http_200_does_not_imply_fresh`, `does_not_use_browser_clock_as_refresh_evidence`, and `cached_items_remain_visible_during_warning`. Map `response.freshness.refresh_state`, `last_refreshed_at`, `error_code`, and controlled `error_message` into Pinia. Keep a request-in-flight UI state only if it cannot overwrite the last authoritative server state; after the response, render the server object verbatim. A successful cached browse response is not proof of provider synchronization. Remove `new Date().toISOString()` and any unconditional `fresh` assignment from CloudFolderView. On request failure without a server freshness payload, preserve cached items and prior last successful timestamp while showing a controlled client transport warning; never invent a provider refresh time. - HTTP 200 plus warning freshness renders warning, not fresh - server last_refreshed_at is preserved exactly and browser Date is not called to create evidence - error code/message remain controlled display data and do not affect item ownership/navigation - cached items remain usable during refreshing/warning states - store reset clears cloud browse state without persisting secrets cd frontend && npm test -- --run src/stores/__tests__/cloudConnections.test.js src/views/__tests__/CloudFolderView.test.js The UI reports backend freshness truthfully and preserves cached navigation through warnings. Task 3: Implement normalized item use across cloud view and trees frontend/src/views/CloudFolderView.vue, frontend/src/components/cloud/CloudProviderTreeItem.vue, frontend/src/components/cloud/CloudFolderTreeItem.vue, frontend/src/components/storage/StorageBrowser.vue, frontend/src/views/__tests__/CloudFolderView.test.js, frontend/src/components/cloud/__tests__/CloudProviderTreeItem.test.js, frontend/src/components/cloud/__tests__/CloudFolderTreeItem.test.js, frontend/src/components/cloud/__tests__/CloudBreadcrumbNavigation.test.js - frontend/src/components/ui/TreeItem.vue — shared expand/collapse state contract - frontend/src/components/storage/StorageBrowser.vue — single browser row/event contract - frontend/src/views/CloudFolderView.vue — thin view boundary - frontend/src/components/cloud/CloudProviderTreeItem.vue — root folder source - frontend/src/components/cloud/CloudFolderTreeItem.vue — nested folder source Replace all cloud `is_dir` checks with `item.kind === "folder"` and file checks with `item.kind === "file"`. Route and browse nested folders with `provider_item_id`; retain `id` for Vue keys, selection, and DocuVault metadata identity. Do not add an alias field, provider-specific mapper, alternate browser, or duplicate expand/collapse state. Keep CloudProviderTreeItem and CloudFolderTreeItem wrapped by TreeItem.vue. Use named-route objects for connection routing and keep `parent_ref` in serialized query/API state; do not interpolate it into a route path. Ensure folder references are encoded only by Vue Router/API query serializers, not manually concatenated or decoded. Maintain explicit session-only breadcrumb lineage from root to visited folders; never reconstruct hierarchy by splitting `provider_item_id`, because Drive/OneDrive IDs and DAV references are opaque. Preserve connection UUID routing from Phase 12. Remove dead compatibility logic in the same change. - no active cloud component references item.is_dir - every provider browse/navigation call uses provider_item_id for nested parent_ref - stable id remains the rendering/metadata identity and is never sent to a provider adapter - CloudFolderView contains no grid/layout duplication - focused and full cloud frontend suites pass - opaque IDs containing reserved characters and breadcrumb back-navigation round-trip exactly cd frontend && npm test -- --run src/views/__tests__/CloudFolderView.test.js src/components/cloud/__tests__/CloudProviderTreeItem.test.js src/components/cloud/__tests__/CloudFolderTreeItem.test.js src/components/storage/__tests__/StorageBrowser.skeleton.test.js src/stores/__tests__/cloudConnections.test.js src/components/cloud/__tests__/CloudBreadcrumbNavigation.test.js && ! rg '\.is_dir' src/views/CloudFolderView.vue src/components/cloud Cloud folders render and navigate with the canonical API fields while shared component boundaries remain intact. Task 4: Document, security-review, commit, and push Plan 03 AGENTS.md, README.md, RUNBOOK.md, SECURITY.md, .planning/phases/12.1-fix-nextcloud-root-listing-and-sync-visibility/12.1-03-SUMMARY.md Update required docs for normalized cloud UI routing, breadcrumb lineage, and server freshness. Bump the backend/frontend patch version once and synchronize package-lock metadata because the plan changes user-visible behavior. Run a dedicated security agent against this plan's diff, resolving or escalating findings. Run focused/full frontend tests, production build, npm audit, Compose validation, and diff checks. Stage only Plan 03 files, exclude `.env` and unrelated work, commit atomically as `fix(12.1): align cloud browser contract`, and push immediately. docker compose config --quiet && cd frontend && npm test && npm run build && npm audit --audit-level=high && cd .. && git diff --check Plan 03 has independent documentation, security approval, green gates, commit, and push. | Threat ID | Threat | Mitigation and evidence | |-----------|--------|-------------------------| | T-12.1-11 | DocuVault UUID sent as provider path | Distinct-ID fixtures and provider_item_id navigation assertions | | T-12.1-12 | UI falsely reports provider synchronization | Server freshness mapping; no browser-clock evidence | | T-12.1-13 | Provider reference injected into provider-specific URL | API query serialization; adapters own provider translation/validation | | T-12.1-14 | Secrets persisted in frontend state | Existing memory/session rules; tests store folder reference only | | T-12.1-15 | Parallel browser/tree logic drifts | StorageBrowser and TreeItem remain canonical shared components | 1. Demonstrate actual API-shaped fixtures fail current is_dir/item.id code. 2. Verify kind-based root and nested classification. 3. Verify provider_item_id navigation while id remains row identity. 4. Verify warning/refreshing/fresh server payloads and exact timestamps. 5. Search for legacy is_dir usage in active cloud components and run the focused frontend suite. - Users can see folders/files returned by every normalized provider adapter. - Folder clicks and tree expansion use valid provider references. - Synchronization wording reflects backend state rather than HTTP status or client time. - Shared StorageBrowser/TreeItem architecture remains unchanged. Create `.planning/phases/12.1-fix-nextcloud-root-listing-and-sync-visibility/12.1-03-SUMMARY.md` when complete.