Plans 01–04, CONTEXT, PATTERNS, RESEARCH, UAT, and .gitkeep for the fix-nextcloud-root-listing-and-sync-visibility sub-phase. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
206 lines
15 KiB
Markdown
206 lines
15 KiB
Markdown
---
|
|
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"
|
|
---
|
|
|
|
<objective>
|
|
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.
|
|
</objective>
|
|
|
|
<execution_context>
|
|
@$HOME/.claude/get-shit-done/workflows/execute-plan.md
|
|
@$HOME/.claude/get-shit-done/templates/summary.md
|
|
</execution_context>
|
|
|
|
<context>
|
|
@.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
|
|
</context>
|
|
|
|
<tasks>
|
|
|
|
<task type="auto">
|
|
<name>Task 1: Add red normalized-item and navigation regressions</name>
|
|
<files>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</files>
|
|
<read_first>
|
|
- 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
|
|
</read_first>
|
|
<action>
|
|
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.
|
|
</action>
|
|
<acceptance_criteria>
|
|
- 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
|
|
</acceptance_criteria>
|
|
<verify><automated>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</automated></verify>
|
|
<done>Red frontend tests reproduce invisible folders and wrong-reference navigation using the actual API shape.</done>
|
|
</task>
|
|
|
|
<task type="auto">
|
|
<name>Task 2: Make backend freshness authoritative in Pinia and the view</name>
|
|
<files>frontend/src/stores/cloudConnections.js, frontend/src/views/CloudFolderView.vue, frontend/src/stores/__tests__/cloudConnections.test.js, frontend/src/views/__tests__/CloudFolderView.test.js</files>
|
|
<read_first>
|
|
- 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
|
|
</read_first>
|
|
<action>
|
|
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.
|
|
</action>
|
|
<acceptance_criteria>
|
|
- 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
|
|
</acceptance_criteria>
|
|
<verify><automated>cd frontend && npm test -- --run src/stores/__tests__/cloudConnections.test.js src/views/__tests__/CloudFolderView.test.js</automated></verify>
|
|
<done>The UI reports backend freshness truthfully and preserves cached navigation through warnings.</done>
|
|
</task>
|
|
|
|
<task type="auto">
|
|
<name>Task 3: Implement normalized item use across cloud view and trees</name>
|
|
<files>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</files>
|
|
<read_first>
|
|
- 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
|
|
</read_first>
|
|
<action>
|
|
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.
|
|
</action>
|
|
<acceptance_criteria>
|
|
- 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
|
|
</acceptance_criteria>
|
|
<verify><automated>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</automated></verify>
|
|
<done>Cloud folders render and navigate with the canonical API fields while shared component boundaries remain intact.</done>
|
|
</task>
|
|
|
|
<task type="auto">
|
|
<name>Task 4: Document, security-review, commit, and push Plan 03</name>
|
|
<files>AGENTS.md, README.md, RUNBOOK.md, SECURITY.md, .planning/phases/12.1-fix-nextcloud-root-listing-and-sync-visibility/12.1-03-SUMMARY.md</files>
|
|
<action>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.</action>
|
|
<verify><automated>docker compose config --quiet && cd frontend && npm test && npm run build && npm audit --audit-level=high && cd .. && git diff --check</automated></verify>
|
|
<done>Plan 03 has independent documentation, security approval, green gates, commit, and push.</done>
|
|
</task>
|
|
|
|
</tasks>
|
|
|
|
<threat_model>
|
|
| 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 |
|
|
</threat_model>
|
|
|
|
<verification>
|
|
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.
|
|
</verification>
|
|
|
|
<success_criteria>
|
|
- 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.
|
|
</success_criteria>
|
|
|
|
<output>Create `.planning/phases/12.1-fix-nextcloud-root-listing-and-sync-visibility/12.1-03-SUMMARY.md` when complete.</output>
|