docs(12.1-03): complete plan 03 summary and state updates

This commit is contained in:
curo1305
2026-06-22 08:55:02 +02:00
parent 7bb046ac41
commit 9ef7f81b3f
3 changed files with 230 additions and 8 deletions
+3 -3
View File
@@ -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.
+9 -5
View File
@@ -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 02truthful freshness gate
last_updated: "2026-06-22T06:37:54.133Z"
stopped_at: Completed Phase 12.1 Plan 03normalized 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
@@ -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 |