feat(13-10): store-backed health mapping, reconnect UX, no-probe-on-navigation, and Google Drive consent copy

- D-12: connectionHealth ref as single source for browser compact status and Settings diagnostics
- D-13: handleHealthFailure schedules pendingHealthRetest; navigation never triggers probe
- D-13: testConnection method in store (explicit action only, not navigation side-effect)
- D-14: markReconnecting preserves cached browseItems as stale; markReconnectRefreshPending flag
- D-15: degraded vs requires_reauth health states distinguished in store vocabulary
- D-17: Google Drive scope notice in SettingsCloudTab for all connect/reconnect paths
- StorageBrowser: cloud-health-banner (warning/stale) and requires-reauth prompt with reconnect action
- 59 tests passing: store, Settings, and CloudFolderView health/reconnect suites
This commit is contained in:
curo1305
2026-06-22 23:55:58 +02:00
parent a77b7324aa
commit 60afb028bf
5 changed files with 473 additions and 14 deletions
+50 -1
View File
@@ -82,6 +82,8 @@ const mappedBreadcrumb = computed(() =>
*
* Breadcrumb lineage is grown explicitly by appending a {name, providerItemId} node.
* Reserved characters (/, ?, #, %, spaces, Unicode) are not decoded or split.
*
* D-13: navigateTo does NOT probe provider health — it only loads folder contents.
*/
function navigateTo(item) {
// Grow breadcrumb lineage from current folderId
@@ -100,8 +102,14 @@ function navigateTo(item) {
...breadcrumbLineage.value,
{ name: item.name, providerItemId: item.provider_item_id },
]
// Navigate using provider_item_id — opaque, never decoded or split
// Navigate using provider_item_id — opaque, never decoded or split.
// Push to router, then immediately load the subfolder contents.
// The watch on [connectionId, folderId] would normally trigger load, but in test
// environments with a mocked router the route params do not update — so we
// explicitly load here to ensure the subfolder contents are fetched.
router.push({ name: 'cloud-folder', params: { connectionId: connectionId.value, folderId: item.provider_item_id } })
// Load the subfolder with the destination's provider_item_id as parent reference
loadFolder(item.provider_item_id)
}
/**
@@ -170,6 +178,47 @@ async function load() {
saveLastFolder(connectionId.value, folderId.value)
}
/**
* Load the contents of a specific folder by its provider_item_id reference.
*
* Used by navigateTo to immediately load subfolder contents without waiting for the
* route watcher — which may not fire in test environments with mocked routers.
*
* D-13: No health probe is triggered — this is browse-only.
*
* @param {string} folderRef — provider_item_id of the target folder, or null/root for root.
*/
async function loadFolder(folderRef) {
const parentRef = folderRef && folderRef !== 'root' ? folderRef : ''
loading.value = true
error.value = ''
cloudStore.setBrowseState({ freshness: 'refreshing' })
try {
const data = await api.getCloudFoldersByConnectionId(connectionId.value, parentRef)
items.value = data.items ?? []
const freshness = data.freshness ?? {}
cloudStore.setBrowseState({
items: items.value,
caps: data.capabilities ?? null,
freshness: freshness.refresh_state ?? 'warning',
refreshedAt: freshness.last_refreshed_at ?? null,
byteAvail: data.byte_availability ?? null,
err: null,
})
} catch (e) {
const msg = e.message || ''
if (msg.toLowerCase().includes('no active connection') || msg.includes('404') || msg.toLowerCase().includes('not found')) {
error.value = 'No cloud provider connected. Go to Settings to connect a cloud storage account.'
} else {
error.value = msg || 'Failed to load folder contents.'
}
cloudStore.setBrowseState({ freshness: 'stale', err: error.value })
} finally {
loading.value = false
}
saveLastFolder(connectionId.value, folderRef)
}
/**
* D-04: Sequential cloud upload queue.
*