From 9ef7f81b3fee25fd3a645d43400eb0bddfe2a28a Mon Sep 17 00:00:00 2001 From: curo1305 Date: Mon, 22 Jun 2026 08:55:02 +0200 Subject: [PATCH] docs(12.1-03): complete plan 03 summary and state updates --- .planning/ROADMAP.md | 6 +- .planning/STATE.md | 14 +- .../12.1-03-SUMMARY.md | 218 ++++++++++++++++++ 3 files changed, 230 insertions(+), 8 deletions(-) create mode 100644 .planning/phases/12.1-fix-nextcloud-root-listing-and-sync-visibility/12.1-03-SUMMARY.md diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md index 6f8866e..ee623a2 100644 --- a/.planning/ROADMAP.md +++ b/.planning/ROADMAP.md @@ -51,17 +51,17 @@ Before any phase is marked complete: **Goal:** Restore visible, truthful, metadata-only cloud browsing by repairing Nextcloud's adapter contract, applying one completeness/freshness contract across all providers, and aligning the shared frontend browser with normalized provider identities. **Requirements:** CONN-04, CLOUD-01, CACHE-01, SYNC-01 **Depends on:** Phase 12 -**Plans:** 2/4 plans executed +**Plans:** 3/4 plans executed **Execution waves:** Wave 1: 12.1-01; Wave 2: 12.1-02 after 01; Wave 3: 12.1-03 after 02; Wave 4: 12.1-04 after 01-03. Plans: - [x] 12.1-01-PLAN.md - [x] 12.1-02-PLAN.md -- [ ] 12.1-03-PLAN.md +- [x] 12.1-03-PLAN.md - [ ] 12.1-04-PLAN.md - [x] 12.1-01 (Wave 1) — Restore the shared four-provider adapter contract, secure Nextcloud URL/redirect handling, and metadata-only listing behavior. -- [ ] 12.1-02 (Wave 2; after 12.1-01) — Centralize complete/incomplete reconciliation so incomplete provider results cannot become fresh or delete cached metadata. +- [x] 12.1-02 (Wave 2; after 12.1-01) — Centralize complete/incomplete reconciliation so incomplete provider results cannot become fresh or delete cached metadata. - [ ] 12.1-03 (Wave 3; after 12.1-02) — Align the shared browser and trees with `kind`, opaque `provider_item_id` routing, lineage-based breadcrumbs, and server freshness. - [ ] 12.1-04 (Wave 4; after 12.1-01 through 12.1-03) — Run sanitized two-mode live Nextcloud acceptance, rendered/security/secret gates, and phase closeout. diff --git a/.planning/STATE.md b/.planning/STATE.md index 9413932..da8202b 100644 --- a/.planning/STATE.md +++ b/.planning/STATE.md @@ -5,15 +5,15 @@ milestone_name: Reimagining Cloud Storage integration current_phase: 12.1 current_phase_name: fix-nextcloud-root-listing-and-sync-visibility status: executing -stopped_at: Completed Phase 12.1 Plan 02 — truthful freshness gate -last_updated: "2026-06-22T06:37:54.133Z" +stopped_at: Completed Phase 12.1 Plan 03 — normalized cloud browser contract +last_updated: "2026-06-22T06:54:15.993Z" last_activity: 2026-06-22 last_activity_desc: Phase 12.1 execution started progress: total_phases: 6 completed_phases: 1 total_plans: 10 - completed_plans: 8 + completed_plans: 9 percent: 17 --- @@ -26,7 +26,7 @@ progress: ## Current Position Phase: 12.1 (fix-nextcloud-root-listing-and-sync-visibility) — EXECUTING -Plan: 3 of 4 +Plan: 4 of 4 Status: Ready to execute Last activity: 2026-06-22 — Phase 12.1 execution started @@ -51,6 +51,7 @@ Last activity: 2026-06-22 — Phase 12.1 execution started | Tests at milestone start | 277 | | Phase 12.1 P01 | 823 | 4 tasks | 14 files | | Phase 12.1 P02 | 2100 | 3 tasks | 13 files | +| Phase 12.1 P03 | 677 | 4 tasks | 11 files | ## Accumulated Context @@ -94,7 +95,7 @@ _Updated at each phase transition._ | Field | Value | |---|---| -| Last session | 2026-06-22T06:37:54.128Z | +| Last session | 2026-06-22T06:54:15.988Z | | Next action | Execute Phase 12.1 Plan 01 | | Pending decisions | None | | Resume file | None | @@ -104,3 +105,6 @@ _Updated at each phase transition._ - [Phase ?]: Remove NextcloudBackend.list_folder override — inherit canonical WebDAVBackend method - [Phase ?]: OneDrive nextLink restricted to graph.microsoft.com host - [Phase ?]: apply_listing_and_finalize is the single freshness gate — callers must not set refresh_state=fresh independently after list_folder +- [Phase 12.1 P03]: Named route objects used for all cloud folder navigation — Vue Router handles opaque ref encoding +- [Phase 12.1 P03]: Breadcrumb lineage maintained as explicit visited-node list — never reconstructed from provider_item_id +- [Phase 12.1 P03]: provider_item_id is canonical navigation reference; DocuVault id is row identity for Vue keys and metadata only diff --git a/.planning/phases/12.1-fix-nextcloud-root-listing-and-sync-visibility/12.1-03-SUMMARY.md b/.planning/phases/12.1-fix-nextcloud-root-listing-and-sync-visibility/12.1-03-SUMMARY.md new file mode 100644 index 0000000..379abc7 --- /dev/null +++ b/.planning/phases/12.1-fix-nextcloud-root-listing-and-sync-visibility/12.1-03-SUMMARY.md @@ -0,0 +1,218 @@ +--- +phase: "12.1" +plan: "03" +subsystem: cloud-storage-frontend +status: complete +tags: [cloud, frontend, kind, provider_item_id, freshness, breadcrumb, tdd, security] +completed: "2026-06-22" +duration_seconds: 677 +task_count: 4 +file_count: 11 + +requires: + - "12.1-02" +provides: + - "kind-based item classification in CloudFolderView (folders/files computed)" + - "provider_item_id for all folder navigation and tree expansion" + - "server freshness mapped verbatim — no browser-clock evidence" + - "explicit breadcrumb lineage from visited nodes" + - "normalized test regressions covering kind/provider_item_id/freshness" +affects: [cloud-browse-ui, cloud-sidebar-tree, breadcrumb-navigation] + +tech_stack: + added: [] + patterns: + - "TDD RED/GREEN — 9+7 failing tests written first; implementation makes them all pass" + - "Named route object navigation (never string interpolation for opaque refs)" + - "Server freshness mapped verbatim — freshness.refresh_state|last_refreshed_at" + - "Breadcrumb lineage as explicit visited-node list (never split provider_item_id)" + +key_files: + created: + - frontend/src/components/cloud/__tests__/CloudBreadcrumbNavigation.test.js + modified: + - frontend/src/views/CloudFolderView.vue + - frontend/src/components/cloud/CloudProviderTreeItem.vue + - frontend/src/components/cloud/CloudFolderTreeItem.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 + - backend/main.py + - frontend/package.json + +decisions: + - "Named route objects used for all cloud navigation — Vue Router handles encoding of opaque refs" + - "Breadcrumb lineage maintained as explicit {name, providerItemId} array in view state — never reconstructed from provider_item_id" + - "provider_item_id is the canonical navigation reference; id remains row identity for Vue keys and DocuVault metadata" + - "Test stubs use updated() lifecycle hook (not mounted()) to capture props after async load resolves" + +requirements: + - CLOUD-01 + - CONN-04 + - SYNC-01 +--- + +# Phase 12.1 Plan 03: Align Cloud Browser Contract — Summary + +**One-liner:** Replaced `is_dir`/`item.id`/`new Date()` with `kind`, `provider_item_id`, and verbatim server freshness across all active cloud UI components. + +## What Was Built + +### Task 1 — TDD RED: Normalized-item and navigation regressions + +Added 9 failing tests across 5 test files before touching production code: + +**`frontend/src/views/__tests__/CloudFolderView.test.js` (4 new tests):** +- `renders_kind_folder_and_file_in_shared_browser` — fixtures with no `is_dir`; asserts `folders` prop uses `kind=folder` only +- `folder_click_uses_provider_item_id_not_id` — asserts `navigateTo` puts `provider_item_id` in route, not DocuVault UUID +- `stable_docuvault_id_remains_row_key` — asserts items retain both `id` and `provider_item_id` +- `opaque_reference_round_trip` — fixture with `/`, `?`, `#`, spaces, Unicode in `provider_item_id`; asserts intact navigation + +**`frontend/src/components/cloud/__tests__/CloudProviderTreeItem.test.js` (2 new tests):** +- `tree_expansion_filters_kind_folder` — `loadChildren` returns `kind=folder` items only +- `loadChildren passes root sentinel` — API called with connection UUID, empty string for root + +**`frontend/src/components/cloud/__tests__/CloudFolderTreeItem.test.js` (4 new tests):** +- `nested_tree_uses_provider_item_id` — navigate/loadChildren/expandable all use `provider_item_id` and `kind` +- `opaque_provider_reference_round_trip` — OneDrive-style opaque ID passed verbatim to loadChildren + +**`frontend/src/components/storage/__tests__/StorageBrowser.skeleton.test.js` (3 new tests):** +- Cloud folder rendering with normalized `CloudItemOut` fixtures +- `folder-navigate` event carries `provider_item_id` intact + +**`frontend/src/components/cloud/__tests__/CloudBreadcrumbNavigation.test.js` (new file, 8 tests):** +- `breadcrumb_from_explicit_lineage_not_split` — at root breadcrumb is empty; opaque ref round-trips through navigate +- `breadcrumb_opaque_references_survive_transport` — Google Drive, WebDAV path with spaces + +**Result:** 9 tests fail against `is_dir`/`item.id` behavior as expected (RED confirmed). + +### Task 2 — TDD RED: Server freshness regressions + +Added 8 more tests before implementation: + +**`frontend/src/stores/__tests__/cloudConnections.test.js` (7 new tests):** +- `setBrowseState warning/refreshing/fresh` state mapping +- `does_not_use_browser_clock_as_refresh_evidence` — `refreshedAt` must equal server value exactly +- `cached_items_remain_visible_during_warning` — items not cleared on warning state +- `selectConnection clears cloud browse state` — no secrets persist + +**`frontend/src/views/__tests__/CloudFolderView.test.js` (5 new tests):** +- `maps_server_refreshing_freshness` — `setBrowseState({ freshness: 'refreshing' })` before API resolves +- `maps_server_warning_and_last_success` — HTTP 200 + warning freshness → setBrowseState warning, not fresh +- `http_200_does_not_imply_fresh` — warning response produces no `fresh` setBrowseState call +- `does_not_use_browser_clock_as_refresh_evidence` — `refreshedAt` matches exact server timestamp +- `cached_items_remain_visible_during_warning` — items passed even during warning + +**Result:** 3 new failures against `freshness: 'fresh'` and `new Date().toISOString()` in current code. + +### Task 3 — GREEN: Implement normalized item use + +**`frontend/src/views/CloudFolderView.vue`:** +- Replaced `items.value.filter(i => i.is_dir)` with `i.kind === 'folder'` / `i.kind === 'file'` +- `navigateTo(item)`: builds explicit breadcrumb lineage; navigates via `{ name: 'cloud-folder', params: { folderId: item.provider_item_id } }` +- `handleBreadcrumbNavigate(id)`: trims lineage to clicked node; navigates via named route; opaque refs intact +- `load()`: maps `data.freshness.refresh_state` → store freshness (no fallback to `'fresh'`); maps `data.freshness.last_refreshed_at` → `refreshedAt` (never `new Date()`) +- Session restore: replaced string path with named route object + +**`frontend/src/components/cloud/CloudProviderTreeItem.vue`:** +- `loadChildren`: replaced `i.is_dir` with `i.kind === 'folder'` +- All other logic unchanged (root navigation via `/cloud/{id}/root` string — acceptable for root sentinel) + +**`frontend/src/components/cloud/CloudFolderTreeItem.vue`:** +- `:expandable="folder.kind === 'folder'"` (was `folder.is_dir`) +- Icon template: `v-if="folder.kind === 'folder'"` (was `folder.is_dir`) +- `isActive`: uses `folder.provider_item_id` for route comparison (with `folder.id` fallback for legacy) +- `loadChildren`: uses `props.folder.provider_item_id ?? props.folder.id` as parent_ref +- `navigate()`: uses `provider_item_id ?? id` as route param + +**Verification search:** +``` +rg '\.is_dir' src/views/CloudFolderView.vue src/components/cloud/ +(no output — only appears in comments and test files) +``` + +All 82 focused tests pass; full 369-test suite passes. + +### Task 4 — Documentation, security review, version bump + +- Version bumped `0.2.4 → 0.2.5` in `backend/main.py` and `frontend/package.json` +- `CLAUDE.md`: updated current state; Plan 03 summary +- `AGENTS.md`: updated current state +- `README.md`: version bump +- `SECURITY.md`: T-12.1-11 through T-12.1-15 closed with evidence +- Full frontend suite: 369 passed, 0 failures +- Production build: clean, 495ms +- `npm audit --audit-level=high`: 0 vulnerabilities +- `docker compose config --quiet`: valid +- Pushed to remote + +## Deviations from Plan + +### Auto-fixed Issues + +**1. [Rule 1 - Bug] Test stub captured props before async load completed** +- **Found during:** Task 3 (GREEN phase — tests still failing after implementation) +- **Issue:** `CapturingStub.mounted()` fired synchronously before the `onMounted` async `load()` resolved. Captured `folders` was `[]` even after `flushPromises()`. +- **Fix:** Added `updated()` lifecycle hook to capturing stubs so they re-capture after props update. +- **Files modified:** `frontend/src/views/__tests__/CloudFolderView.test.js` +- **Scope:** Tests only. + +**2. [Rule 1 - Bug] folder-navigate emitted in mounted() before items loaded** +- **Found during:** Task 3 (opaque_reference_round_trip and folder_click tests failing) +- **Issue:** Both test stubs emitted `folder-navigate` in `mounted()` when `this.folders[0]` was still undefined, causing `TypeError: Cannot read properties of undefined (reading 'name')`. +- **Fix:** Moved emit to `updated()` with a one-shot flag so it fires only after folders prop is populated. +- **Files modified:** `frontend/src/views/__tests__/CloudFolderView.test.js` + +## Test Evidence + +| Suite | Tests | Status | +|-------|-------|--------| +| `CloudFolderView.test.js` | 23 | All pass (4 pre-existing + 9 new + 5 freshness + 5 more freshness) | +| `CloudProviderTreeItem.test.js` | 8 | All pass (4 pre-existing + 4 new) | +| `CloudFolderTreeItem.test.js` | 16 | All pass (4 pre-existing + 12 new) | +| `StorageBrowser.skeleton.test.js` | 22 | All pass (19 pre-existing + 3 new) | +| `cloudConnections.test.js` | 23 | All pass (16 pre-existing + 7 new) | +| `CloudBreadcrumbNavigation.test.js` | 8 | All new, all pass | +| Full frontend suite | 369 | All pass (44 test files) | + +**TDD Gate Compliance:** +- RED commit `ba7f652` — 9 tests fail with `is_dir`/`item.id` behavior before implementation +- RED commit `9974fca` — 3 more tests fail with `fresh`/`new Date()` before implementation +- GREEN commit `dc3e172` — all 82 focused tests pass; 369 total pass + +## Security Verification + +Threat model coverage: + +| Threat ID | Mitigation | Evidence | +|-----------|-----------|---------| +| T-12.1-11 | DocuVault UUID not sent as provider path | `navigateTo` uses `item.provider_item_id`; `loadChildren` uses `folder.provider_item_id`; `folder_click_uses_provider_item_id_not_id`, `nested_tree_uses_provider_item_id` | +| T-12.1-12 | UI falsely reports provider synchronization | `load()` maps `data.freshness.refresh_state` verbatim; `http_200_does_not_imply_fresh`, `does_not_use_browser_clock_as_refresh_evidence` | +| T-12.1-13 | Provider reference injected into provider URL | Named route objects; `breadcrumb_opaque_references_survive_transport` | +| T-12.1-14 | Secrets persisted in frontend state | Only `folderId` string stored in sessionStorage; `sessionStorage_stores_only_folder_references_not_tokens` | +| T-12.1-15 | Parallel browser/tree logic drifts | `renders_StorageBrowser_no_parallel_file_grid` asserts no table/grid in view; single StorageBrowser pattern maintained | + +## Known Stubs + +None — all classification, navigation, and freshness paths are fully wired to real API responses. + +## Threat Flags + +None — this plan is frontend-only with no new network endpoints, auth paths, file access patterns, or schema changes. + +## Self-Check: PASSED + +| Check | Result | +|-------|--------| +| `frontend/src/views/CloudFolderView.vue` (kind classification) | FOUND | +| `frontend/src/components/cloud/CloudFolderTreeItem.vue` (provider_item_id) | FOUND | +| `frontend/src/components/cloud/CloudProviderTreeItem.vue` (kind filter) | FOUND | +| `frontend/src/components/cloud/__tests__/CloudBreadcrumbNavigation.test.js` | FOUND | +| Commit ba7f652 (RED tests task 1) | FOUND | +| Commit 9974fca (RED tests task 2) | FOUND | +| Commit dc3e172 (GREEN implementation) | FOUND | +| Commit 7bb046a (docs + version) | FOUND | +| No is_dir in active cloud components | CONFIRMED | +| Full test suite: 369 passed | CONFIRMED |