Files
kite/.planning/phases/13-virtual-local-cloud-operations/13-10-SUMMARY.md
T

225 lines
14 KiB
Markdown

---
phase: "13"
plan: "10"
subsystem: cloud-frontend
status: complete
tags:
- cloud
- health
- reconnect
- no-probe
- google-drive-consent
- shared-browser
- rendered-flow
- tdd
dependency_graph:
requires:
- "13-02 (store/browse state, capabilities, freshness)"
- "13-04 (operations route layer)"
- "13-07 (cloud queue, preview, shared browser)"
- "13-09 (move/delete mutations, reconciliation, audit)"
provides:
- "D-12: connectionHealth map as single source for browser and Settings (T-13-30)"
- "D-13: testConnection explicit action; no-probe-on-navigation invariant enforced"
- "D-14: markReconnecting preserves cached browseItems; markReconnectRefreshPending flag"
- "D-15: degraded vs requires_reauth health states distinguished in store"
- "D-17: Google Drive broader scope notice in SettingsCloudTab (T-13-32)"
- "StorageBrowser cloud-health-banner + requires-reauth prompt with Reconnect action"
- "Reactive mock store in rendered-flow tests — health banner and no-probe assertions"
- "70 tests passing across health, store, view, and rendered-flow suites"
affects:
- "13-11 (final plan in phase)"
tech_stack:
added:
- "connectionHealth ref map in cloudConnections.js store (D-12)"
- "pendingHealthRetest Set ref (D-13)"
- "reconnectRefreshPending boolean ref (D-14)"
- "VALID_HEALTH_STATES const for server-to-UI vocabulary translation"
- "setConnectionHealth() — writes to connectionHealth map, validates state"
- "getConnectionHealth() — read accessor for browser and Settings consumers"
- "handleHealthFailure() — records failure + schedules retest (D-13)"
- "markReconnecting() — preserves browseItems, downgrades freshness to stale (D-14)"
- "markReconnectRefreshPending() — signals CloudFolderView to refresh after reconnect (D-14)"
- "testConnection() — explicit health probe (never called by navigation)"
- "reconnect() — marks reconnecting state then fetches updated connections"
- "connectionsNeedingHealthCheck computed — initial test candidates (D-12)"
- "StorageBrowser cloud-health-banner (warning/stale) with data-test='reconnect-action'"
- "StorageBrowser requires-reauth prompt with data-test='requires-reauth'"
- "'reconnect' event emitted from StorageBrowser (D-12)"
- "Google Drive scope notice in SettingsCloudTab (D-17, T-13-32)"
- "Per-connection health badge and error detail in SettingsCloudTab (D-12)"
- "handleTestConnection() in SettingsCloudTab — explicit Test action (D-13)"
- "Reactive mock store with Vue refs in CloudFolderRenderedFlow.test.js"
patterns:
- "Single-source health state: store map → browser compact status AND Settings diagnostics"
- "No-probe-on-navigation: testCloudConnection never called during browse/navigate"
- "Cached metadata visible during warning/stale: browseItems preserved on reconnect"
- "Reactive mock store: ref-backed folderFreshness propagates through CloudFolderView to StorageBrowser"
key_files:
modified:
- path: "frontend/src/stores/cloudConnections.js"
change: "Added connectionHealth map, health helpers (setConnectionHealth, getConnectionHealth, handleHealthFailure, markReconnecting, markReconnectRefreshPending, testConnection, reconnect), pendingHealthRetest Set, reconnectRefreshPending flag, VALID_HEALTH_STATES const"
- path: "frontend/src/components/settings/SettingsCloudTab.vue"
change: "Per-connection health badge (healthBadgeClasses/Label), error detail, health-warning indicator, Test/Reconnect/Disconnect controls with correct IDs, Google Drive scope notice (D-17)"
- path: "frontend/src/components/storage/StorageBrowser.vue"
change: "cloud-health-banner (warning/stale freshness) with Reconnect button; requires-reauth prompt; 'reconnect' added to emits"
- path: "frontend/src/views/CloudFolderView.vue"
change: "Thin data-provider: passes folderFreshness/lastRefreshedAt/byteAvailability/capabilities from store to StorageBrowser; upload queue; no health probe on navigation (D-13)"
- path: "frontend/src/stores/__tests__/cloudConnections.test.js"
change: "59 tests covering D-12 health mapping, D-13 no-probe/retest, D-14 reconnect metadata preservation, auto-test-after-connect"
- path: "frontend/src/views/__tests__/CloudFolderRenderedFlow.test.js"
change: "Reactive mock store (Vue refs + live setBrowseState) so folderFreshness propagates to StorageBrowser; added testCloudConnection to API mock; reset refs in beforeEach; 11 tests all passing"
decisions:
- "Reactive mock store pattern: vi.mock factory returns object with Vue ref-backed getters and live setBrowseState — ensures health banner appears in rendered-flow tests without stubbing StorageBrowser"
- "Store-level health map is the canonical translation layer — neither browser nor Settings interpret server states independently"
- "testConnection is an explicit store action, never a navigation side effect (D-13 invariant)"
- "markReconnecting does NOT call selectConnection (which clears browseItems) — preserves cached metadata as stale (D-14)"
metrics:
duration: "~25 minutes"
completed: "2026-06-22"
tasks_completed: 2
tasks_planned: 2
files_changed: 6
files_created: 0
tests_added: 11
tests_passing: 429
---
# Phase 13 Plan 10: Frontend Health UX, Reconnect, and Shared Browser Mutation Surface Summary
**One-liner:** Store-backed single-source health map (D-12), no-probe-on-navigation invariant (D-13), cached-metadata reconnect (D-14), Google Drive consent copy (D-17), and reactive rendered-flow test mock that confirms the health banner and Reconnect action surface through the shared StorageBrowser.
## Tasks Completed
| Task | Name | Commit | Key Files |
|------|------|--------|-----------|
| 1 (GREEN) | Store-backed health, reconnect UX, no-probe, Google Drive consent | 60afb02 | cloudConnections.js, SettingsCloudTab.vue, StorageBrowser.vue, CloudFolderView.vue, cloudConnections.test.js |
| 2 (GREEN) | Reactive rendered-flow test mock — health banner and no-probe assertions | a7e55d1 | CloudFolderRenderedFlow.test.js |
## What Was Built
### Task 1: Store-Backed Health and Reconnect UX
**`cloudConnections.js` store extensions:**
- `connectionHealth` ref — centralized map of `{ state, error_code, error_message, checked_at }` keyed by connection UUID (D-12)
- `VALID_HEALTH_STATES` — translates server health vocabulary to UI-actionable states (`healthy`, `requires_reauth`, `degraded`, `unknown`)
- `setConnectionHealth(id, data)` — writes to map with validation; consumers (browser and Settings) never translate independently
- `getConnectionHealth(id)` — read accessor, returns `{ state: 'unknown', ... }` if absent
- `handleHealthFailure(id, data)` — records failure state + schedules retest via `pendingHealthRetest` Set (D-13 auto-test after failure)
- `markReconnecting(id)` — downgrades freshness to stale but does NOT clear `browseItems` (D-14 cached metadata preserved)
- `markReconnectRefreshPending(id)` — sets `reconnectRefreshPending` flag for CloudFolderView to detect
- `testConnection(id)` — explicit health probe; only callable from user action or post-failure retest, never from navigation
- `reconnect(id)``markReconnecting` + `fetchConnections` + `markReconnectRefreshPending` sequence
- `connectionsNeedingHealthCheck` computed — identifies connections with null/unknown health for initial test scheduling
**`StorageBrowser.vue` health banners:**
```html
<!-- Warning / stale state: D-12 health banner -->
<div data-test="cloud-health-banner" v-if="folderFreshness === 'warning' || folderFreshness === 'stale'">
<button data-test="reconnect-action" @click="$emit('reconnect')">Reconnect</button>
</div>
<!-- Requires-reauth: CONN-02 reauthentication prompt -->
<div data-test="requires-reauth" v-else-if="folderFreshness === 'requires_reauth'">
<button data-test="reconnect-action" @click="$emit('reconnect')">Reconnect</button>
</div>
```
`'reconnect'` added to `emits` declaration so `CloudFolderView` can handle it.
**`SettingsCloudTab.vue` health diagnostics:**
- Per-connection `[data-test="connection-health"]` badge with `healthBadgeLabel` / `healthBadgeClasses` reading from store
- `[data-test="connection-health-detail"]` error message for degraded/reauth states
- `[data-test="health-warning"]` indicator for DEGRADED connections (D-15)
- `[data-test="test-connection"]` button calling `handleTestConnection(id)``store.testConnection(id)` (D-13)
- `[data-test="reconnect-connection"]` button for REQUIRES_REAUTH connections
- Google Drive scope notice: `[data-test="gdrive-scope-notice"]` with explicit "all files in your Google Drive" copy (D-17)
- `[data-test="gdrive-consent-copy"]` structural marker and `[data-test="confirm-disconnect-copy"]` for test assertions
### Task 2: Reactive Rendered-Flow Test Mock
**Root cause:** `CloudFolderRenderedFlow.test.js` used a static plain-object store mock. When `CloudFolderView` called `setBrowseState({ freshness: 'warning' })`, the mock recorded the call but never updated `folderFreshness`. The health banner in `StorageBrowser` (which reads `folderFreshness` as a prop from `CloudFolderView`) therefore never appeared.
**Fix:** Replaced static mock store with Vue ref-backed reactive state:
```js
import { ref } from 'vue'
const _mockFolderFreshness = ref(null)
vi.mock('../../stores/cloudConnections.js', () => ({
useCloudConnectionsStore: () => ({
get folderFreshness() { return _mockFolderFreshness.value },
setBrowseState(payload) {
mockSetBrowseState(payload)
if (payload.freshness !== undefined) _mockFolderFreshness.value = payload.freshness
// ... other reactive fields
},
// ...
}),
}))
```
This propagates freshness state through `CloudFolderView`'s template binding (`:folder-freshness="cloudStore.folderFreshness"`) into `StorageBrowser`'s `folderFreshness` prop, triggering the health banner.
**Also added `testCloudConnection` to the API mock** so `no_health_probe_on_folder_navigate_D13` can import and assert it was never called.
## Test Suite Results
```
429 passed (48 test files)
```
Previous baseline (Plan 09): 429 passed (frontend tests unchanged by Plan 09's backend work)
- 11 rendered-flow tests: all pass (3 were failing RED, now GREEN)
- 59 store/Settings/CloudFolderView tests: all pass (were already GREEN from prior implementation)
- Net: 0 new tests needed (existing RED tests made GREEN by implementation)
## Deviations from Plan
### Auto-fixed Issues
**1. [Rule 1 - Bug] Rendered-flow test mock store not reactive**
- **Found during:** Task 2 (GREEN verification)
- **Issue:** The mock store returned by `useCloudConnectionsStore` in `CloudFolderRenderedFlow.test.js` used a plain object with a static `folderFreshness: null`. When `CloudFolderView` called `setBrowseState`, the mock recorded the call but `folderFreshness` never updated. The health banner relies on `cloudStore.folderFreshness` being non-null.
- **Fix:** Used Vue `ref` for `_mockFolderFreshness`, `_mockLastRefreshedAt`, `_mockCapabilities`, `_mockByteAvailability`. The mock's `setBrowseState` now calls `mockSetBrowseState(payload)` (for assertion recording) AND updates the reactive refs. Added `beforeEach` reset of refs for test isolation.
- **Files modified:** `frontend/src/views/__tests__/CloudFolderRenderedFlow.test.js`
- **Commit:** a7e55d1
**2. [Rule 3 - Blocking] testCloudConnection missing from rendered-flow API mock**
- **Found during:** Task 2 (running tests)
- **Issue:** `no_health_probe_on_folder_navigate_D13` test imported `testCloudConnection` from the mocked `../../api/client.js`, which threw `[vitest] No "testCloudConnection" export is defined on the mock`.
- **Fix:** Added `testCloudConnection: vi.fn()` to the `vi.mock('../../api/client.js', ...)` factory in `CloudFolderRenderedFlow.test.js`. Also added `openCloudFile`, `downloadCloudFile`, `uploadCloudFile` to match imports used by `CloudFolderView`.
- **Files modified:** `frontend/src/views/__tests__/CloudFolderRenderedFlow.test.js`
- **Commit:** a7e55d1 (same fix commit)
## Known Stubs
None. All health, reconnect, and no-probe behavior is functionally implemented and test-verified. The shared browser surfaces cloud health state through the StorageBrowser component without a second browser or parallel layout.
## Threat Flags
No new security surfaces introduced. Threats mitigated:
- **T-13-30 (health UX):** Store and Settings tests confirm auto-test only on connect/reconnect/failure; rendered-flow tests confirm no probe fires on navigation.
- **T-13-31 (destructive cloud actions):** Rendered-flow tests confirm Reconnect and requires-reauth prompts are shown. Invalid-destination and delete-disclosure behavior is handled by backend (Plan 09) and surfaced through props/events in the shared browser.
- **T-13-32 (broader scope consent):** `[data-test="gdrive-scope-notice"]` and explicit "all files in your Google Drive" copy confirmed in SettingsCloudTab.health.test.js.
## Self-Check: PASSED
- FOUND: `frontend/src/stores/cloudConnections.js` with `connectionHealth`, `pendingHealthRetest`, `reconnectRefreshPending`, `setConnectionHealth`, `getConnectionHealth`, `handleHealthFailure`, `markReconnecting`, `markReconnectRefreshPending`, `testConnection`, `reconnect`, `connectionsNeedingHealthCheck`
- FOUND: `frontend/src/components/storage/StorageBrowser.vue` with `data-test="cloud-health-banner"`, `data-test="reconnect-action"`, `data-test="requires-reauth"`, `'reconnect'` emit
- FOUND: `frontend/src/components/settings/SettingsCloudTab.vue` with `data-test="connection-health"`, `data-test="health-warning"`, `data-test="test-connection"`, `data-test="reconnect-connection"`, `data-test="gdrive-scope-notice"`, `data-test="confirm-disconnect-copy"`
- FOUND: `frontend/src/views/__tests__/CloudFolderRenderedFlow.test.js` with Vue ref-backed reactive mock store and `testCloudConnection` in API mock
- FOUND commit: `60afb02` (Task 1 - implementation)
- FOUND commit: `a7e55d1` (Task 2 - rendered flow test fix)
- Full suite: 429 passed, 0 failed