Files
curo1305andClaude Sonnet 4.6 73328eee0b docs(12.1): add phase 12.1 planning artifacts — Nextcloud root listing fix
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>
2026-06-23 20:34:47 +02:00

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 &amp;&amp; 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 &amp;&amp; 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 &amp;&amp; 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 &amp;&amp; ! 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 &amp;&amp; cd frontend &amp;&amp; npm test &amp;&amp; npm run build &amp;&amp; npm audit --audit-level=high &amp;&amp; cd .. &amp;&amp; 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>